Merge lp:~mbp/bzr/progress into lp:bzr

Proposed by Martin Pool
Status: Rejected
Rejected by: Martin Pool
Proposed branch: lp:~mbp/bzr/progress
Merge into: lp:bzr
Diff against target: 559 lines (+184/-100)
10 files modified
NEWS (+22/-0)
bzrlib/branch.py (+4/-0)
bzrlib/errors.py (+16/-8)
bzrlib/help_topics/en/patterns.txt (+14/-4)
bzrlib/progress.py (+0/-22)
bzrlib/tests/blackbox/test_commit.py (+16/-0)
bzrlib/tests/test_errors.py (+34/-1)
bzrlib/tests/test_progress.py (+37/-4)
bzrlib/tests/test_ui.py (+4/-48)
bzrlib/ui/text.py (+37/-13)
To merge this branch: bzr merge lp:~mbp/bzr/progress
Reviewer Review Type Date Requested Status
John A Meinel Needs Fixing
bzr-core Pending
Review via email: mp+29605@code.launchpad.net

Commit message

Truncate progress messages rather than counters

Description of the change

* Truncate the description rather than the counter, so that you don't get misleading numbers.

* Put the spinner (or bar) between the transport activity and the current progress task, so we don't have two '|' on the line, one of them apparently stuck.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

sent to pqm by email

Revision history for this message
John A Meinel (jameinel) wrote :
Download full text (3.4 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/progress into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> #388266 many uses of DummyProgress are unnecessary
> https://bugs.launchpad.net/bugs/388266
>
>
> * Truncate the description rather than the counter, so that you don't get misleading numbers.
>
> * Put the spinner (or bar) between the transport activity and the current progress task, so we don't have two '|' on the line, one of them apparently stuck.
>

Failing in PQM with:

> ======================================================================
> FAIL: bzrlib.tests.test_ui.TestTextUIFactory.test_progress_note_clears
> ----------------------------------------------------------------------
> _StringException: Text attachment: log
> ------------
> 4446.457 Deprecated method called
> Called from:
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/test_ui.py", line 138, in test_progress_n
> ote_clears
> pb.note, 't')
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 1389, in applyDeprecat
> ed
> call_warnings, result = self._capture_deprecation_warnings(a_callable,
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/__init__.py", line 1357, in _capture_depr
> ecation_warnings
> result = a_callable(*args, **kwargs)
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/symbol_versioning.py", line 132, in decorated_m
> ethod
> trace.mutter_callsite(4, "Deprecated method called")
> ------------
> Text attachment: traceback
> ------------
> Traceback (most recent call last):
> File "/usr/lib/python2.4/site-packages/testtools/runtest.py", line 128, in _run_user
> return fn(*args)
> File "/usr/lib/python2.4/site-packages/testtools/testcase.py", line 368, in _run_test_method
> testMethod()
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/test_ui.py", line 144, in test_progress_n
> ote_clears
> self.assertContainsRe(stderr.getvalue(), r'\r {10,}\r$')
> AssertionError: pattern "\r {10,}\r$" not found in
> """\
> """ 1/1
>
> ------------
>
>
> ======================================================================
> FAIL: bzrlib.tests.test_ui.TestTextUIFactory.test_text_factory_prompts_and_clears
> ----------------------------------------------------------------------
> _StringException: Text attachment: log
> ------------
>
> ------------
> Text attachment: traceback
> ------------
> Traceback (most recent call last):
> File "/usr/lib/python2.4/site-packages/testtools/runtest.py", line 128, in _run_user
> return fn(*args)
> File "/usr/lib/python2.4/site-packages/testtools/testcase.py", line 368, in _run_test_method
> testMethod()
> File "/home/pqm/bzr-pqm-workdir/home/+trunk/bzrlib/tests/test_ui.py", line 208, in test_text_facto
> ry_prompts_and_clears
> "foo *\r\r *\r*")
> *" not found in pattern "foo *
> """\
> what do you want? [y/n]: what do you want? [y/n]: """
>
> ------------
>
>
> ----------------------------------------------------------------------
> Ran 45 tests in 6.348s
>
> FAILED (failures=2)

John
=:->

-----BEGIN P...

Read more...

Revision history for this message
John A Meinel (jameinel) wrote :

Failing in PQM as sent.

review: Needs Fixing
Revision history for this message
Martin Pool (mbp) wrote :

thanks for submitting it, I'll look into those failures

--
Martin

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin Pool wrote:
> thanks for submitting it, I'll look into those failures
>

Yeah, I was trying to get it into 2.2b4, since it is sort of an api
change (at least a visual one to the user). It is probably ok to land
between 2.2b4 and 2.2rc1, but I had hoped to not have to think about
that. :)

Speaking of which MergeIntoMerger... should that be pushed back to 2.3?

John
=:->

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkw5ifAACgkQJdeBCYSNAANHgwCgkEu4oRPpIMpf/sW4yjP+8lPx
e6oAnjwZjH63WvldPJKvpVU646KJC7LP
=CMbD
-----END PGP SIGNATURE-----

Revision history for this message
Martin Pool (mbp) wrote :

On 11 July 2010 11:10, John A Meinel <email address hidden> wrote:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Martin Pool wrote:
>> thanks for submitting it, I'll look into those failures
>>
>
> Yeah, I was trying to get it into 2.2b4, since it is sort of an api
> change (at least a visual one to the user). It is probably ok to land
> between 2.2b4 and 2.2rc1, but I had hoped to not have to think about
> that. :)

I think fairly small cosmetic ui changes are ok, certainly before the
2.2.0 release.

> Speaking of which MergeIntoMerger... should that be pushed back to 2.3?

From my memory of the type of changes, it seems like it may break some
plugins. Perhaps if we test the likely suspects and find that it
doesn't...

--
Martin

Revision history for this message
Martin Pool (mbp) wrote :

Unmerged revisions

5809. By Martin Pool

Just testing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-07-13 21:25:03 +0000
+++ NEWS 2010-07-17 16:48:48 +0000
@@ -45,6 +45,12 @@
45Compatibility Breaks45Compatibility Breaks
46********************46********************
4747
48* BzrError subclasses no longer support the name "message" to be used
49 as an argument for __init__ or in _fmt format specification as this
50 breaks in some Python versions. errors.LockError.__init__ argument
51 is now named "msg" instead of earlier "message".
52 (Parth Malwankar, #603461)
53
48New Features54New Features
49************55************
5056
@@ -58,21 +64,37 @@
58* Don't traceback trying to unversion children files of an already64* Don't traceback trying to unversion children files of an already
59 unversioned directory. (Vincent Ladeuil, #494221)65 unversioned directory. (Vincent Ladeuil, #494221)
6066
67* Progress bars prefer to truncate the text message rather than the
68 counters. The spinner is shown between the network transfer indicator
69 and the progress message. (Martin Pool)
70
71* Recursive binding for checkouts is now detected by bzr. A clear error
72 message is shown to the user. (Parth Malwankar, #405192)
73
61Improvements74Improvements
62************75************
6376
64Documentation77Documentation
65*************78*************
6679
80* ``bzr help patterns`` now explains case insensitive patterns and
81 points to Python regular expression documentation.
82 (Parth Malwankar, #594386)
83
67API Changes84API Changes
68***********85***********
6986
87* Delete ``ProgressTask.note``, which was deprecated in 2.1.
88
70Internals89Internals
71*********90*********
7291
73Testing92Testing
74*******93*******
7594
95* Unit test added to ensure that "message" is not uses as a format variable
96 name in BzrError subclasses as this conflicts with some Python versions.
97 (Parth Malwankar, #603461)
7698
77bzr 2.2b499bzr 2.2b4
78#########100#########
79101
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2010-06-30 08:34:11 +0000
+++ bzrlib/branch.py 2010-07-17 16:48:48 +0000
@@ -246,9 +246,13 @@
246 if not local and not config.has_explicit_nickname():246 if not local and not config.has_explicit_nickname():
247 try:247 try:
248 master = self.get_master_branch(possible_transports)248 master = self.get_master_branch(possible_transports)
249 if master and self.user_url == master.user_url:
250 raise errors.RecursiveBind(self.user_url)
249 if master is not None:251 if master is not None:
250 # return the master branch value252 # return the master branch value
251 return master.nick253 return master.nick
254 except errors.RecursiveBind, e:
255 raise e
252 except errors.BzrError, e:256 except errors.BzrError, e:
253 # Silently fall back to local implicit nick if the master is257 # Silently fall back to local implicit nick if the master is
254 # unavailable258 # unavailable
255259
=== modified file 'bzrlib/errors.py'
--- bzrlib/errors.py 2010-07-09 16:16:11 +0000
+++ bzrlib/errors.py 2010-07-17 16:48:48 +0000
@@ -947,11 +947,8 @@
947 # original exception is available as e.original_error947 # original exception is available as e.original_error
948 #948 #
949 # New code should prefer to raise specific subclasses949 # New code should prefer to raise specific subclasses
950 def __init__(self, message):950 def __init__(self, msg):
951 # Python 2.5 uses a slot for StandardError.message,951 self.msg = msg
952 # so use a different variable name. We now work around this in
953 # BzrError.__str__, but this member name is kept for compatability.
954 self.msg = message
955952
956953
957class LockActive(LockError):954class LockActive(LockError):
@@ -1378,12 +1375,12 @@
13781375
1379class WeaveParentMismatch(WeaveError):1376class WeaveParentMismatch(WeaveError):
13801377
1381 _fmt = "Parents are mismatched between two revisions. %(message)s"1378 _fmt = "Parents are mismatched between two revisions. %(msg)s"
13821379
13831380
1384class WeaveInvalidChecksum(WeaveError):1381class WeaveInvalidChecksum(WeaveError):
13851382
1386 _fmt = "Text did not match it's checksum: %(message)s"1383 _fmt = "Text did not match it's checksum: %(msg)s"
13871384
13881385
1389class WeaveTextDiffers(WeaveError):1386class WeaveTextDiffers(WeaveError):
@@ -1437,7 +1434,7 @@
14371434
1438class VersionedFileInvalidChecksum(VersionedFileError):1435class VersionedFileInvalidChecksum(VersionedFileError):
14391436
1440 _fmt = "Text did not match its checksum: %(message)s"1437 _fmt = "Text did not match its checksum: %(msg)s"
14411438
14421439
1443class KnitError(InternalBzrError):1440class KnitError(InternalBzrError):
@@ -3149,12 +3146,14 @@
3149 def __init__(self, bzrdir):3146 def __init__(self, bzrdir):
3150 self.bzrdir = bzrdir3147 self.bzrdir = bzrdir
31513148
3149
3152class NoWhoami(BzrError):3150class NoWhoami(BzrError):
31533151
3154 _fmt = ('Unable to determine your name.\n'3152 _fmt = ('Unable to determine your name.\n'
3155 "Please, set your name with the 'whoami' command.\n"3153 "Please, set your name with the 'whoami' command.\n"
3156 'E.g. bzr whoami "Your Name <name@example.com>"')3154 'E.g. bzr whoami "Your Name <name@example.com>"')
31573155
3156
3158class InvalidPattern(BzrError):3157class InvalidPattern(BzrError):
31593158
3160 _fmt = ('Invalid pattern(s) found. %(msg)s')3159 _fmt = ('Invalid pattern(s) found. %(msg)s')
@@ -3162,3 +3161,12 @@
3162 def __init__(self, msg):3161 def __init__(self, msg):
3163 self.msg = msg3162 self.msg = msg
31643163
3164
3165class RecursiveBind(BzrError):
3166
3167 _fmt = ('Branch "%(branch_url)s" appears to be bound to itself. '
3168 'Please use `bzr unbind` to fix.')
3169
3170 def __init__(self, branch_url):
3171 self.branch_url = branch_url
3172
31653173
=== modified file 'bzrlib/help_topics/en/patterns.txt'
--- bzrlib/help_topics/en/patterns.txt 2010-01-02 07:36:36 +0000
+++ bzrlib/help_topics/en/patterns.txt 2010-07-17 16:48:48 +0000
@@ -10,7 +10,7 @@
10slash or is a regular expression, it is compared to the whole path10slash or is a regular expression, it is compared to the whole path
11from the branch root. Otherwise, it is compared to only the last11from the branch root. Otherwise, it is compared to only the last
12component of the path. To match a file only in the root directory,12component of the path. To match a file only in the root directory,
13prepend './'. Patterns specifying absolute paths are not allowed.13prepend ``./``. Patterns specifying absolute paths are not allowed.
1414
15Patterns may include globbing wildcards such as::15Patterns may include globbing wildcards such as::
1616
@@ -19,10 +19,20 @@
19 /**/ - Matches 0 or more directories in a path19 /**/ - Matches 0 or more directories in a path
20 [a-z] - Matches a single character from within a group of characters20 [a-z] - Matches a single character from within a group of characters
2121
22Patterns may also be Python regular expressions. Regular expression22Patterns may also be `Python regular expressions`_. Regular expression
23patterns are identified by a 'RE:' prefix followed by the regular23patterns are identified by a ``RE:`` prefix followed by the regular
24expression. Regular expression patterns may not include named or24expression. Regular expression patterns may not include named or
25numbered groups.25numbered groups.
2626
27Ignore patterns may be prefixed with '!', which means that a filename27Case insensitive ignore patterns can be specified with regular expressions
28by using the ``i`` (for ignore case) flag in the pattern.
29
30For example, a case insensitive match for ``foo`` may be specified as::
31
32 RE:(?i)foo
33
34Ignore patterns may be prefixed with ``!``, which means that a filename
28matched by that pattern will not be ignored.35matched by that pattern will not be ignored.
36
37.. _Python regular expressions: http://docs.python.org/library/re.html
38
2939
=== modified file 'bzrlib/progress.py'
--- bzrlib/progress.py 2010-04-30 11:03:59 +0000
+++ bzrlib/progress.py 2010-07-17 16:48:48 +0000
@@ -152,19 +152,6 @@
152 own_fraction = 0.0152 own_fraction = 0.0
153 return self._parent_task._overall_completion_fraction(own_fraction)153 return self._parent_task._overall_completion_fraction(own_fraction)
154154
155 @deprecated_method(deprecated_in((2, 1, 0)))
156 def note(self, fmt_string, *args):
157 """Record a note without disrupting the progress bar.
158
159 Deprecated: use ui_factory.note() instead or bzrlib.trace. Note that
160 ui_factory.note takes just one string as the argument, not a format
161 string and arguments.
162 """
163 if args:
164 self.ui_factory.note(fmt_string % args)
165 else:
166 self.ui_factory.note(fmt_string)
167
168 def clear(self):155 def clear(self):
169 # TODO: deprecate this method; the model object shouldn't be concerned156 # TODO: deprecate this method; the model object shouldn't be concerned
170 # with whether it's shown or not. Most callers use this because they157 # with whether it's shown or not. Most callers use this because they
@@ -220,12 +207,6 @@
220 self.clear()207 self.clear()
221 self._stack.return_pb(self)208 self._stack.return_pb(self)
222209
223 def note(self, fmt_string, *args, **kwargs):
224 """Record a note without disrupting the progress bar."""
225 self.clear()
226 self.to_messages_file.write(fmt_string % args)
227 self.to_messages_file.write('\n')
228
229210
230class DummyProgress(object):211class DummyProgress(object):
231 """Progress-bar standin that does nothing.212 """Progress-bar standin that does nothing.
@@ -248,9 +229,6 @@
248 def clear(self):229 def clear(self):
249 pass230 pass
250231
251 def note(self, fmt_string, *args, **kwargs):
252 """See _BaseProgressBar.note()."""
253
254 def child_progress(self, **kwargs):232 def child_progress(self, **kwargs):
255 return DummyProgress(**kwargs)233 return DummyProgress(**kwargs)
256234
257235
=== modified file 'bzrlib/tests/blackbox/test_commit.py'
--- bzrlib/tests/blackbox/test_commit.py 2010-06-28 02:41:22 +0000
+++ bzrlib/tests/blackbox/test_commit.py 2010-07-17 16:48:48 +0000
@@ -22,6 +22,7 @@
22import sys22import sys
2323
24from bzrlib import (24from bzrlib import (
25 bzrdir,
25 osutils,26 osutils,
26 ignores,27 ignores,
27 msgeditor,28 msgeditor,
@@ -757,3 +758,18 @@
757 osutils.set_or_unset_env('BZR_EMAIL', None)758 osutils.set_or_unset_env('BZR_EMAIL', None)
758 out, err = self.run_bzr(['commit', '-m', 'initial'], 3)759 out, err = self.run_bzr(['commit', '-m', 'initial'], 3)
759 self.assertContainsRe(err, 'Unable to determine your name')760 self.assertContainsRe(err, 'Unable to determine your name')
761
762 def test_commit_recursive_checkout(self):
763 """Ensure that a commit to a recursive checkout fails cleanly.
764 """
765 self.run_bzr(['init', 'test_branch'])
766 self.run_bzr(['checkout', 'test_branch', 'test_checkout'])
767 os.chdir('test_checkout')
768 self.run_bzr(['bind', '.']) # bind to self
769 open('foo.txt', 'w').write('hello')
770 self.run_bzr(['add'])
771 out, err = self.run_bzr(['commit', '-m', 'addedfoo'], 3)
772 self.assertEqual(out, '')
773 self.assertContainsRe(err,
774 'Branch.*test_checkout.*appears to be bound to itself')
775
760776
=== modified file 'bzrlib/tests/test_errors.py'
--- bzrlib/tests/test_errors.py 2010-07-09 16:16:11 +0000
+++ bzrlib/tests/test_errors.py 2010-07-17 16:48:48 +0000
@@ -16,6 +16,8 @@
1616
17"""Tests for the formatting and construction of errors."""17"""Tests for the formatting and construction of errors."""
1818
19import inspect
20import re
19import socket21import socket
20import sys22import sys
2123
@@ -26,11 +28,36 @@
26 symbol_versioning,28 symbol_versioning,
27 urlutils,29 urlutils,
28 )30 )
29from bzrlib.tests import TestCase, TestCaseWithTransport31from bzrlib.tests import TestCase, TestCaseWithTransport, TestSkipped
3032
3133
32class TestErrors(TestCaseWithTransport):34class TestErrors(TestCaseWithTransport):
3335
36 def test_no_arg_named_message(self):
37 """Ensure the __init__ and _fmt in errors do not have "message" arg.
38
39 This test fails if __init__ or _fmt in errors has an argument
40 named "message" as this can cause errors in some Python versions.
41 Python 2.5 uses a slot for StandardError.message.
42 See bug #603461
43 """
44 fmt_pattern = re.compile("%\(message\)[sir]")
45 subclasses_present = getattr(errors.BzrError, '__subclasses__', None)
46 if not subclasses_present:
47 raise TestSkipped('__subclasses__ attribute required for classes. '
48 'Requires Python 2.5 or later.')
49 for c in errors.BzrError.__subclasses__():
50 init = getattr(c, '__init__', None)
51 fmt = getattr(c, '_fmt', None)
52 if init:
53 args = inspect.getargspec(init)[0]
54 self.assertFalse('message' in args,
55 ('Argument name "message" not allowed for '
56 '"errors.%s.__init__"' % c.__name__))
57 if fmt and fmt_pattern.search(fmt):
58 self.assertFalse(True, ('"message" not allowed in '
59 '"errors.%s._fmt"' % c.__name__))
60
34 def test_bad_filename_encoding(self):61 def test_bad_filename_encoding(self):
35 error = errors.BadFilenameEncoding('bad/filen\xe5me', 'UTF-8')62 error = errors.BadFilenameEncoding('bad/filen\xe5me', 'UTF-8')
36 self.assertEqualDiff(63 self.assertEqualDiff(
@@ -660,6 +687,12 @@
660 self.assertEqualDiff("Invalid pattern(s) found. Bad pattern msg.",687 self.assertEqualDiff("Invalid pattern(s) found. Bad pattern msg.",
661 str(error))688 str(error))
662689
690 def test_recursive_bind(self):
691 error = errors.RecursiveBind('foo_bar_branch')
692 msg = ('Branch "foo_bar_branch" appears to be bound to itself. '
693 'Please use `bzr unbind` to fix.')
694 self.assertEqualDiff(msg, str(error))
695
663696
664class PassThroughError(errors.BzrError):697class PassThroughError(errors.BzrError):
665698
666699
=== modified file 'bzrlib/tests/test_progress.py'
--- bzrlib/tests/test_progress.py 2010-02-17 17:11:16 +0000
+++ bzrlib/tests/test_progress.py 2010-07-17 16:48:48 +0000
@@ -58,7 +58,7 @@
58 def make_view(self):58 def make_view(self):
59 out = StringIO()59 out = StringIO()
60 view = TextProgressView(out)60 view = TextProgressView(out)
61 view._width = 8061 view._avail_width = lambda: 79
62 return out, view62 return out, view
63 63
64 def make_task(self, parent_task, view, msg, curr, total):64 def make_task(self, parent_task, view, msg, curr, total):
@@ -100,13 +100,13 @@
100 # so we're in the first half of the main task, and half way through100 # so we're in the first half of the main task, and half way through
101 # that101 # that
102 self.assertEqual(102 self.assertEqual(
103r'[####- ] reticulating splines:stage2 1/2'103'[####- ] reticulating splines:stage2 1/2 '
104 , view._render_line())104 , view._render_line())
105 # if the nested task is complete, then we're all the way through the105 # if the nested task is complete, then we're all the way through the
106 # first half of the overall work106 # first half of the overall work
107 task2.update('stage2', 2, 2)107 task2.update('stage2', 2, 2)
108 self.assertEqual(108 self.assertEqual(
109r'[#########\ ] reticulating splines:stage2 2/2'109'[#########\ ] reticulating splines:stage2 2/2 '
110 , view._render_line())110 , view._render_line())
111111
112 def test_render_progress_sub_nested(self):112 def test_render_progress_sub_nested(self):
@@ -123,5 +123,38 @@
123 # progress indication, just a label; and the bottom one is half done,123 # progress indication, just a label; and the bottom one is half done,
124 # so the overall fraction is 1/4124 # so the overall fraction is 1/4
125 self.assertEqual(125 self.assertEqual(
126 r'[####| ] a:b:c 1/2'126'[####| ] a:b:c 1/2 '
127 , view._render_line())127 , view._render_line())
128
129 def test_render_truncated(self):
130 # when the bar is too long for the terminal, we prefer not to truncate
131 # the counters because they might be interesting, and because
132 # truncating the numbers might be misleading
133 out, view = self.make_view()
134 task_a = ProgressTask(None, progress_view=view)
135 task_a.update('start_' + 'a' * 200 + '_end', 2000, 5000)
136 line = view._render_line()
137 self.assertEqual(
138'- start_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.. 2000/5000',
139 line)
140 self.assertEqual(len(line), 79)
141
142
143 def test_render_with_activity(self):
144 # if the progress view has activity, it's shown before the spinner
145 out, view = self.make_view()
146 task_a = ProgressTask(None, progress_view=view)
147 view._last_transport_msg = ' 123kB 100kB/s '
148 line = view._render_line()
149 self.assertEqual(
150' 123kB 100kB/s / ',
151 line)
152 self.assertEqual(len(line), 79)
153
154 task_a.update('start_' + 'a' * 200 + '_end', 2000, 5000)
155 view._last_transport_msg = ' 123kB 100kB/s '
156 line = view._render_line()
157 self.assertEqual(
158' 123kB 100kB/s \\ start_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.. 2000/5000',
159 line)
160 self.assertEqual(len(line), 79)
128161
=== modified file 'bzrlib/tests/test_ui.py'
--- bzrlib/tests/test_ui.py 2010-06-21 22:29:38 +0000
+++ bzrlib/tests/test_ui.py 2010-07-17 16:48:48 +0000
@@ -100,51 +100,6 @@
100 finally:100 finally:
101 pb.finished()101 pb.finished()
102102
103 def test_progress_note(self):
104 stderr = tests.StringIOWrapper()
105 stdout = tests.StringIOWrapper()
106 ui_factory = _mod_ui_text.TextUIFactory(stdin=tests.StringIOWrapper(''),
107 stderr=stderr,
108 stdout=stdout)
109 pb = ui_factory.nested_progress_bar()
110 try:
111 result = self.applyDeprecated(deprecated_in((2, 1, 0)),
112 pb.note,
113 't')
114 self.assertEqual(None, result)
115 self.assertEqual("t\n", stdout.getvalue())
116 # Since there was no update() call, there should be no clear() call
117 self.failIf(re.search(r'^\r {10,}\r$',
118 stderr.getvalue()) is not None,
119 'We cleared the stderr without anything to put there')
120 finally:
121 pb.finished()
122
123 def test_progress_note_clears(self):
124 stderr = test_progress._TTYStringIO()
125 stdout = test_progress._TTYStringIO()
126 # so that we get a TextProgressBar
127 os.environ['TERM'] = 'xterm'
128 ui_factory = _mod_ui_text.TextUIFactory(
129 stdin=tests.StringIOWrapper(''),
130 stdout=stdout, stderr=stderr)
131 self.assertIsInstance(ui_factory._progress_view,
132 _mod_ui_text.TextProgressView)
133 pb = ui_factory.nested_progress_bar()
134 try:
135 # Create a progress update that isn't throttled
136 pb.update('x', 1, 1)
137 result = self.applyDeprecated(deprecated_in((2, 1, 0)),
138 pb.note, 't')
139 self.assertEqual(None, result)
140 self.assertEqual("t\n", stdout.getvalue())
141 # the exact contents will depend on the terminal width and we don't
142 # care about that right now - but you're probably running it on at
143 # least a 10-character wide terminal :)
144 self.assertContainsRe(stderr.getvalue(), r'\r {10,}\r$')
145 finally:
146 pb.finished()
147
148 def test_text_ui_get_boolean(self):103 def test_text_ui_get_boolean(self):
149 stdin = tests.StringIOWrapper("y\n" # True104 stdin = tests.StringIOWrapper("y\n" # True
150 "n\n" # False105 "n\n" # False
@@ -193,6 +148,7 @@
193 factory = _mod_ui_text.TextUIFactory(148 factory = _mod_ui_text.TextUIFactory(
194 stdin=tests.StringIOWrapper("yada\ny\n"),149 stdin=tests.StringIOWrapper("yada\ny\n"),
195 stdout=out, stderr=out)150 stdout=out, stderr=out)
151 factory._avail_width = lambda: 79
196 pb = factory.nested_progress_bar()152 pb = factory.nested_progress_bar()
197 pb.show_bar = False153 pb.show_bar = False
198 pb.show_spinner = False154 pb.show_spinner = False
@@ -204,9 +160,9 @@
204 factory.get_boolean,160 factory.get_boolean,
205 "what do you want"))161 "what do you want"))
206 output = out.getvalue()162 output = out.getvalue()
207 self.assertContainsRe(factory.stdout.getvalue(),163 self.assertContainsRe(output,
208 "foo *\r\r *\r*")164 "| foo *\r\r *\r*")
209 self.assertContainsRe(factory.stdout.getvalue(),165 self.assertContainsRe(output,
210 r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")166 r"what do you want\? \[y/n\]: what do you want\? \[y/n\]: ")
211 # stdin should have been totally consumed167 # stdin should have been totally consumed
212 self.assertEqual('', factory.stdin.readline())168 self.assertEqual('', factory.stdin.readline())
213169
=== modified file 'bzrlib/ui/text.py'
--- bzrlib/ui/text.py 2010-06-21 01:30:45 +0000
+++ bzrlib/ui/text.py 2010-07-17 16:48:48 +0000
@@ -300,13 +300,15 @@
300 # correspond reliably to overall command progress300 # correspond reliably to overall command progress
301 self.enable_bar = False301 self.enable_bar = False
302302
303 def _avail_width(self):
304 # we need one extra space for terminals that wrap on last char
305 w = osutils.terminal_width()
306 if w is None:
307 return w
308 else:
309 return w - 1
310
303 def _show_line(self, s):311 def _show_line(self, s):
304 # sys.stderr.write("progress %r\n" % s)
305 width = osutils.terminal_width()
306 if width is not None:
307 # we need one extra space for terminals that wrap on last char
308 width = width - 1
309 s = '%-*.*s' % (width, width, s)
310 self._term_file.write('\r' + s + '\r')312 self._term_file.write('\r' + s + '\r')
311313
312 def clear(self):314 def clear(self):
@@ -348,6 +350,10 @@
348 return ''350 return ''
349351
350 def _format_task(self, task):352 def _format_task(self, task):
353 """Format task-specific parts of progress bar.
354
355 :returns: (text_part, counter_part) both unicode strings.
356 """
351 if not task.show_count:357 if not task.show_count:
352 s = ''358 s = ''
353 elif task.current_cnt is not None and task.total_cnt is not None:359 elif task.current_cnt is not None and task.total_cnt is not None:
@@ -363,21 +369,39 @@
363 t = t._parent_task369 t = t._parent_task
364 if t.msg:370 if t.msg:
365 m = t.msg + ':' + m371 m = t.msg + ':' + m
366 return m + s372 return m, s
367373
368 def _render_line(self):374 def _render_line(self):
369 bar_string = self._render_bar()375 bar_string = self._render_bar()
370 if self._last_task:376 if self._last_task:
371 task_msg = self._format_task(self._last_task)377 task_part, counter_part = self._format_task(self._last_task)
372 else:378 else:
373 task_msg = ''379 task_part = counter_part = ''
374 if self._last_task and not self._last_task.show_transport_activity:380 if self._last_task and not self._last_task.show_transport_activity:
375 trans = ''381 trans = ''
376 else:382 else:
377 trans = self._last_transport_msg383 trans = self._last_transport_msg
378 if trans:384 # the bar separates the transport activity from the message, so even
379 trans += ' | '385 # if there's no bar or spinner, we must show something if both those
380 return (bar_string + trans + task_msg)386 # fields are present
387 if (task_part or trans) and not bar_string:
388 bar_string = '| '
389 # preferentially truncate the task message if we don't have enough
390 # space
391 avail_width = self._avail_width()
392 if avail_width is not None:
393 # if terminal avail_width is unknown, don't truncate
394 current_len = len(bar_string) + len(trans) + len(task_part) + len(counter_part)
395 gap = current_len - avail_width
396 if gap > 0:
397 task_part = task_part[:-gap-2] + '..'
398 s = trans + bar_string + task_part + counter_part
399 if avail_width is not None:
400 if len(s) < avail_width:
401 s = s.ljust(avail_width)
402 elif len(s) > avail_width:
403 s = s[:avail_width]
404 return s
381405
382 def _repaint(self):406 def _repaint(self):
383 s = self._render_line()407 s = self._render_line()
@@ -439,7 +463,7 @@
439 rate = (self._bytes_since_update463 rate = (self._bytes_since_update
440 / (now - self._transport_update_time))464 / (now - self._transport_update_time))
441 # using base-10 units (see HACKING.txt).465 # using base-10 units (see HACKING.txt).
442 msg = ("%6dkB %5dkB/s" %466 msg = ("%6dkB %5dkB/s " %
443 (self._total_byte_count / 1000, int(rate) / 1000,))467 (self._total_byte_count / 1000, int(rate) / 1000,))
444 self._transport_update_time = now468 self._transport_update_time = now
445 self._last_repaint = now469 self._last_repaint = now