Merge lp:~mbp/bzr/387717-progress-bar-tty into lp:~bzr/bzr/trunk-old

Proposed by Martin Pool
Status: Superseded
Proposed branch: lp:~mbp/bzr/387717-progress-bar-tty
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 477 lines
To merge this branch: bzr merge lp:~mbp/bzr/387717-progress-bar-tty
Reviewer Review Type Date Requested Status
Vincent Ladeuil Needs Fixing
John A Meinel Needs Fixing
Andrew Bennetts Approve
Review via email: mp+7669@code.launchpad.net

This proposal has been superseded by a proposal from 2009-06-22.

To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

This stops progress bars being sent to stderr when it's not a file.

(That's not quite the point of bug 387717, but it's still useful.)

There are some more changes in here: ProgressTasks now talk directly to their ProgressView rather than to the UIFactory which makes them more easily testable in isolation, and may let us remove some forwarding methods in the UIFactory. (The factory still constructs the task so it can arrange to have notifications sent where ever it wants.)

Some classes whose constructor is deprecated now have comments pointing this out.

LINES, TERM and COLUMNS are set to specific values when running tests to reduce variation.

Revision history for this message
Andrew Bennetts (spiv) wrote :

> + # TODO: deprecate passing ui_factory

It would be good to actually do this TODO :)

It shouldn't be hard to do.

> 145 + 'TERM': 'dumb',
> 146 + 'LINES': '25',
> 147 + 'COLUMNS': '80',

(Idle thought... I wonder if the cost of constructing that dictionary every test is a measureable cost on the total test suite run, especially as we keep adding things to it?)

Part of me wonders if it would be better to just set these variables on TestTextProgressView rather than in the base case...

> + task = self.make_task(None, view, 'reticulating splines', 5, 20)

:)

> 248 + self.assertEqual(
> 249 + r'[####| ] a:b:c 1/2'
> 250 + , view._render_line())

I think adding a assertRendering method might reduce boilerplate a little, but it's not a big issue given there's only a few test methods. (And it's only moved code anyway, rather than new code.)

review: Approve
Revision history for this message
Matthew Fuller (fullermd) wrote :

> * Progress bars are now suppressed again when the environment variable
> + ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to
> + a terminal.
> + (Martin Pool, #339385, #387717)

They're suppressed when stderr *IS* directed to a terminal? 8-}

--
Matthew Fuller (MF4839) | <email address hidden>
Systems/Network Administrator | http://www.over-yonder.net/~fullermd/
           On the Internet, nobody can hear you scream.

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

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

Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
> This stops progress bars being sent to stderr when it's not a file.
>
> (That's not quite the point of bug 387717, but it's still useful.)
>
> There are some more changes in here: ProgressTasks now talk directly to their ProgressView rather than to the UIFactory which makes them more easily testable in isolation, and may let us remove some forwarding methods in the UIFactory. (The factory still constructs the task so it can arrange to have notifications sent where ever it wants.)
>
> Some classes whose constructor is deprecated now have comments pointing this out.
>
> LINES, TERM and COLUMNS are set to specific values when running tests to reduce variation.
>

...

@@ -82,7 +91,9 @@
         self.total_cnt = None
         self.current_cnt = None
         self.msg = ''
+ # TODO: deprecate passing ui_factory
         self.ui_factory = ui_factory
+ self.progress_view = progress_view
         self.show_pct = False
         self.show_spinner = True
         self.show_eta = False,

^- Why not just deprecate it?

if ui_factory is not None:
  symbol_versioning.warn(
    'Passing ui_factory' % symbol_versioning.deprecated_in((1, 17, 0)),
    ' Pass progress_view instead')

or are there still bits that use the ui_factory without going to
progress_view?

...

+# NOTE: This is also deprecated; you should provide a ProgressView instead.
 class _BaseProgressBar(object):

Same here, just wrap __init__:

     @deprecated_method(deprecated_in((1, 17, 0)))
     def __init__(self,

Alternatively if you still need to do it from time to time, we've used a
flag like "_internal=False" and then:

if not _internal:
  warn...

So other than

1) Deprecating things with comments rather than deprecated_in()
2)
* Progress bars are now suppressed again when the environment variable
- - ``BZR_PROGRESS_BAR`` is set to ``none``.
- - (Martin Pool, #339385)
+ ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to
+ a terminal.
+ (Martin Pool, #339385, #387717)

^- And this having the wrong meaning.

I approve, but it needs fixing. I would vote "tweak" but I don't know
how to do that.

Review Approve
  review: needs_fixing

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

iEYEARECAAYFAko7mm4ACgkQJdeBCYSNAAPRKwCeJtFuuy6f7RbFEpw7fsNUubkQ
BMgAoICd00+tFOLRsG69P3PFNSGbYVBJ
=Kpqi
-----END PGP SIGNATURE-----

review: Needs Fixing
Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (3.6 KiB)

>>>>> "martin" == Martin Pool <email address hidden> writes:

    martin> Martin Pool has proposed merging
    martin> lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.

    martin> Requested reviews:
    martin> bzr-core (bzr-core)

    martin> This stops progress bars being sent to stderr when
    martin> it's not a file.

    martin> (That's not quite the point of bug 387717, but it's still
    martin> useful.)

    martin> There are some more changes in here: ProgressTasks now talk
    martin> directly to their ProgressView rather than to the UIFactory which
    martin> makes them more easily testable in isolation,

    martin> and may let us remove some forwarding methods in the
    martin> UIFactory.

Eerk, that's where bzr-gtk is plugged in: it uses
_progress_all_finished and _progress_updated (I know, I know,
private methods, bad vila, no sugar :).

    martin> (The factory still constructs the task so it can
    martin> arrange to have notifications sent where ever it
    martin> wants.)

At least make UIFactory use a progress_view class attribute or
define _make_progress_view in UIFactory, not in TextUIFactory and
make it public.

Additional bonus points if you can extract the reusable parts
From TextProgressView ;-D

    martin> Some classes whose constructor is deprecated now have
    martin> comments pointing this out.

Hardly discoverable I'm afraid...

I know the deprecation dance is painful, but bzr-gtk is currently
claiming compatibility with bzr 1.6 -> 1.13 and 1.15, if you
remove the forwarding methods above, that would become quite a
challenge. So at least these methods will have to be deprecated
properly (I don't really care about the classes you refer to
here).

<snip/>

    martin> === modified file 'NEWS'
    martin> --- NEWS 2009-06-17 05:05:28 +0000
    martin> +++ NEWS 2009-06-19 08:49:14 +0000
    martin> @@ -21,8 +21,9 @@
    martin> (Neil Martinsen-Burrell, #220067)

    martin> * Progress bars are now suppressed again when the environment
    martin> variable
    martin> - ``BZR_PROGRESS_BAR`` is set to ``none``.
    martin> - (Martin Pool, #339385)
    martin> + ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to

Who ate that 'not' ? :)

<snip/>

    martin> @@ -71,7 +70,8 @@
    martin> self._progress_view.clear()

    martin> def _make_progress_view(self):
    martin> - if os.environ.get('BZR_PROGRESS_BAR') in ('text', None, ''):
    martin> + if (os.environ.get('BZR_PROGRESS_BAR') in ('text', None, '')
    martin> + and progress._supports_progress(self.stderr)):
    martin> return TextProgressView(self.stderr)
    martin> else:
    martin> return NullProgressView()

Could we please stop outsmarting the user ? ;-D

Please do:

     def _make_progress_view(self):
- if (os.environ.get('BZR_PROGRESS_BAR') in ('text', None, '')
+ if os.environ.get('BZR_PROGRESS_BAR') == 'text':
+ return TextProgressView(self.stderr)
+ elif (os.environ.get('BZR_PROGRESS_BAR') in (None, '')

I assure you I dig hard, but there is no way to reliably detect
where the output is going when bzr is run under emacs, i...

Read more...

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

2009/6/19 fullermd <email address hidden>:
>>  * Progress bars are now suppressed again when the environment variable
>> +  ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to
>> +  a terminal.
>> +  (Martin Pool, #339385, #387717)
>
> They're suppressed when stderr *IS* directed to a terminal?  8-}

Good catch, it was backwards.

--
Martin <http://launchpad.net/~mbp/>

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

2009/6/20 John A Meinel <email address hidden>:
> @@ -82,7 +91,9 @@
>         self.total_cnt = None
>         self.current_cnt = None
>         self.msg = ''
> +        # TODO: deprecate passing ui_factory
>         self.ui_factory = ui_factory
> +        self.progress_view = progress_view
>         self.show_pct = False
>         self.show_spinner = True
>         self.show_eta = False,
>
>
> ^- Why not just deprecate it?
>
> if ui_factory is not None:
>  symbol_versioning.warn(
>    'Passing ui_factory' % symbol_versioning.deprecated_in((1, 17, 0)),
>    ' Pass progress_view instead')
>
> or are there still bits that use the ui_factory without going to
> progress_view?

I though there might be some nontrivial work to deprecate it and to
look into how that would impact bzr-gtk, so I wanted to land this
first. (And from Vincent's comments it looks like that's true.)

That is the kind of change I'll put in when it's ready.
>
> ...
>
> +# NOTE: This is also deprecated; you should provide a ProgressView instead.
>  class _BaseProgressBar(object):
>
> Same here, just wrap __init__:
>
>     @deprecated_method(deprecated_in((1, 17, 0)))
>     def __init__(self,
>
> Alternatively if you still need to do it from time to time, we've used a
> flag like "_internal=False" and then:
>
> if not _internal:
>  warn...

All the public concrete constructors are deprecated. You're right I
could and maybe should add an _internal flag so that any callers other
than those subclasses will warn.

>
>
> So other than
>
> 1) Deprecating things with comments rather than deprecated_in()

My cover letter wasn't very clear: these things are already actually
deprecated, it's just that the deprecation marks the constructor not
the class and I wanted it to be more obvious.

> I approve, but it needs fixing. I would vote "tweak" but I don't know
> how to do that.

There's a bug asking for a 'tweak' status. -
https://bugs.edge.launchpad.net/launchpad-code/+bug/373078

--
Martin <http://launchpad.net/~mbp/>

Revision history for this message
Martin Pool (mbp) wrote :
Download full text (4.3 KiB)

2009/6/20 Vincent Ladeuil <email address hidden>:
> Review: Needs Fixing
>
>>>>>> "martin" == Martin Pool <email address hidden> writes:
>
>    martin> Martin Pool has proposed merging
>    martin> lp:~mbp/bzr/387717-progress-bar-tty into lp:bzr.
>
>    martin> Requested reviews:
>    martin>     bzr-core (bzr-core)
>
>    martin> This stops progress bars being sent to stderr when
>    martin> it's not a file.
>
>
>    martin> (That's not quite the point of bug 387717, but it's still
>    martin> useful.)
>
>    martin> There are some more changes in here: ProgressTasks now talk
>    martin> directly to their ProgressView rather than to the UIFactory which
>    martin> makes them more easily testable in isolation,
>
>    martin> and may let us remove some forwarding methods in the
>    martin> UIFactory.
>
> Eerk, that's where bzr-gtk is plugged in: it uses
> _progress_all_finished and _progress_updated (I know, I know,
> private methods, bad vila, no sugar :).

This is why I have some doubt about our past approach of saying
private interfaces can be changed without warning - if people need to
access something, they'll do it regardless of the name and then we
break them. Therefore I think the naming should be just as a guide to
use that particular class, and we need to make the assessment more
broadly.

At any rate I have not actually removed those methods. I'll have a
look at bzr-gtk.

>    martin> (The factory still constructs the task so it can
>    martin> arrange to have notifications sent where ever it
>    martin> wants.)
>
> At least make UIFactory use a progress_view class attribute or
> define _make_progress_view in UIFactory, not in TextUIFactory and
> make it public.
>
> Additional bonus points if you can extract the reusable parts
> >From TextProgressView ;-D

Good ideas.

Though I'll need to think about whether the public should be able to
ask the UIFactory for a ProgressView as opposed to asking it for a
progress bar already bound to a view. How the views get hooked up is
possibly part of the UI's policy...

>    martin> Some classes whose constructor is deprecated now have
>    martin> comments pointing this out.
>
> Hardly discoverable I'm afraid...
>
> I know the deprecation dance is painful, but bzr-gtk is currently
> claiming compatibility with bzr 1.6 -> 1.13 and 1.15, if you
> remove the forwarding methods above, that would become quite a
> challenge. So at least these methods will have to be deprecated
> properly (I don't really care about the classes you refer to
> here).

I didn't deprecate anything, I just added comments on classes whose
constructor is deprecated.

>    martin>      def _make_progress_view(self):
>    martin> - if os.environ.get('BZR_PROGRESS_BAR') in ('text', None, ''):
>    martin> + if (os.environ.get('BZR_PROGRESS_BAR') in ('text', None, '')
>    martin> +            and progress._supports_progress(self.stderr)):
>    martin>              return TextProgressView(self.stderr)
>    martin>          else:
>    martin>              return NullProgressView()
>
>
> Could we please stop outsmarting the user ? ;-D
>
> Please do:
>
>     def _make_progress_view(self):
> -        if (os.environ.get('BZR_P...

Read more...

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

In bzr-gtk I see:

        if getattr(ui.ui_factory, "set_progress_bar_widget", None) is not None:
            # We'are using our own ui, let's tell it to use our widget.
            ui.ui_factory.set_progress_bar_widget(self.progress_widget)

so it can redirect the progress display into the relevant window, and
then it uses _progress_updated and _progress_all_finished to update
them.

So I don't think my patch will break this at all.

If we did deprecate the things I mentioned deprecating, then bzr-gtk
would need to (at least with later bzrlibs) return its own
ProgressView that points to the right progress bar. I don't think it
will be traumatic.

I was in passing thinking about letting it use the activity
notifications to update a throbber and/or to run a nested gtk main
loop.

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (5.8 KiB)

>>>>> "martin" == Martin Pool <email address hidden> writes:

<snip/>

    >>    martin> and may let us remove some forwarding methods in the
    >>    martin> UIFactory.
    >>
    >> Eerk, that's where bzr-gtk is plugged in: it uses
    >> _progress_all_finished and _progress_updated (I know, I know,
    >> private methods, bad vila, no sugar :).

    martin> This is why I have some doubt about our past approach
    martin> of saying private interfaces can be changed without
    martin> warning - if people need to access something, they'll
    martin> do it regardless of the name and then we break them.

Right. I think our approach about deprecation is sound.

Regarding plugins that use private methods because they couldn't
find a way to achieve something with the public ones, well, we
can't predict *all* needs. But we may encourage people to
*report* what private methods they use so that we can either make
them public or explain how to avoid using them.

<snip/>

    martin> At any rate I have not actually removed those
    martin> methods.

Sure, but you said you will :)

<snip/>

    >> At least make UIFactory use a progress_view class attribute or
    >> define _make_progress_view in UIFactory, not in TextUIFactory and
    >> make it public.
    >>
    >> Additional bonus points if you can extract the reusable parts
    >> >From TextProgressView ;-D

    martin> Good ideas.

Great.

    martin> Though I'll need to think about whether the public
    martin> should be able to ask the UIFactory for a
    martin> ProgressView as opposed to asking it for a progress
    martin> bar already bound to a view.

As I understand your design, there are no more progress bars,
there are progress tasks, a stack of them is maintained
transparently, and the whole stack uses a single report view (I
call it widget).

    martin> How the views get hooked up is possibly part of the
    martin> UI's policy...

Definitely.

There is still a problem specific to bzr-gtk (at least I couldn't
a figure a way bzrlib itself could address it).

Right now, bzr-gtk use a single UIFactory instance (and that's
the design encouraged by bzrlib). From there, the progress widget
is unique too (since UIFactory forwards to it).

Now, imagine that 'bzr viz' starts 'bzr gblame' and that each of
them use a report widget... at one point the progress will
reported by the wrong widget.

Again, I anticipate, it hasn't been encountered yet.

Just a limitation I wanted you to be aware of.

The way that could be addressed in bzr-gtk is to set the progress
widget before each call to bzrlib that requires it (as long as
the bzrlib calls are issued by objects that know, or have a way
to know, which progress widget they should use, that shouldn't be
a too heavy constraint).

And if no more progress widgets are around, bzr-gtk falls back to
creating a top-level window for that purpose.

The following threads give more details:
https://lists.canonical.com/archives/bzr-gtk/2009-June/001824.html
https://lists.canonical.com/archives/bzr-gtk/2009-June/001846.html

    >>    martin> Some classes whose constructor is deprecated now have
    >>    martin> comments pointing this out.
    >>
    >> Ha...

Read more...

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (5.8 KiB)

>>>>> "martin" == Martin Pool <email address hidden> writes:

<snip/>

    >>    martin> and may let us remove some forwarding methods in the
    >>    martin> UIFactory.
    >>
    >> Eerk, that's where bzr-gtk is plugged in: it uses
    >> _progress_all_finished and _progress_updated (I know, I know,
    >> private methods, bad vila, no sugar :).

    martin> This is why I have some doubt about our past approach
    martin> of saying private interfaces can be changed without
    martin> warning - if people need to access something, they'll
    martin> do it regardless of the name and then we break them.

Right. I think our approach about deprecation is sound.

Regarding plugins that use private methods because they couldn't
find a way to achieve something with the public ones, well, we
can't predict *all* needs. But we may encourage people to
*report* what private methods they use so that we can either make
them public or explain how to avoid using them.

<snip/>

    martin> At any rate I have not actually removed those
    martin> methods.

Sure, but you said you will :)

<snip/>

    >> At least make UIFactory use a progress_view class attribute or
    >> define _make_progress_view in UIFactory, not in TextUIFactory and
    >> make it public.
    >>
    >> Additional bonus points if you can extract the reusable parts
    >> >From TextProgressView ;-D

    martin> Good ideas.

Great.

    martin> Though I'll need to think about whether the public
    martin> should be able to ask the UIFactory for a
    martin> ProgressView as opposed to asking it for a progress
    martin> bar already bound to a view.

As I understand your design, there are no more progress bars,
there are progress tasks, a stack of them is maintained
transparently, and the whole stack uses a single report view (I
call it widget).

    martin> How the views get hooked up is possibly part of the
    martin> UI's policy...

Definitely.

There is still a problem specific to bzr-gtk (at least I couldn't
a figure a way bzrlib itself could address it).

Right now, bzr-gtk use a single UIFactory instance (and that's
the design encouraged by bzrlib). From there, the progress widget
is unique too (since UIFactory forwards to it).

Now, imagine that 'bzr viz' starts 'bzr gblame' and that each of
them use a report widget... at one point the progress will
reported by the wrong widget.

Again, I anticipate, it hasn't been encountered yet.

Just a limitation I wanted you to be aware of.

The way that could be addressed in bzr-gtk is to set the progress
widget before each call to bzrlib that requires it (as long as
the bzrlib calls are issued by objects that know, or have a way
to know, which progress widget they should use, that shouldn't be
a too heavy constraint).

And if no more progress widgets are around, bzr-gtk falls back to
creating a top-level window for that purpose.

The following threads give more details:
https://lists.canonical.com/archives/bzr-gtk/2009-June/001824.html
https://lists.canonical.com/archives/bzr-gtk/2009-June/001846.html

    >>    martin> Some classes whose constructor is deprecated now have
    >>    martin> comments pointing this out.
    >>
    >> Ha...

Read more...

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

>>>>> "martin" == Martin Pool <email address hidden> writes:

    martin> In bzr-gtk I see:
    martin> if getattr(ui.ui_factory, "set_progress_bar_widget", None) is not None:
    martin> # We'are using our own ui, let's tell it to use our widget.
    martin> ui.ui_factory.set_progress_bar_widget(self.progress_widget)

    martin> so it can redirect the progress display into the relevant window, and
    martin> then it uses _progress_updated and _progress_all_finished to update
    martin> them.

That's it.

    martin> So I don't think my patch will break this at all.

Not yet :-)

    martin> If we did deprecate the things I mentioned
    martin> deprecating, then bzr-gtk would need to (at least
    martin> with later bzrlibs) return its own ProgressView that
    martin> points to the right progress bar. I don't think it
    martin> will be traumatic.

Agreed.

My point was that it will not be able (as it is now) to support
multiple bzr versions, not a big deal, but one aim of our
deprecation policy is to warn people about what part of their
toolset they should upgrade...

    martin> I was in passing thinking about letting it use the
    martin> activity notifications to update a throbber and/or to
    martin> run a nested gtk main loop.

The GtkProgressBar.update() does:

        while gtk.events_pending():
            gtk.main_iteration()

  Vincent

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

2009/6/22 Vincent Ladeuil <email address hidden>:
>    martin> If we did deprecate the things I mentioned
>    martin> deprecating, then bzr-gtk would need to (at least
>    martin> with later bzrlibs) return its own ProgressView that
>    martin> points to the right progress bar.  I don't think it
>    martin> will be traumatic.
>
> Agreed.
>
> My point was that it will not be able (as it is now) to support
> multiple bzr versions, not a big deal, but one aim of our
> deprecation policy is to warn people about what part of their
> toolset they should upgrade...

I'll try to deprecate them soon so that we can have a long warning period.

>    martin> I was in passing thinking about letting it use the
>    martin> activity notifications to update a throbber and/or to
>    martin> run a nested gtk main loop.
>
> The GtkProgressBar.update() does:
>
>        while gtk.events_pending():
>            gtk.main_iteration()
>
>  Vincent

If it does that from the transport activity notification callback too
it will probably get much more responsive when network IO is running.

--
Martin <http://launchpad.net/~mbp/>

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

> > + # TODO: deprecate passing ui_factory
>
> It would be good to actually do this TODO :)
>
> It shouldn't be hard to do.

I'll probably do it next or soon, but wanted to keep this review small.

> > 145 + 'TERM': 'dumb',
> > 146 + 'LINES': '25',
> > 147 + 'COLUMNS': '80',
>
> (Idle thought... I wonder if the cost of constructing that dictionary every
> test is a measureable cost on the total test suite run, especially as we keep
> adding things to it?)
>
> Part of me wonders if it would be better to just set these variables on
> TestTextProgressView rather than in the base case...

We have occasionally had errors in other cases, particularly in black-box tests but also in others, due to them inadvertently depending on one of these. In a sense every one of those failures is a chance to think about whether that particular case should be tested across various screen setups, but on the whole I think this is an inefficient way to discover them. Let's instead have a standard environment across all tests and then add variations where we need to.

>
> > + task = self.make_task(None, view, 'reticulating splines', 5, 20)
>
> :)
>
> > 248 + self.assertEqual(
> > 249 + r'[####| ] a:b:c 1/2'
> > 250 + , view._render_line())
>
> I think adding a assertRendering method might reduce boilerplate a little, but
> it's not a big issue given there's only a few test methods. (And it's only
> moved code anyway, rather than new code.)

Right.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-06-20 01:53:18 +0000
3+++ NEWS 2009-06-20 08:35:18 +0000
4@@ -39,8 +39,9 @@
5 (Ian Clatworthy)
6
7 * Progress bars are now suppressed again when the environment variable
8- ``BZR_PROGRESS_BAR`` is set to ``none``.
9- (Martin Pool, #339385)
10+ ``BZR_PROGRESS_BAR`` is set to ``none``, or when stderr is directed to
11+ a terminal.
12+ (Martin Pool, #339385, #387717)
13
14 * Unshelve works correctly when multiple zero-length files are present on
15 the shelf. (Aaron Bentley, #363444)
16@@ -94,6 +95,10 @@
17 API Changes
18 ***********
19
20+* ProgressTasks now prefer to talk direct to their ProgressView not to the
21+ UIFactory.
22+ (Martin Pool)
23+
24 * Removed overspecific error class ``InvalidProgressBarType``.
25 (Martin Pool)
26
27
28=== modified file 'bzrlib/progress.py'
29--- bzrlib/progress.py 2009-06-10 03:56:49 +0000
30+++ bzrlib/progress.py 2009-06-20 08:35:18 +0000
31@@ -42,13 +42,15 @@
32 )
33
34
35-# XXX: deprecated; can be removed when the ProgressBar factory is removed
36 def _supports_progress(f):
37- """Detect if we can use pretty progress bars on the output stream f.
38+ """Detect if we can use pretty progress bars on file F.
39
40 If this returns true we expect that a human may be looking at that
41 output, and that we can repaint a line to update it.
42+
43+ This doesn't check the policy for whether we *should* use them.
44 """
45+ # XXX: This is a bit redundant with make_ui_for_terminal.
46 isatty = getattr(f, 'isatty', None)
47 if isatty is None:
48 return False
49@@ -71,9 +73,16 @@
50 will not necessarily respect all these fields.
51 """
52
53- def __init__(self, parent_task=None, ui_factory=None):
54+ def __init__(self, parent_task=None, ui_factory=None, progress_view=None):
55 """Construct a new progress task.
56
57+ :param parent_task: Enclosing ProgressTask or None.
58+
59+ :param progress_view: ProgressView to display this ProgressTask.
60+
61+ :param ui_factory: The UI factory that will display updates;
62+ deprecated in favor of passing progress_view directly.
63+
64 Normally you should not call this directly but rather through
65 `ui_factory.nested_progress_bar`.
66 """
67@@ -82,7 +91,9 @@
68 self.total_cnt = None
69 self.current_cnt = None
70 self.msg = ''
71+ # TODO: deprecate passing ui_factory
72 self.ui_factory = ui_factory
73+ self.progress_view = progress_view
74 self.show_pct = False
75 self.show_spinner = True
76 self.show_eta = False,
77@@ -101,16 +112,23 @@
78 self.current_cnt = current_cnt
79 if total_cnt:
80 self.total_cnt = total_cnt
81- self.ui_factory._progress_updated(self)
82+ if self.progress_view:
83+ self.progress_view.show_progress(self)
84+ else:
85+ self.ui_factory._progress_updated(self)
86
87 def tick(self):
88 self.update(self.msg)
89
90 def finished(self):
91- self.ui_factory._progress_finished(self)
92+ if self.progress_view:
93+ self.progress_view.task_finished(self)
94+ else:
95+ self.ui_factory._progress_finished(self)
96
97 def make_sub_task(self):
98- return ProgressTask(self, self.ui_factory)
99+ return ProgressTask(self, ui_factory=self.ui_factory,
100+ progress_view=self.progress_view)
101
102 def _overall_completion_fraction(self, child_fraction=0.0):
103 """Return fractional completion of this task and its parents
104@@ -139,7 +157,10 @@
105
106 def clear(self):
107 # XXX: shouldn't be here; put it in mutter or the ui instead
108- self.ui_factory.clear_term()
109+ if self.progress_view:
110+ self.progress_view.clear()
111+ else:
112+ self.ui_factory.clear_term()
113
114
115 @deprecated_function(deprecated_in((1, 16, 0)))
116@@ -166,6 +187,7 @@
117 return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
118
119
120+# NOTE: This is also deprecated; you should provide a ProgressView instead.
121 class _BaseProgressBar(object):
122
123 def __init__(self,
124@@ -453,6 +475,8 @@
125 #self.to_file.flush()
126
127
128+
129+# DEPRECATED
130 class ChildProgress(_BaseProgressBar):
131 """A progress indicator that pushes its data to the parent"""
132
133
134=== modified file 'bzrlib/tests/__init__.py'
135--- bzrlib/tests/__init__.py 2009-06-10 18:58:02 +0000
136+++ bzrlib/tests/__init__.py 2009-06-20 08:35:18 +0000
137@@ -1325,6 +1325,13 @@
138 'BZR_PROGRESS_BAR': None,
139 'BZR_LOG': None,
140 'BZR_PLUGIN_PATH': None,
141+ # Make sure that any text ui tests are consistent regardless of
142+ # the environment the test case is run in; you may want tests that
143+ # test other combinations. 'dumb' is a reasonable guess for tests
144+ # going to a pipe or a StringIO.
145+ 'TERM': 'dumb',
146+ 'LINES': '25',
147+ 'COLUMNS': '80',
148 # SSH Agent
149 'SSH_AUTH_SOCK': None,
150 # Proxies
151
152=== modified file 'bzrlib/tests/test_progress.py'
153--- bzrlib/tests/test_progress.py 2009-06-10 03:56:49 +0000
154+++ bzrlib/tests/test_progress.py 2009-06-20 08:35:19 +0000
155@@ -14,14 +14,21 @@
156 # along with this program; if not, write to the Free Software
157 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
158
159+
160 import os
161 from StringIO import StringIO
162
163 from bzrlib import errors
164-from bzrlib.tests import TestCase
165+from bzrlib.progress import (
166+ ProgressTask,
167+ )
168 from bzrlib.symbol_versioning import (
169 deprecated_in,
170 )
171+from bzrlib.tests import TestCase
172+from bzrlib.ui.text import (
173+ TextProgressView,
174+ )
175
176
177 class _TTYStringIO(StringIO):
178@@ -36,3 +43,72 @@
179
180 def isatty(self):
181 return False
182+
183+
184+class TestTextProgressView(TestCase):
185+ """Tests for text display of progress bars.
186+
187+ These try to exercise the progressview independently of its construction,
188+ which is arranged by the TextUIFactory.
189+ """
190+ # The ProgressTask now connects directly to the ProgressView, so we can
191+ # check them independently of the factory or of the determination of what
192+ # view to use.
193+
194+ def make_view(self):
195+ out = StringIO()
196+ view = TextProgressView(out)
197+ view._width = 80
198+ return out, view
199+
200+ def make_task(self, parent_task, view, msg, curr, total):
201+ # would normally be done by UIFactory; is done here so that we don't
202+ # have to have one.
203+ task = ProgressTask(parent_task, progress_view=view)
204+ task.msg = msg
205+ task.current_cnt = curr
206+ task.total_cnt = total
207+ return task
208+
209+ def test_render_progress_easy(self):
210+ """Just one task and one quarter done"""
211+ out, view = self.make_view()
212+ task = self.make_task(None, view, 'reticulating splines', 5, 20)
213+ view.show_progress(task)
214+ self.assertEqual(
215+'\r[####/ ] reticulating splines 5/20 \r'
216+ , out.getvalue())
217+
218+ def test_render_progress_nested(self):
219+ """Tasks proportionally contribute to overall progress"""
220+ out, view = self.make_view()
221+ task = self.make_task(None, view, 'reticulating splines', 0, 2)
222+ task2 = self.make_task(task, view, 'stage2', 1, 2)
223+ view.show_progress(task2)
224+ # so we're in the first half of the main task, and half way through
225+ # that
226+ self.assertEqual(
227+r'[####- ] reticulating splines:stage2 1/2'
228+ , view._render_line())
229+ # if the nested task is complete, then we're all the way through the
230+ # first half of the overall work
231+ task2.update('stage2', 2, 2)
232+ self.assertEqual(
233+r'[#########\ ] reticulating splines:stage2 2/2'
234+ , view._render_line())
235+
236+ def test_render_progress_sub_nested(self):
237+ """Intermediate tasks don't mess up calculation."""
238+ out, view = self.make_view()
239+ task_a = ProgressTask(None, progress_view=view)
240+ task_a.update('a', 0, 2)
241+ task_b = ProgressTask(task_a, progress_view=view)
242+ task_b.update('b')
243+ task_c = ProgressTask(task_b, progress_view=view)
244+ task_c.update('c', 1, 2)
245+ # the top-level task is in its first half; the middle one has no
246+ # progress indication, just a label; and the bottom one is half done,
247+ # so the overall fraction is 1/4
248+ self.assertEqual(
249+ r'[####| ] a:b:c 1/2'
250+ , view._render_line())
251
252=== modified file 'bzrlib/tests/test_ui.py'
253--- bzrlib/tests/test_ui.py 2009-06-17 05:16:48 +0000
254+++ bzrlib/tests/test_ui.py 2009-06-20 08:35:19 +0000
255@@ -25,6 +25,9 @@
256
257 import bzrlib
258 import bzrlib.errors as errors
259+from bzrlib.progress import (
260+ ProgressTask,
261+ )
262 from bzrlib.symbol_versioning import (
263 deprecated_in,
264 )
265@@ -33,7 +36,10 @@
266 TestUIFactory,
267 StringIOWrapper,
268 )
269-from bzrlib.tests.test_progress import _TTYStringIO
270+from bzrlib.tests.test_progress import (
271+ _NonTTYStringIO,
272+ _TTYStringIO,
273+ )
274 from bzrlib.ui import (
275 CLIUIFactory,
276 SilentUIFactory,
277@@ -107,22 +113,35 @@
278 def test_progress_construction(self):
279 """TextUIFactory constructs the right progress view.
280 """
281+ err = _TTYStringIO()
282+ os.environ['TERM'] = 'xterm'
283 os.environ['BZR_PROGRESS_BAR'] = 'none'
284- self.assertIsInstance(TextUIFactory()._progress_view,
285+ self.assertIsInstance(TextUIFactory(stderr=err)._progress_view,
286 NullProgressView)
287
288 os.environ['BZR_PROGRESS_BAR'] = 'text'
289- self.assertIsInstance(TextUIFactory()._progress_view,
290+ self.assertIsInstance(TextUIFactory(stderr=err)._progress_view,
291 TextProgressView)
292
293 os.environ['BZR_PROGRESS_BAR'] = 'text'
294- self.assertIsInstance(TextUIFactory()._progress_view,
295+ self.assertIsInstance(TextUIFactory(stderr=err)._progress_view,
296 TextProgressView)
297
298 del os.environ['BZR_PROGRESS_BAR']
299- self.assertIsInstance(TextUIFactory()._progress_view,
300+ self.assertIsInstance(TextUIFactory(stderr=err)._progress_view,
301 TextProgressView)
302
303+ # if not a tty, no progress bars
304+ self.assertIsInstance(
305+ TextUIFactory(stderr=_NonTTYStringIO())._progress_view,
306+ NullProgressView)
307+
308+ # if a tty but dumb, no progress bars
309+ os.environ['TERM'] = 'dumb'
310+ self.assertIsInstance(
311+ TextUIFactory(stderr=_TTYStringIO())._progress_view,
312+ NullProgressView)
313+
314 def test_progress_note(self):
315 stderr = StringIO()
316 stdout = StringIO()
317@@ -142,14 +161,15 @@
318 pb.finished()
319
320 def test_progress_note_clears(self):
321- stderr = StringIO()
322- stdout = StringIO()
323- # The PQM redirects the output to a file, so it
324- # defaults to creating a Dots progress bar. we
325- # need to force it to believe we are a TTY
326+ stderr = _TTYStringIO()
327+ stdout = _TTYStringIO()
328+ # so that we get a TextProgressBar
329+ os.environ['TERM'] = 'xterm'
330 ui_factory = TextUIFactory(
331 stdin=StringIO(''),
332 stdout=stdout, stderr=stderr)
333+ self.assertIsInstance(ui_factory._progress_view,
334+ TextProgressView)
335 pb = ui_factory.nested_progress_bar()
336 try:
337 # Create a progress update that isn't throttled
338@@ -222,6 +242,7 @@
339 def test_text_factory_prompts_and_clears(self):
340 # a get_boolean call should clear the pb before prompting
341 out = _TTYStringIO()
342+ os.environ['TERM'] = 'xterm'
343 factory = TextUIFactory(stdin=StringIO("yada\ny\n"), stdout=out, stderr=out)
344 pb = factory.nested_progress_bar()
345 pb.show_bar = False
346@@ -295,61 +316,3 @@
347 pb.finished()
348
349
350-class TestTextProgressView(TestCase):
351- """Tests for text display of progress bars.
352- """
353- # XXX: These might be a bit easier to write if the rendering and
354- # state-maintaining parts of TextProgressView were more separate, and if
355- # the progress task called back directly to its own view not to the ui
356- # factory. -- mbp 20090312
357-
358- def _make_factory(self):
359- out = StringIO()
360- uif = TextUIFactory(stderr=out)
361- uif._progress_view._width = 80
362- return out, uif
363-
364- def test_render_progress_easy(self):
365- """Just one task and one quarter done"""
366- out, uif = self._make_factory()
367- task = uif.nested_progress_bar()
368- task.update('reticulating splines', 5, 20)
369- self.assertEqual(
370-'\r[####/ ] reticulating splines 5/20 \r'
371- , out.getvalue())
372-
373- def test_render_progress_nested(self):
374- """Tasks proportionally contribute to overall progress"""
375- out, uif = self._make_factory()
376- task = uif.nested_progress_bar()
377- task.update('reticulating splines', 0, 2)
378- task2 = uif.nested_progress_bar()
379- task2.update('stage2', 1, 2)
380- # so we're in the first half of the main task, and half way through
381- # that
382- self.assertEqual(
383-r'[####\ ] reticulating splines:stage2 1/2'
384- , uif._progress_view._render_line())
385- # if the nested task is complete, then we're all the way through the
386- # first half of the overall work
387- task2.update('stage2', 2, 2)
388- self.assertEqual(
389-r'[#########| ] reticulating splines:stage2 2/2'
390- , uif._progress_view._render_line())
391-
392- def test_render_progress_sub_nested(self):
393- """Intermediate tasks don't mess up calculation."""
394- out, uif = self._make_factory()
395- task_a = uif.nested_progress_bar()
396- task_a.update('a', 0, 2)
397- task_b = uif.nested_progress_bar()
398- task_b.update('b')
399- task_c = uif.nested_progress_bar()
400- task_c.update('c', 1, 2)
401- # the top-level task is in its first half; the middle one has no
402- # progress indication, just a label; and the bottom one is half done,
403- # so the overall fraction is 1/4
404- self.assertEqual(
405- r'[####| ] a:b:c 1/2'
406- , uif._progress_view._render_line())
407-
408
409=== modified file 'bzrlib/ui/__init__.py'
410--- bzrlib/ui/__init__.py 2009-06-10 03:56:49 +0000
411+++ bzrlib/ui/__init__.py 2009-06-20 08:35:19 +0000
412@@ -19,11 +19,21 @@
413 This tells the library how to display things to the user. Through this
414 layer different applications can choose the style of UI.
415
416-At the moment this layer is almost trivial: the application can just
417-choose the style of progress bar.
418-
419-Set the ui_factory member to define the behaviour. The default
420-displays no output.
421+Several levels are supported, and you can also register new factories such as
422+for a GUI.
423+
424+UIFactory
425+ Abstract base class
426+
427+CLIUIFactory
428+ Basic text output, and can read input from the user.
429+
430+TextUIFactory
431+ Can also repaint the screen to draw progress bars.
432+
433+SilentUIFactory
434+ Produces no output and cannot take any input; useful for programs using
435+ bzrlib in batch mode or for programs such as loggerhead.
436 """
437
438 import os
439@@ -279,11 +289,15 @@
440 cls = CLIUIFactory
441 elif not isatty():
442 cls = CLIUIFactory
443+ # The following case also handles Win32 - on that platform $TERM is
444+ # typically never set, so the case None is treated as a smart terminal,
445+ # not dumb. <https://bugs.launchpad.net/bugs/334808> win32 files do have
446+ # isatty methods that return true.
447 elif os.environ.get('TERM') in ('dumb', ''):
448 # e.g. emacs compile window
449 cls = CLIUIFactory
450 # User may know better, otherwise default to TextUIFactory
451- if ( os.environ.get('BZR_USE_TEXT_UI', None) is not None
452+ if (os.environ.get('BZR_USE_TEXT_UI', None) is not None
453 or cls is None):
454 from bzrlib.ui.text import TextUIFactory
455 cls = TextUIFactory
456
457=== modified file 'bzrlib/ui/text.py'
458--- bzrlib/ui/text.py 2009-06-17 05:03:18 +0000
459+++ bzrlib/ui/text.py 2009-06-20 08:35:19 +0000
460@@ -15,7 +15,6 @@
461 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
462
463
464-
465 """Text UI, write output to the console.
466 """
467
468@@ -71,7 +70,8 @@
469 self._progress_view.clear()
470
471 def _make_progress_view(self):
472- if os.environ.get('BZR_PROGRESS_BAR') in ('text', None, ''):
473+ if (os.environ.get('BZR_PROGRESS_BAR') in ('text', None, '')
474+ and progress._supports_progress(self.stderr)):
475 return TextProgressView(self.stderr)
476 else:
477 return NullProgressView()