~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/progress.py

Fix BzrDir.create_workingtree for NULL_REVISION

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Aaron Bentley <aaron.bentley@utoronto.ca>
2
 
# Copyright (C) 2005 Canonical <canonical.com>
 
2
# Copyright (C) 2005, 2006 Canonical <canonical.com>
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
5
5
#    it under the terms of the GNU General Public License as published by
39
39
import sys
40
40
import time
41
41
import os
42
 
from collections import deque
 
42
 
 
43
import bzrlib.errors as errors
 
44
from bzrlib.trace import mutter
43
45
 
44
46
 
45
47
def _supports_progress(f):
46
 
    if not hasattr(f, 'isatty'):
 
48
    isatty = getattr(f, 'isatty', None)
 
49
    if isatty is None:
47
50
        return False
48
 
    if not f.isatty():
 
51
    if not isatty():
49
52
        return False
50
53
    if os.environ.get('TERM') == 'dumb':
51
54
        # e.g. emacs compile window
53
56
    return True
54
57
 
55
58
 
56
 
 
57
 
def ProgressBar(to_file=sys.stderr, **kwargs):
 
59
_progress_bar_types = {}
 
60
 
 
61
 
 
62
def ProgressBar(to_file=None, **kwargs):
58
63
    """Abstract factory"""
59
 
    if _supports_progress(to_file):
60
 
        return TTYProgressBar(to_file=to_file, **kwargs)
 
64
    if to_file is None:
 
65
        to_file = sys.stderr
 
66
    requested_bar_type = os.environ.get('BZR_PROGRESS_BAR')
 
67
    # An value of '' or not set reverts to standard processing
 
68
    if requested_bar_type in (None, ''):
 
69
        if _supports_progress(to_file):
 
70
            return TTYProgressBar(to_file=to_file, **kwargs)
 
71
        else:
 
72
            return DotsProgressBar(to_file=to_file, **kwargs)
61
73
    else:
62
 
        return DotsProgressBar(to_file=to_file, **kwargs)
63
 
    
64
 
    
 
74
        # Minor sanitation to prevent spurious errors
 
75
        requested_bar_type = requested_bar_type.lower().strip()
 
76
        # TODO: jam 20060710 Arguably we shouldn't raise an exception
 
77
        #       but should instead just disable progress bars if we
 
78
        #       don't recognize the type
 
79
        if requested_bar_type not in _progress_bar_types:
 
80
            raise errors.InvalidProgressBarType(requested_bar_type,
 
81
                                                _progress_bar_types.keys())
 
82
        return _progress_bar_types[requested_bar_type](to_file=to_file, **kwargs)
 
83
 
 
84
 
 
85
class ProgressBarStack(object):
 
86
    """A stack of progress bars."""
 
87
 
 
88
    def __init__(self,
 
89
                 to_file=None,
 
90
                 show_pct=False,
 
91
                 show_spinner=True,
 
92
                 show_eta=False,
 
93
                 show_bar=True,
 
94
                 show_count=True,
 
95
                 to_messages_file=None,
 
96
                 klass=None):
 
97
        """Setup the stack with the parameters the progress bars should have."""
 
98
        if to_file is None:
 
99
            to_file = sys.stderr
 
100
        if to_messages_file is None:
 
101
            to_messages_file = sys.stdout
 
102
        self._to_file = to_file
 
103
        self._show_pct = show_pct
 
104
        self._show_spinner = show_spinner
 
105
        self._show_eta = show_eta
 
106
        self._show_bar = show_bar
 
107
        self._show_count = show_count
 
108
        self._to_messages_file = to_messages_file
 
109
        self._stack = []
 
110
        self._klass = klass or ProgressBar
 
111
 
 
112
    def top(self):
 
113
        if len(self._stack) != 0:
 
114
            return self._stack[-1]
 
115
        else:
 
116
            return None
 
117
 
 
118
    def bottom(self):
 
119
        if len(self._stack) != 0:
 
120
            return self._stack[0]
 
121
        else:
 
122
            return None
 
123
 
 
124
    def get_nested(self):
 
125
        """Return a nested progress bar."""
 
126
        if len(self._stack) == 0:
 
127
            func = self._klass
 
128
        else:
 
129
            func = self.top().child_progress
 
130
        new_bar = func(to_file=self._to_file,
 
131
                       show_pct=self._show_pct,
 
132
                       show_spinner=self._show_spinner,
 
133
                       show_eta=self._show_eta,
 
134
                       show_bar=self._show_bar,
 
135
                       show_count=self._show_count,
 
136
                       to_messages_file=self._to_messages_file,
 
137
                       _stack=self)
 
138
        self._stack.append(new_bar)
 
139
        return new_bar
 
140
 
 
141
    def return_pb(self, bar):
 
142
        """Return bar after its been used."""
 
143
        if bar is not self._stack[-1]:
 
144
            raise errors.MissingProgressBarFinish()
 
145
        self._stack.pop()
 
146
 
 
147
 
65
148
class _BaseProgressBar(object):
 
149
 
66
150
    def __init__(self,
67
 
                 to_file=sys.stderr,
 
151
                 to_file=None,
68
152
                 show_pct=False,
69
153
                 show_spinner=False,
70
 
                 show_eta=True,
 
154
                 show_eta=False,
71
155
                 show_bar=True,
72
 
                 show_count=True):
 
156
                 show_count=True,
 
157
                 to_messages_file=None,
 
158
                 _stack=None):
73
159
        object.__init__(self)
 
160
        if to_file is None:
 
161
            to_file = sys.stderr
 
162
        if to_messages_file is None:
 
163
            to_messages_file = sys.stdout
74
164
        self.to_file = to_file
75
 
 
 
165
        self.to_messages_file = to_messages_file
76
166
        self.last_msg = None
77
167
        self.last_cnt = None
78
168
        self.last_total = None
81
171
        self.show_eta = show_eta
82
172
        self.show_bar = show_bar
83
173
        self.show_count = show_count
84
 
 
 
174
        self._stack = _stack
 
175
        # seed throttler
 
176
        self.MIN_PAUSE = 0.1 # seconds
 
177
        now = time.clock()
 
178
        # starting now
 
179
        self.start_time = now
 
180
        # next update should not throttle
 
181
        self.last_update = now - self.MIN_PAUSE - 1
 
182
 
 
183
    def finished(self):
 
184
        """Return this bar to its progress stack."""
 
185
        self.clear()
 
186
        assert self._stack is not None
 
187
        self._stack.return_pb(self)
 
188
 
 
189
    def note(self, fmt_string, *args, **kwargs):
 
190
        """Record a note without disrupting the progress bar."""
 
191
        self.clear()
 
192
        self.to_messages_file.write(fmt_string % args)
 
193
        self.to_messages_file.write('\n')
 
194
 
 
195
    def child_progress(self, **kwargs):
 
196
        return ChildProgress(**kwargs)
85
197
 
86
198
 
87
199
class DummyProgress(_BaseProgressBar):
95
207
    def update(self, msg=None, current=None, total=None):
96
208
        pass
97
209
 
 
210
    def child_update(self, message, current, total):
 
211
        pass
 
212
 
98
213
    def clear(self):
99
214
        pass
100
215
        
101
 
    
 
216
    def note(self, fmt_string, *args, **kwargs):
 
217
        """See _BaseProgressBar.note()."""
 
218
 
 
219
    def child_progress(self, **kwargs):
 
220
        return DummyProgress(**kwargs)
 
221
 
 
222
 
 
223
_progress_bar_types['dummy'] = DummyProgress
 
224
_progress_bar_types['none'] = DummyProgress
 
225
 
 
226
 
102
227
class DotsProgressBar(_BaseProgressBar):
 
228
 
103
229
    def __init__(self, **kwargs):
104
230
        _BaseProgressBar.__init__(self, **kwargs)
105
231
        self.last_msg = None
112
238
        if msg and msg != self.last_msg:
113
239
            if self.need_nl:
114
240
                self.to_file.write('\n')
115
 
            
116
241
            self.to_file.write(msg + ': ')
117
242
            self.last_msg = msg
118
243
        self.need_nl = True
121
246
    def clear(self):
122
247
        if self.need_nl:
123
248
            self.to_file.write('\n')
 
249
        self.need_nl = False
124
250
        
 
251
    def child_update(self, message, current, total):
 
252
        self.tick()
 
253
 
 
254
 
 
255
_progress_bar_types['dots'] = DotsProgressBar
 
256
 
125
257
    
126
258
class TTYProgressBar(_BaseProgressBar):
127
259
    """Progress bar display object.
144
276
    The output file should be in line-buffered or unbuffered mode.
145
277
    """
146
278
    SPIN_CHARS = r'/-\|'
147
 
    MIN_PAUSE = 0.1 # seconds
148
279
 
149
280
 
150
281
    def __init__(self, **kwargs):
152
283
        _BaseProgressBar.__init__(self, **kwargs)
153
284
        self.spin_pos = 0
154
285
        self.width = terminal_width()
155
 
        self.start_time = None
156
 
        self.last_update = None
157
 
        self.last_updates = deque()
 
286
        self.last_updates = []
 
287
        self._max_last_updates = 10
 
288
        self.child_fraction = 0
 
289
        self._have_output = False
158
290
    
159
291
 
160
 
    def throttle(self):
 
292
    def throttle(self, old_msg):
161
293
        """Return True if the bar was updated too recently"""
162
 
        now = time.time()
163
 
        if self.start_time is None:
164
 
            self.start_time = self.last_update = now
 
294
        # time.time consistently takes 40/4000 ms = 0.01 ms.
 
295
        # but every single update to the pb invokes it.
 
296
        # so we use time.clock which takes 20/4000 ms = 0.005ms
 
297
        # on the downside, time.clock() appears to have approximately
 
298
        # 10ms granularity, so we treat a zero-time change as 'throttled.'
 
299
        now = time.clock()
 
300
        if self.start_time is not None and (now - self.start_time) < 1:
 
301
            return True
 
302
        if old_msg != self.last_msg:
165
303
            return False
166
 
        else:
167
 
            interval = now - self.last_update
168
 
            if interval > 0 and interval < self.MIN_PAUSE:
169
 
                return True
 
304
        interval = now - self.last_update
 
305
        # if interval > 0
 
306
        if interval < self.MIN_PAUSE:
 
307
            return True
170
308
 
171
309
        self.last_updates.append(now - self.last_update)
 
310
        # Don't let the queue grow without bound
 
311
        self.last_updates = self.last_updates[-self._max_last_updates:]
172
312
        self.last_update = now
173
313
        return False
174
314
        
175
 
 
176
315
    def tick(self):
177
 
        self.update(self.last_msg, self.last_cnt, self.last_total)
178
 
                 
179
 
 
180
 
 
181
 
    def update(self, msg, current_cnt=None, total_cnt=None):
 
316
        self.update(self.last_msg, self.last_cnt, self.last_total, 
 
317
                    self.child_fraction)
 
318
 
 
319
    def child_update(self, message, current, total):
 
320
        if current is not None and total != 0:
 
321
            child_fraction = float(current) / total
 
322
            if self.last_cnt is None:
 
323
                pass
 
324
            elif self.last_cnt + child_fraction <= self.last_total:
 
325
                self.child_fraction = child_fraction
 
326
            else:
 
327
                mutter('not updating child fraction')
 
328
        if self.last_msg is None:
 
329
            self.last_msg = ''
 
330
        self.tick()
 
331
 
 
332
    def update(self, msg, current_cnt=None, total_cnt=None, 
 
333
               child_fraction=0):
182
334
        """Update and redraw progress bar."""
 
335
        if msg is None:
 
336
            msg = self.last_msg
 
337
 
 
338
        if total_cnt is None:
 
339
            total_cnt = self.last_total
183
340
 
184
341
        if current_cnt < 0:
185
342
            current_cnt = 0
186
343
            
187
344
        if current_cnt > total_cnt:
188
345
            total_cnt = current_cnt
 
346
        
 
347
        ## # optional corner case optimisation 
 
348
        ## # currently does not seem to fire so costs more than saved.
 
349
        ## # trivial optimal case:
 
350
        ## # NB if callers are doing a clear and restore with
 
351
        ## # the saved values, this will prevent that:
 
352
        ## # in that case add a restore method that calls
 
353
        ## # _do_update or some such
 
354
        ## if (self.last_msg == msg and
 
355
        ##     self.last_cnt == current_cnt and
 
356
        ##     self.last_total == total_cnt and
 
357
        ##     self.child_fraction == child_fraction):
 
358
        ##     return
189
359
 
 
360
        old_msg = self.last_msg
190
361
        # save these for the tick() function
191
362
        self.last_msg = msg
192
363
        self.last_cnt = current_cnt
193
364
        self.last_total = total_cnt
194
 
            
195
 
        if self.throttle():
196
 
            return 
197
 
        
198
 
        if self.show_eta and self.start_time and total_cnt:
199
 
            eta = get_eta(self.start_time, current_cnt, total_cnt,
200
 
                    last_updates = self.last_updates)
 
365
        self.child_fraction = child_fraction
 
366
 
 
367
        # each function call takes 20ms/4000 = 0.005 ms, 
 
368
        # but multiple that by 4000 calls -> starts to cost.
 
369
        # so anything to make this function call faster
 
370
        # will improve base 'diff' time by up to 0.1 seconds.
 
371
        if self.throttle(old_msg):
 
372
            return
 
373
 
 
374
        if self.show_eta and self.start_time and self.last_total:
 
375
            eta = get_eta(self.start_time, self.last_cnt + self.child_fraction, 
 
376
                    self.last_total, last_updates = self.last_updates)
201
377
            eta_str = " " + str_tdelta(eta)
202
378
        else:
203
379
            eta_str = ""
210
386
        # always update this; it's also used for the bar
211
387
        self.spin_pos += 1
212
388
 
213
 
        if self.show_pct and total_cnt and current_cnt:
214
 
            pct = 100.0 * current_cnt / total_cnt
 
389
        if self.show_pct and self.last_total and self.last_cnt:
 
390
            pct = 100.0 * ((self.last_cnt + self.child_fraction) / self.last_total)
215
391
            pct_str = ' (%5.1f%%)' % pct
216
392
        else:
217
393
            pct_str = ''
218
394
 
219
395
        if not self.show_count:
220
396
            count_str = ''
221
 
        elif current_cnt is None:
 
397
        elif self.last_cnt is None:
222
398
            count_str = ''
223
 
        elif total_cnt is None:
224
 
            count_str = ' %i' % (current_cnt)
 
399
        elif self.last_total is None:
 
400
            count_str = ' %i' % (self.last_cnt)
225
401
        else:
226
402
            # make both fields the same size
227
 
            t = '%i' % (total_cnt)
228
 
            c = '%*i' % (len(t), current_cnt)
 
403
            t = '%i' % (self.last_total)
 
404
            c = '%*i' % (len(t), self.last_cnt)
229
405
            count_str = ' ' + c + '/' + t 
230
406
 
231
407
        if self.show_bar:
232
408
            # progress bar, if present, soaks up all remaining space
233
 
            cols = self.width - 1 - len(msg) - len(spin_str) - len(pct_str) \
 
409
            cols = self.width - 1 - len(self.last_msg) - len(spin_str) - len(pct_str) \
234
410
                   - len(eta_str) - len(count_str) - 3
235
411
 
236
 
            if total_cnt:
 
412
            if self.last_total:
237
413
                # number of markers highlighted in bar
238
 
                markers = int(round(float(cols) * current_cnt / total_cnt))
 
414
                markers = int(round(float(cols) * 
 
415
                              (self.last_cnt + self.child_fraction) / self.last_total))
239
416
                bar_str = '[' + ('=' * markers).ljust(cols) + '] '
240
417
            elif False:
241
418
                # don't know total, so can't show completion.
249
426
        else:
250
427
            bar_str = ''
251
428
 
252
 
        m = spin_str + bar_str + msg + count_str + pct_str + eta_str
 
429
        m = spin_str + bar_str + self.last_msg + count_str + pct_str + eta_str
253
430
 
254
431
        assert len(m) < self.width
255
432
        self.to_file.write('\r' + m.ljust(self.width - 1))
 
433
        self._have_output = True
256
434
        #self.to_file.flush()
257
435
            
258
 
 
259
436
    def clear(self):        
260
 
        self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
 
437
        if self._have_output:
 
438
            self.to_file.write('\r%s\r' % (' ' * (self.width - 1)))
 
439
        self._have_output = False
261
440
        #self.to_file.flush()        
262
 
    
263
 
 
264
 
        
 
441
 
 
442
 
 
443
_progress_bar_types['tty'] = TTYProgressBar
 
444
 
 
445
 
 
446
class ChildProgress(_BaseProgressBar):
 
447
    """A progress indicator that pushes its data to the parent"""
 
448
 
 
449
    def __init__(self, _stack, **kwargs):
 
450
        _BaseProgressBar.__init__(self, _stack=_stack, **kwargs)
 
451
        self.parent = _stack.top()
 
452
        self.current = None
 
453
        self.total = None
 
454
        self.child_fraction = 0
 
455
        self.message = None
 
456
 
 
457
    def update(self, msg, current_cnt=None, total_cnt=None):
 
458
        self.current = current_cnt
 
459
        self.total = total_cnt
 
460
        self.message = msg
 
461
        self.child_fraction = 0
 
462
        self.tick()
 
463
 
 
464
    def child_update(self, message, current, total):
 
465
        if current is None or total == 0:
 
466
            self.child_fraction = 0
 
467
        else:
 
468
            self.child_fraction = float(current) / total
 
469
        self.tick()
 
470
 
 
471
    def tick(self):
 
472
        if self.current is None:
 
473
            count = None
 
474
        else:
 
475
            count = self.current+self.child_fraction
 
476
            if count > self.total:
 
477
                if __debug__:
 
478
                    mutter('clamping count of %d to %d' % (count, self.total))
 
479
                count = self.total
 
480
        self.parent.child_update(self.message, count, self.total)
 
481
 
 
482
    def clear(self):
 
483
        pass
 
484
 
 
485
    def note(self, *args, **kwargs):
 
486
        self.parent.note(*args, **kwargs)
 
487
 
 
488
 
265
489
def str_tdelta(delt):
266
490
    if delt is None:
267
491
        return "-:--:--"
284
508
    if current > total:
285
509
        return None                     # wtf?
286
510
 
287
 
    elapsed = time.time() - start_time
 
511
    elapsed = time.clock() - start_time
288
512
 
289
513
    if elapsed < 2.0:                   # not enough time to estimate
290
514
        return None
294
518
    assert total_duration >= elapsed
295
519
 
296
520
    if last_updates and len(last_updates) >= n_recent:
297
 
        while len(last_updates) > n_recent:
298
 
            last_updates.popleft()
299
521
        avg = sum(last_updates) / float(len(last_updates))
300
522
        time_left = avg * (total - current)
301
523
 
307
529
    return total_duration - elapsed
308
530
 
309
531
 
 
532
class ProgressPhase(object):
 
533
    """Update progress object with the current phase"""
 
534
    def __init__(self, message, total, pb):
 
535
        object.__init__(self)
 
536
        self.pb = pb
 
537
        self.message = message
 
538
        self.total = total
 
539
        self.cur_phase = None
 
540
 
 
541
    def next_phase(self):
 
542
        if self.cur_phase is None:
 
543
            self.cur_phase = 0
 
544
        else:
 
545
            self.cur_phase += 1
 
546
        assert self.cur_phase < self.total 
 
547
        self.pb.update(self.message, self.cur_phase, self.total)
 
548
 
 
549
 
310
550
def run_tests():
311
551
    import doctest
312
552
    result = doctest.testmod()