##// END OF EJS Templates
progress: react more reasonably to nested progress topics...
Augie Fackler -
r13130:f139f34b default
parent child Browse files
Show More
@@ -1,206 +1,214 b''
1 1 # progress.py show progress bars for some actions
2 2 #
3 3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 """show progress bars for some actions
20 20
21 21 This extension uses the progress information logged by hg commands
22 22 to draw progress bars that are as informative as possible. Some progress
23 23 bars only offer indeterminate information, while others have a definite
24 24 end point.
25 25
26 26 The following settings are available::
27 27
28 28 [progress]
29 29 delay = 3 # number of seconds (float) before showing the progress bar
30 30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 31 format = topic bar number # format of the progress bar
32 32 width = <none> # if set, the maximum width of the progress information
33 33 # (that is, min(width, term width) will be used)
34 34 clear-complete = True # clear the progress bar after it's done
35 35 disable = False # if true, don't show a progress bar
36 36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 37 # disable is given
38 38
39 39 Valid entries for the format field are topic, bar, number, unit, and
40 40 item. item defaults to the last 20 characters of the item, but this
41 41 can be changed by adding either ``-<num>`` which would take the last
42 42 num characters, or ``+<num>`` for the first num characters.
43 43 """
44 44
45 45 import sys
46 46 import time
47 47
48 48 from mercurial import util
49 49
50 50 def spacejoin(*args):
51 51 return ' '.join(s for s in args if s)
52 52
53 53 def shouldprint(ui):
54 54 return (getattr(sys.stderr, 'isatty', None) and
55 55 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
56 56
57 57 class progbar(object):
58 58 def __init__(self, ui):
59 59 self.ui = ui
60 60 self.resetstate()
61 61
62 62 def resetstate(self):
63 63 self.topics = []
64 self.topicstates = {}
64 65 self.printed = False
65 66 self.lastprint = time.time() + float(self.ui.config(
66 67 'progress', 'delay', default=3))
67 68 self.indetcount = 0
68 69 self.refresh = float(self.ui.config(
69 70 'progress', 'refresh', default=0.1))
70 71 self.order = self.ui.configlist(
71 72 'progress', 'format',
72 73 default=['topic', 'bar', 'number'])
73 74
74 75 def show(self, topic, pos, item, unit, total):
75 76 if not shouldprint(self.ui):
76 77 return
77 78 termwidth = self.width()
78 79 self.printed = True
79 80 head = ''
80 81 needprogress = False
81 82 tail = ''
82 83 for indicator in self.order:
83 84 add = ''
84 85 if indicator == 'topic':
85 86 add = topic
86 87 elif indicator == 'number':
87 88 if total:
88 89 add = ('% ' + str(len(str(total))) +
89 90 's/%s') % (pos, total)
90 91 else:
91 92 add = str(pos)
92 93 elif indicator.startswith('item') and item:
93 94 slice = 'end'
94 95 if '-' in indicator:
95 96 wid = int(indicator.split('-')[1])
96 97 elif '+' in indicator:
97 98 slice = 'beginning'
98 99 wid = int(indicator.split('+')[1])
99 100 else:
100 101 wid = 20
101 102 if slice == 'end':
102 103 add = item[-wid:]
103 104 else:
104 105 add = item[:wid]
105 106 add += (wid - len(add)) * ' '
106 107 elif indicator == 'bar':
107 108 add = ''
108 109 needprogress = True
109 110 elif indicator == 'unit' and unit:
110 111 add = unit
111 112 if not needprogress:
112 113 head = spacejoin(head, add)
113 114 else:
114 115 tail = spacejoin(add, tail)
115 116 if needprogress:
116 117 used = 0
117 118 if head:
118 119 used += len(head) + 1
119 120 if tail:
120 121 used += len(tail) + 1
121 122 progwidth = termwidth - used - 3
122 123 if total and pos <= total:
123 124 amt = pos * progwidth // total
124 125 bar = '=' * (amt - 1)
125 126 if amt > 0:
126 127 bar += '>'
127 128 bar += ' ' * (progwidth - amt)
128 129 else:
129 130 progwidth -= 3
130 131 self.indetcount += 1
131 132 # mod the count by twice the width so we can make the
132 133 # cursor bounce between the right and left sides
133 134 amt = self.indetcount % (2 * progwidth)
134 135 amt -= progwidth
135 136 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
136 137 ' ' * int(abs(amt)))
137 138 prog = ''.join(('[', bar , ']'))
138 139 out = spacejoin(head, prog, tail)
139 140 else:
140 141 out = spacejoin(head, tail)
141 142 sys.stderr.write('\r' + out[:termwidth])
142 143 sys.stderr.flush()
143 144
144 145 def clear(self):
145 146 if not shouldprint(self.ui):
146 147 return
147 148 sys.stderr.write('\r%s\r' % (' ' * self.width()))
148 149
149 150 def complete(self):
150 151 if not shouldprint(self.ui):
151 152 return
152 153 if self.ui.configbool('progress', 'clear-complete', default=True):
153 154 self.clear()
154 155 else:
155 156 sys.stderr.write('\n')
156 157 sys.stderr.flush()
157 158
158 159 def width(self):
159 160 tw = self.ui.termwidth()
160 161 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
161 162
162 163 def progress(self, topic, pos, item='', unit='', total=None):
163 164 if pos is None:
164 if self.topics and self.topics[-1] == topic and self.printed:
165 self.topicstates.pop(topic, None)
166 # reset the progress bar if this is the outermost topic
167 if self.topics and self.topics[0] == topic and self.printed:
165 168 self.complete()
166 169 self.resetstate()
170 # truncate the list of topics assuming all topics within
171 # this one are also closed
172 if topic in self.topics:
173 self.topics = self.topics[:self.topics.index(topic)]
167 174 else:
168 175 if topic not in self.topics:
169 176 self.topics.append(topic)
170 177 now = time.time()
171 if (now - self.lastprint >= self.refresh
172 and topic == self.topics[-1]):
178 self.topicstates[topic] = pos, item, unit, total
179 if now - self.lastprint >= self.refresh and self.topics:
173 180 self.lastprint = now
174 self.show(topic, pos, item, unit, total)
181 current = self.topics[-1]
182 self.show(current, *self.topicstates[current])
175 183
176 184 def uisetup(ui):
177 185 class progressui(ui.__class__):
178 186 _progbar = None
179 187
180 188 def progress(self, *args, **opts):
181 189 self._progbar.progress(*args, **opts)
182 190 return super(progressui, self).progress(*args, **opts)
183 191
184 192 def write(self, *args, **opts):
185 193 if self._progbar.printed:
186 194 self._progbar.clear()
187 195 return super(progressui, self).write(*args, **opts)
188 196
189 197 def write_err(self, *args, **opts):
190 198 if self._progbar.printed:
191 199 self._progbar.clear()
192 200 return super(progressui, self).write_err(*args, **opts)
193 201
194 202 # Apps that derive a class from ui.ui() can use
195 203 # setconfig('progress', 'disable', 'True') to disable this extension
196 204 if ui.configbool('progress', 'disable'):
197 205 return
198 206 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
199 207 ui.__class__ = progressui
200 208 # we instantiate one globally shared progress bar to avoid
201 209 # competing progress bars when multiple UI objects get created
202 210 if not progressui._progbar:
203 211 progressui._progbar = progbar(ui)
204 212
205 213 def reposetup(ui, repo):
206 214 uisetup(repo.ui)
@@ -1,257 +1,251 b''
1 1
2 2 $ "$TESTDIR/hghave" svn svn-bindings || exit 80
3 3
4 4 $ fixpath()
5 5 > {
6 6 > tr '\\' /
7 7 > }
8 8 $ cat > $HGRCPATH <<EOF
9 9 > [extensions]
10 10 > convert =
11 11 > graphlog =
12 12 > EOF
13 13
14 14 $ svnadmin create svn-repo
15 15 $ svnadmin load -q svn-repo < "$TESTDIR/svn/move.svndump"
16 16 $ svnpath=`pwd | fixpath`
17 17
18 18 SVN wants all paths to start with a slash. Unfortunately,
19 19 Windows ones don't. Handle that.
20 20
21 21 $ expr "$svnpath" : "\/" > /dev/null
22 22 > if [ $? -ne 0 ]; then
23 23 > svnpath="/$svnpath"
24 24 > fi
25 25 > svnurl="file://$svnpath/svn-repo"
26 26
27 27 Convert trunk and branches
28 28
29 29 $ hg convert --datesort "$svnurl"/subproject A-hg
30 30 initializing destination A-hg repository
31 31 scanning source...
32 32 sorting...
33 33 converting...
34 34 13 createtrunk
35 35 12 moved1
36 36 11 moved1
37 37 10 moved2
38 38 9 changeb and rm d2
39 39 8 changeb and rm d2
40 40 7 moved1again
41 41 6 moved1again
42 42 5 copyfilefrompast
43 43 4 copydirfrompast
44 44 3 add d3
45 45 2 copy dir and remove subdir
46 46 1 add d4old
47 47 0 rename d4old into d4new
48 48
49 49 $ cd A-hg
50 50 $ hg glog --template '{rev} {desc|firstline} files: {files}\n'
51 51 o 13 rename d4old into d4new files: d4new/g d4old/g
52 52 |
53 53 o 12 add d4old files: d4old/g
54 54 |
55 55 o 11 copy dir and remove subdir files: d3/d31/e d4/d31/e d4/f
56 56 |
57 57 o 10 add d3 files: d3/d31/e d3/f
58 58 |
59 59 o 9 copydirfrompast files: d2/d
60 60 |
61 61 o 8 copyfilefrompast files: d
62 62 |
63 63 o 7 moved1again files: d1/b d1/c
64 64 |
65 65 | o 6 moved1again files:
66 66 | |
67 67 o | 5 changeb and rm d2 files: d1/b d2/d
68 68 | |
69 69 | o 4 changeb and rm d2 files: b
70 70 | |
71 71 o | 3 moved2 files: d2/d
72 72 | |
73 73 o | 2 moved1 files: d1/b d1/c
74 74 | |
75 75 | o 1 moved1 files: b c
76 76 |
77 77 o 0 createtrunk files:
78 78
79 79
80 80 Check move copy records
81 81
82 82 $ hg st --rev 12:13 --copies
83 83 A d4new/g
84 84 d4old/g
85 85 R d4old/g
86 86
87 87 Check branches
88 88
89 89 $ hg branches
90 90 default 13:* (glob)
91 91 d1 6:* (glob)
92 92 $ cd ..
93 93
94 94 $ mkdir test-replace
95 95 $ cd test-replace
96 96 $ svnadmin create svn-repo
97 97 $ svnadmin load -q svn-repo < "$TESTDIR/svn/replace.svndump"
98 98
99 99 Convert files being replaced by directories
100 100
101 101 $ hg convert svn-repo hg-repo
102 102 initializing destination hg-repo repository
103 103 scanning source...
104 104 sorting...
105 105 converting...
106 106 6 initial
107 107 5 clobber symlink
108 108 4 clobber1
109 109 3 clobber2
110 110 2 adddb
111 111 1 branch
112 112 0 clobberdir
113 113
114 114 $ cd hg-repo
115 115
116 116 Manifest before
117 117
118 118 $ hg -v manifest -r 1
119 119 644 a
120 120 644 d/b
121 121 644 d2/a
122 122 644 @ dlink
123 123 644 @ dlink2
124 124 644 dlink3
125 125
126 126 Manifest after clobber1
127 127
128 128 $ hg -v manifest -r 2
129 129 644 a/b
130 130 644 d/b
131 131 644 d2/a
132 132 644 dlink/b
133 133 644 @ dlink2
134 134 644 dlink3
135 135
136 136 Manifest after clobber2
137 137
138 138 $ hg -v manifest -r 3
139 139 644 a/b
140 140 644 d/b
141 141 644 d2/a
142 142 644 dlink/b
143 143 644 @ dlink2
144 144 644 @ dlink3
145 145
146 146 Manifest after clobberdir
147 147
148 148 $ hg -v manifest -r 6
149 149 644 a/b
150 150 644 d/b
151 151 644 d2/a
152 152 644 d2/c
153 153 644 dlink/b
154 154 644 @ dlink2
155 155 644 @ dlink3
156 156
157 157 Try updating
158 158
159 159 $ hg up -qC default
160 160 $ cd ..
161 161
162 162 Test convert progress bar'
163 163
164 164 $ cat >> $HGRCPATH <<EOF
165 165 > [extensions]
166 166 > progress =
167 167 > [progress]
168 168 > assume-tty = 1
169 169 > delay = 0
170 170 > refresh = 0
171 171 > EOF
172 172 $ cat > filtercr.py <<EOF
173 173 > import sys, re
174 174 > for line in sys.stdin:
175 175 > line = re.sub(r'\r+[^\n]', lambda m: '\n' + m.group()[-1:], line)
176 176 > sys.stdout.write(line)
177 177 > EOF
178 178
179 179 $ hg convert svn-repo hg-progress 2>&1 | python filtercr.py
180 180
181 181 scanning [ <=> ] 1
182 182 scanning [ <=> ] 2
183 183 scanning [ <=> ] 3
184 184 scanning [ <=> ] 4
185 185 scanning [ <=> ] 5
186 186 scanning [ <=> ] 6
187 187 scanning [ <=> ] 7
188 188
189 189 converting [ ] 0/7
190 190 getting files [========> ] 1/6
191 191 getting files [==================> ] 2/6
192 192 getting files [============================> ] 3/6
193 193 getting files [======================================> ] 4/6
194 194 getting files [================================================> ] 5/6
195 195 getting files [==========================================================>] 6/6
196 196
197 197 converting [=======> ] 1/7
198 198 scanning paths [ ] 0/1
199
200 199 getting files [==========================================================>] 1/1
201 200
202 201 converting [================> ] 2/7
203 202 scanning paths [ ] 0/2
204 203 scanning paths [============================> ] 1/2
205
206 204 getting files [=============> ] 1/4
207 205 getting files [============================> ] 2/4
208 206 getting files [===========================================> ] 3/4
209 207 getting files [==========================================================>] 4/4
210 208
211 209 converting [=========================> ] 3/7
212 210 scanning paths [ ] 0/1
213
214 211 getting files [==========================================================>] 1/1
215 212
216 213 converting [==================================> ] 4/7
217 214 scanning paths [ ] 0/1
218
219 215 getting files [==========================================================>] 1/1
220 216
221 217 converting [===========================================> ] 5/7
222 218 scanning paths [ ] 0/3
223 219 scanning paths [==================> ] 1/3
224 220 scanning paths [=====================================> ] 2/3
225
226 221 getting files [======> ] 1/8
227 222 getting files [=============> ] 2/8
228 223 getting files [=====================> ] 3/8
229 224 getting files [============================> ] 4/8
230 225 getting files [===================================> ] 5/8
231 226 getting files [===========================================> ] 6/8
232 227 getting files [==================================================> ] 7/8
233 228 getting files [==========================================================>] 8/8
234 229
235 230 converting [====================================================> ] 6/7
236 231 scanning paths [ ] 0/1
237
238 232 getting files [======> ] 1/8
239 233 getting files [=============> ] 2/8
240 234 getting files [=====================> ] 3/8
241 235 getting files [============================> ] 4/8
242 236 getting files [===================================> ] 5/8
243 237 getting files [===========================================> ] 6/8
244 238 getting files [==================================================> ] 7/8
245 239 getting files [==========================================================>] 8/8
246 240
247 241 initializing destination hg-progress repository
248 242 scanning source...
249 243 sorting...
250 244 converting...
251 245 6 initial
252 246 5 clobber symlink
253 247 4 clobber1
254 248 3 clobber2
255 249 2 adddb
256 250 1 branch
257 251 0 clobberdir
General Comments 0
You need to be logged in to leave comments. Login now