##// END OF EJS Templates
util: add helper function isatty(fd) to check for tty-ness
Idan Kamara -
r14515:76f295ea default
parent child Browse files
Show More
@@ -1,116 +1,117
1 # pager.py - display output using a pager
1 # pager.py - display output using a pager
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # To load the extension, add it to your configuration file:
8 # To load the extension, add it to your configuration file:
9 #
9 #
10 # [extension]
10 # [extension]
11 # pager =
11 # pager =
12 #
12 #
13 # Run "hg help pager" to get info on configuration.
13 # Run "hg help pager" to get info on configuration.
14
14
15 '''browse command output with an external pager
15 '''browse command output with an external pager
16
16
17 To set the pager that should be used, set the application variable::
17 To set the pager that should be used, set the application variable::
18
18
19 [pager]
19 [pager]
20 pager = less -FRSX
20 pager = less -FRSX
21
21
22 If no pager is set, the pager extensions uses the environment variable
22 If no pager is set, the pager extensions uses the environment variable
23 $PAGER. If neither pager.pager, nor $PAGER is set, no pager is used.
23 $PAGER. If neither pager.pager, nor $PAGER is set, no pager is used.
24
24
25 If you notice "BROKEN PIPE" error messages, you can disable them by
25 If you notice "BROKEN PIPE" error messages, you can disable them by
26 setting::
26 setting::
27
27
28 [pager]
28 [pager]
29 quiet = True
29 quiet = True
30
30
31 You can disable the pager for certain commands by adding them to the
31 You can disable the pager for certain commands by adding them to the
32 pager.ignore list::
32 pager.ignore list::
33
33
34 [pager]
34 [pager]
35 ignore = version, help, update
35 ignore = version, help, update
36
36
37 You can also enable the pager only for certain commands using
37 You can also enable the pager only for certain commands using
38 pager.attend. Below is the default list of commands to be paged::
38 pager.attend. Below is the default list of commands to be paged::
39
39
40 [pager]
40 [pager]
41 attend = annotate, cat, diff, export, glog, log, qdiff
41 attend = annotate, cat, diff, export, glog, log, qdiff
42
42
43 Setting pager.attend to an empty value will cause all commands to be
43 Setting pager.attend to an empty value will cause all commands to be
44 paged.
44 paged.
45
45
46 If pager.attend is present, pager.ignore will be ignored.
46 If pager.attend is present, pager.ignore will be ignored.
47
47
48 To ignore global commands like :hg:`version` or :hg:`help`, you have
48 To ignore global commands like :hg:`version` or :hg:`help`, you have
49 to specify them in your user configuration file.
49 to specify them in your user configuration file.
50
50
51 The --pager=... option can also be used to control when the pager is
51 The --pager=... option can also be used to control when the pager is
52 used. Use a boolean value like yes, no, on, off, or use auto for
52 used. Use a boolean value like yes, no, on, off, or use auto for
53 normal behavior.
53 normal behavior.
54 '''
54 '''
55
55
56 import sys, os, signal, shlex, errno
56 import sys, os, signal, shlex, errno
57 from mercurial import commands, dispatch, util, extensions
57 from mercurial import commands, dispatch, util, extensions
58 from mercurial.i18n import _
58 from mercurial.i18n import _
59
59
60 def _runpager(p):
60 def _runpager(p):
61 if not hasattr(os, 'fork'):
61 if not hasattr(os, 'fork'):
62 sys.stdout = util.popen(p, 'wb')
62 sys.stdout = util.popen(p, 'wb')
63 if sys.stderr.isatty():
63 if util.isatty(sys.stderr):
64 sys.stderr = sys.stdout
64 sys.stderr = sys.stdout
65 return
65 return
66 fdin, fdout = os.pipe()
66 fdin, fdout = os.pipe()
67 pid = os.fork()
67 pid = os.fork()
68 if pid == 0:
68 if pid == 0:
69 os.close(fdin)
69 os.close(fdin)
70 os.dup2(fdout, sys.stdout.fileno())
70 os.dup2(fdout, sys.stdout.fileno())
71 if sys.stderr.isatty():
71 if util.isatty(sys.stderr):
72 os.dup2(fdout, sys.stderr.fileno())
72 os.dup2(fdout, sys.stderr.fileno())
73 os.close(fdout)
73 os.close(fdout)
74 return
74 return
75 os.dup2(fdin, sys.stdin.fileno())
75 os.dup2(fdin, sys.stdin.fileno())
76 os.close(fdin)
76 os.close(fdin)
77 os.close(fdout)
77 os.close(fdout)
78 try:
78 try:
79 os.execvp('/bin/sh', ['/bin/sh', '-c', p])
79 os.execvp('/bin/sh', ['/bin/sh', '-c', p])
80 except OSError, e:
80 except OSError, e:
81 if e.errno == errno.ENOENT:
81 if e.errno == errno.ENOENT:
82 # no /bin/sh, try executing the pager directly
82 # no /bin/sh, try executing the pager directly
83 args = shlex.split(p)
83 args = shlex.split(p)
84 os.execvp(args[0], args)
84 os.execvp(args[0], args)
85 else:
85 else:
86 raise
86 raise
87
87
88 def uisetup(ui):
88 def uisetup(ui):
89 if ui.plain():
89 if ui.plain() or '--debugger' in sys.argv or not util.isatty(sys.stdout):
90 return
90 return
91
91
92 def pagecmd(orig, ui, options, cmd, cmdfunc):
92 def pagecmd(orig, ui, options, cmd, cmdfunc):
93 p = ui.config("pager", "pager", os.environ.get("PAGER"))
93 p = ui.config("pager", "pager", os.environ.get("PAGER"))
94 if p and sys.stdout.isatty() and '--debugger' not in sys.argv:
94
95 if p:
95 attend = ui.configlist('pager', 'attend', attended)
96 attend = ui.configlist('pager', 'attend', attended)
96 auto = options['pager'] == 'auto'
97 auto = options['pager'] == 'auto'
97 always = util.parsebool(options['pager'])
98 always = util.parsebool(options['pager'])
98 if (always or auto and
99 if (always or auto and
99 (cmd in attend or
100 (cmd in attend or
100 (cmd not in ui.configlist('pager', 'ignore') and not attend))):
101 (cmd not in ui.configlist('pager', 'ignore') and not attend))):
101 ui.setconfig('ui', 'formatted', ui.formatted())
102 ui.setconfig('ui', 'formatted', ui.formatted())
102 ui.setconfig('ui', 'interactive', False)
103 ui.setconfig('ui', 'interactive', False)
103 _runpager(p)
104 _runpager(p)
104 if ui.configbool('pager', 'quiet'):
105 if ui.configbool('pager', 'quiet'):
105 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
106 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
106 return orig(ui, options, cmd, cmdfunc)
107 return orig(ui, options, cmd, cmdfunc)
107
108
108 extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
109 extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
109
110
110 def extsetup(ui):
111 def extsetup(ui):
111 commands.globalopts.append(
112 commands.globalopts.append(
112 ('', 'pager', 'auto',
113 ('', 'pager', 'auto',
113 _("when to paginate (boolean, always, auto, or never)"),
114 _("when to paginate (boolean, always, auto, or never)"),
114 _('TYPE')))
115 _('TYPE')))
115
116
116 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
117 attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
@@ -1,284 +1,284
1 # progress.py show progress bars for some actions
1 # progress.py show progress bars for some actions
2 #
2 #
3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
4 #
4 #
5 # This program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 """show progress bars for some actions
19 """show progress bars for some actions
20
20
21 This extension uses the progress information logged by hg commands
21 This extension uses the progress information logged by hg commands
22 to draw progress bars that are as informative as possible. Some progress
22 to draw progress bars that are as informative as possible. Some progress
23 bars only offer indeterminate information, while others have a definite
23 bars only offer indeterminate information, while others have a definite
24 end point.
24 end point.
25
25
26 The following settings are available::
26 The following settings are available::
27
27
28 [progress]
28 [progress]
29 delay = 3 # number of seconds (float) before showing the progress bar
29 delay = 3 # number of seconds (float) before showing the progress bar
30 refresh = 0.1 # time in seconds between refreshes of the progress bar
30 refresh = 0.1 # time in seconds between refreshes of the progress bar
31 format = topic bar number estimate # format of the progress bar
31 format = topic bar number estimate # format of the progress bar
32 width = <none> # if set, the maximum width of the progress information
32 width = <none> # if set, the maximum width of the progress information
33 # (that is, min(width, term width) will be used)
33 # (that is, min(width, term width) will be used)
34 clear-complete = True # clear the progress bar after it's done
34 clear-complete = True # clear the progress bar after it's done
35 disable = False # if true, don't show a progress bar
35 disable = False # if true, don't show a progress bar
36 assume-tty = False # if true, ALWAYS show a progress bar, unless
36 assume-tty = False # if true, ALWAYS show a progress bar, unless
37 # disable is given
37 # disable is given
38
38
39 Valid entries for the format field are topic, bar, number, unit,
39 Valid entries for the format field are topic, bar, number, unit,
40 estimate, speed, and item. item defaults to the last 20 characters of
40 estimate, speed, and item. item defaults to the last 20 characters of
41 the item, but this can be changed by adding either ``-<num>`` which
41 the item, but this can be changed by adding either ``-<num>`` which
42 would take the last num characters, or ``+<num>`` for the first num
42 would take the last num characters, or ``+<num>`` for the first num
43 characters.
43 characters.
44 """
44 """
45
45
46 import sys
46 import sys
47 import time
47 import time
48
48
49 from mercurial import util
49 from mercurial.i18n import _
50 from mercurial.i18n import _
50
51
51 def spacejoin(*args):
52 def spacejoin(*args):
52 return ' '.join(s for s in args if s)
53 return ' '.join(s for s in args if s)
53
54
54 def shouldprint(ui):
55 def shouldprint(ui):
55 return (getattr(sys.stderr, 'isatty', None) and
56 return (util.isatty(sys.stderr) or ui.configbool('progress', 'assume-tty'))
56 (sys.stderr.isatty() or ui.configbool('progress', 'assume-tty')))
57
57
58 def fmtremaining(seconds):
58 def fmtremaining(seconds):
59 if seconds < 60:
59 if seconds < 60:
60 # i18n: format XX seconds as "XXs"
60 # i18n: format XX seconds as "XXs"
61 return _("%02ds") % (seconds)
61 return _("%02ds") % (seconds)
62 minutes = seconds // 60
62 minutes = seconds // 60
63 if minutes < 60:
63 if minutes < 60:
64 seconds -= minutes * 60
64 seconds -= minutes * 60
65 # i18n: format X minutes and YY seconds as "XmYYs"
65 # i18n: format X minutes and YY seconds as "XmYYs"
66 return _("%dm%02ds") % (minutes, seconds)
66 return _("%dm%02ds") % (minutes, seconds)
67 # we're going to ignore seconds in this case
67 # we're going to ignore seconds in this case
68 minutes += 1
68 minutes += 1
69 hours = minutes // 60
69 hours = minutes // 60
70 minutes -= hours * 60
70 minutes -= hours * 60
71 if hours < 30:
71 if hours < 30:
72 # i18n: format X hours and YY minutes as "XhYYm"
72 # i18n: format X hours and YY minutes as "XhYYm"
73 return _("%dh%02dm") % (hours, minutes)
73 return _("%dh%02dm") % (hours, minutes)
74 # we're going to ignore minutes in this case
74 # we're going to ignore minutes in this case
75 hours += 1
75 hours += 1
76 days = hours // 24
76 days = hours // 24
77 hours -= days * 24
77 hours -= days * 24
78 if days < 15:
78 if days < 15:
79 # i18n: format X days and YY hours as "XdYYh"
79 # i18n: format X days and YY hours as "XdYYh"
80 return _("%dd%02dh") % (days, hours)
80 return _("%dd%02dh") % (days, hours)
81 # we're going to ignore hours in this case
81 # we're going to ignore hours in this case
82 days += 1
82 days += 1
83 weeks = days // 7
83 weeks = days // 7
84 days -= weeks * 7
84 days -= weeks * 7
85 if weeks < 55:
85 if weeks < 55:
86 # i18n: format X weeks and YY days as "XwYYd"
86 # i18n: format X weeks and YY days as "XwYYd"
87 return _("%dw%02dd") % (weeks, days)
87 return _("%dw%02dd") % (weeks, days)
88 # we're going to ignore days and treat a year as 52 weeks
88 # we're going to ignore days and treat a year as 52 weeks
89 weeks += 1
89 weeks += 1
90 years = weeks // 52
90 years = weeks // 52
91 weeks -= years * 52
91 weeks -= years * 52
92 # i18n: format X years and YY weeks as "XyYYw"
92 # i18n: format X years and YY weeks as "XyYYw"
93 return _("%dy%02dw") % (years, weeks)
93 return _("%dy%02dw") % (years, weeks)
94
94
95 class progbar(object):
95 class progbar(object):
96 def __init__(self, ui):
96 def __init__(self, ui):
97 self.ui = ui
97 self.ui = ui
98 self.resetstate()
98 self.resetstate()
99
99
100 def resetstate(self):
100 def resetstate(self):
101 self.topics = []
101 self.topics = []
102 self.topicstates = {}
102 self.topicstates = {}
103 self.starttimes = {}
103 self.starttimes = {}
104 self.startvals = {}
104 self.startvals = {}
105 self.printed = False
105 self.printed = False
106 self.lastprint = time.time() + float(self.ui.config(
106 self.lastprint = time.time() + float(self.ui.config(
107 'progress', 'delay', default=3))
107 'progress', 'delay', default=3))
108 self.indetcount = 0
108 self.indetcount = 0
109 self.refresh = float(self.ui.config(
109 self.refresh = float(self.ui.config(
110 'progress', 'refresh', default=0.1))
110 'progress', 'refresh', default=0.1))
111 self.order = self.ui.configlist(
111 self.order = self.ui.configlist(
112 'progress', 'format',
112 'progress', 'format',
113 default=['topic', 'bar', 'number', 'estimate'])
113 default=['topic', 'bar', 'number', 'estimate'])
114
114
115 def show(self, now, topic, pos, item, unit, total):
115 def show(self, now, topic, pos, item, unit, total):
116 if not shouldprint(self.ui):
116 if not shouldprint(self.ui):
117 return
117 return
118 termwidth = self.width()
118 termwidth = self.width()
119 self.printed = True
119 self.printed = True
120 head = ''
120 head = ''
121 needprogress = False
121 needprogress = False
122 tail = ''
122 tail = ''
123 for indicator in self.order:
123 for indicator in self.order:
124 add = ''
124 add = ''
125 if indicator == 'topic':
125 if indicator == 'topic':
126 add = topic
126 add = topic
127 elif indicator == 'number':
127 elif indicator == 'number':
128 if total:
128 if total:
129 add = ('% ' + str(len(str(total))) +
129 add = ('% ' + str(len(str(total))) +
130 's/%s') % (pos, total)
130 's/%s') % (pos, total)
131 else:
131 else:
132 add = str(pos)
132 add = str(pos)
133 elif indicator.startswith('item') and item:
133 elif indicator.startswith('item') and item:
134 slice = 'end'
134 slice = 'end'
135 if '-' in indicator:
135 if '-' in indicator:
136 wid = int(indicator.split('-')[1])
136 wid = int(indicator.split('-')[1])
137 elif '+' in indicator:
137 elif '+' in indicator:
138 slice = 'beginning'
138 slice = 'beginning'
139 wid = int(indicator.split('+')[1])
139 wid = int(indicator.split('+')[1])
140 else:
140 else:
141 wid = 20
141 wid = 20
142 if slice == 'end':
142 if slice == 'end':
143 add = item[-wid:]
143 add = item[-wid:]
144 else:
144 else:
145 add = item[:wid]
145 add = item[:wid]
146 add += (wid - len(add)) * ' '
146 add += (wid - len(add)) * ' '
147 elif indicator == 'bar':
147 elif indicator == 'bar':
148 add = ''
148 add = ''
149 needprogress = True
149 needprogress = True
150 elif indicator == 'unit' and unit:
150 elif indicator == 'unit' and unit:
151 add = unit
151 add = unit
152 elif indicator == 'estimate':
152 elif indicator == 'estimate':
153 add = self.estimate(topic, pos, total, now)
153 add = self.estimate(topic, pos, total, now)
154 elif indicator == 'speed':
154 elif indicator == 'speed':
155 add = self.speed(topic, pos, unit, now)
155 add = self.speed(topic, pos, unit, now)
156 if not needprogress:
156 if not needprogress:
157 head = spacejoin(head, add)
157 head = spacejoin(head, add)
158 else:
158 else:
159 tail = spacejoin(tail, add)
159 tail = spacejoin(tail, add)
160 if needprogress:
160 if needprogress:
161 used = 0
161 used = 0
162 if head:
162 if head:
163 used += len(head) + 1
163 used += len(head) + 1
164 if tail:
164 if tail:
165 used += len(tail) + 1
165 used += len(tail) + 1
166 progwidth = termwidth - used - 3
166 progwidth = termwidth - used - 3
167 if total and pos <= total:
167 if total and pos <= total:
168 amt = pos * progwidth // total
168 amt = pos * progwidth // total
169 bar = '=' * (amt - 1)
169 bar = '=' * (amt - 1)
170 if amt > 0:
170 if amt > 0:
171 bar += '>'
171 bar += '>'
172 bar += ' ' * (progwidth - amt)
172 bar += ' ' * (progwidth - amt)
173 else:
173 else:
174 progwidth -= 3
174 progwidth -= 3
175 self.indetcount += 1
175 self.indetcount += 1
176 # mod the count by twice the width so we can make the
176 # mod the count by twice the width so we can make the
177 # cursor bounce between the right and left sides
177 # cursor bounce between the right and left sides
178 amt = self.indetcount % (2 * progwidth)
178 amt = self.indetcount % (2 * progwidth)
179 amt -= progwidth
179 amt -= progwidth
180 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
180 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
181 ' ' * int(abs(amt)))
181 ' ' * int(abs(amt)))
182 prog = ''.join(('[', bar , ']'))
182 prog = ''.join(('[', bar , ']'))
183 out = spacejoin(head, prog, tail)
183 out = spacejoin(head, prog, tail)
184 else:
184 else:
185 out = spacejoin(head, tail)
185 out = spacejoin(head, tail)
186 sys.stderr.write('\r' + out[:termwidth])
186 sys.stderr.write('\r' + out[:termwidth])
187 sys.stderr.flush()
187 sys.stderr.flush()
188
188
189 def clear(self):
189 def clear(self):
190 if not shouldprint(self.ui):
190 if not shouldprint(self.ui):
191 return
191 return
192 sys.stderr.write('\r%s\r' % (' ' * self.width()))
192 sys.stderr.write('\r%s\r' % (' ' * self.width()))
193
193
194 def complete(self):
194 def complete(self):
195 if not shouldprint(self.ui):
195 if not shouldprint(self.ui):
196 return
196 return
197 if self.ui.configbool('progress', 'clear-complete', default=True):
197 if self.ui.configbool('progress', 'clear-complete', default=True):
198 self.clear()
198 self.clear()
199 else:
199 else:
200 sys.stderr.write('\n')
200 sys.stderr.write('\n')
201 sys.stderr.flush()
201 sys.stderr.flush()
202
202
203 def width(self):
203 def width(self):
204 tw = self.ui.termwidth()
204 tw = self.ui.termwidth()
205 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
205 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
206
206
207 def estimate(self, topic, pos, total, now):
207 def estimate(self, topic, pos, total, now):
208 if total is None:
208 if total is None:
209 return ''
209 return ''
210 initialpos = self.startvals[topic]
210 initialpos = self.startvals[topic]
211 target = total - initialpos
211 target = total - initialpos
212 delta = pos - initialpos
212 delta = pos - initialpos
213 if delta > 0:
213 if delta > 0:
214 elapsed = now - self.starttimes[topic]
214 elapsed = now - self.starttimes[topic]
215 if elapsed > float(
215 if elapsed > float(
216 self.ui.config('progress', 'estimate', default=2)):
216 self.ui.config('progress', 'estimate', default=2)):
217 seconds = (elapsed * (target - delta)) // delta + 1
217 seconds = (elapsed * (target - delta)) // delta + 1
218 return fmtremaining(seconds)
218 return fmtremaining(seconds)
219 return ''
219 return ''
220
220
221 def speed(self, topic, pos, unit, now):
221 def speed(self, topic, pos, unit, now):
222 initialpos = self.startvals[topic]
222 initialpos = self.startvals[topic]
223 delta = pos - initialpos
223 delta = pos - initialpos
224 elapsed = now - self.starttimes[topic]
224 elapsed = now - self.starttimes[topic]
225 if elapsed > float(
225 if elapsed > float(
226 self.ui.config('progress', 'estimate', default=2)):
226 self.ui.config('progress', 'estimate', default=2)):
227 return _('%d %s/sec') % (delta / elapsed, unit)
227 return _('%d %s/sec') % (delta / elapsed, unit)
228 return ''
228 return ''
229
229
230 def progress(self, topic, pos, item='', unit='', total=None):
230 def progress(self, topic, pos, item='', unit='', total=None):
231 now = time.time()
231 now = time.time()
232 if pos is None:
232 if pos is None:
233 self.starttimes.pop(topic, None)
233 self.starttimes.pop(topic, None)
234 self.startvals.pop(topic, None)
234 self.startvals.pop(topic, None)
235 self.topicstates.pop(topic, None)
235 self.topicstates.pop(topic, None)
236 # reset the progress bar if this is the outermost topic
236 # reset the progress bar if this is the outermost topic
237 if self.topics and self.topics[0] == topic and self.printed:
237 if self.topics and self.topics[0] == topic and self.printed:
238 self.complete()
238 self.complete()
239 self.resetstate()
239 self.resetstate()
240 # truncate the list of topics assuming all topics within
240 # truncate the list of topics assuming all topics within
241 # this one are also closed
241 # this one are also closed
242 if topic in self.topics:
242 if topic in self.topics:
243 self.topics = self.topics[:self.topics.index(topic)]
243 self.topics = self.topics[:self.topics.index(topic)]
244 else:
244 else:
245 if topic not in self.topics:
245 if topic not in self.topics:
246 self.starttimes[topic] = now
246 self.starttimes[topic] = now
247 self.startvals[topic] = pos
247 self.startvals[topic] = pos
248 self.topics.append(topic)
248 self.topics.append(topic)
249 self.topicstates[topic] = pos, item, unit, total
249 self.topicstates[topic] = pos, item, unit, total
250 if now - self.lastprint >= self.refresh and self.topics:
250 if now - self.lastprint >= self.refresh and self.topics:
251 self.lastprint = now
251 self.lastprint = now
252 self.show(now, topic, *self.topicstates[topic])
252 self.show(now, topic, *self.topicstates[topic])
253
253
254 def uisetup(ui):
254 def uisetup(ui):
255 class progressui(ui.__class__):
255 class progressui(ui.__class__):
256 _progbar = None
256 _progbar = None
257
257
258 def progress(self, *args, **opts):
258 def progress(self, *args, **opts):
259 self._progbar.progress(*args, **opts)
259 self._progbar.progress(*args, **opts)
260 return super(progressui, self).progress(*args, **opts)
260 return super(progressui, self).progress(*args, **opts)
261
261
262 def write(self, *args, **opts):
262 def write(self, *args, **opts):
263 if self._progbar.printed:
263 if self._progbar.printed:
264 self._progbar.clear()
264 self._progbar.clear()
265 return super(progressui, self).write(*args, **opts)
265 return super(progressui, self).write(*args, **opts)
266
266
267 def write_err(self, *args, **opts):
267 def write_err(self, *args, **opts):
268 if self._progbar.printed:
268 if self._progbar.printed:
269 self._progbar.clear()
269 self._progbar.clear()
270 return super(progressui, self).write_err(*args, **opts)
270 return super(progressui, self).write_err(*args, **opts)
271
271
272 # Apps that derive a class from ui.ui() can use
272 # Apps that derive a class from ui.ui() can use
273 # setconfig('progress', 'disable', 'True') to disable this extension
273 # setconfig('progress', 'disable', 'True') to disable this extension
274 if ui.configbool('progress', 'disable'):
274 if ui.configbool('progress', 'disable'):
275 return
275 return
276 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
276 if shouldprint(ui) and not ui.debugflag and not ui.quiet:
277 ui.__class__ = progressui
277 ui.__class__ = progressui
278 # we instantiate one globally shared progress bar to avoid
278 # we instantiate one globally shared progress bar to avoid
279 # competing progress bars when multiple UI objects get created
279 # competing progress bars when multiple UI objects get created
280 if not progressui._progbar:
280 if not progressui._progbar:
281 progressui._progbar = progbar(ui)
281 progressui._progbar = progbar(ui)
282
282
283 def reposetup(ui, repo):
283 def reposetup(ui, repo):
284 uisetup(repo.ui)
284 uisetup(repo.ui)
@@ -1,702 +1,696
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, scmutil, util, error
10 import config, scmutil, util, error
11
11
12 class ui(object):
12 class ui(object):
13 def __init__(self, src=None):
13 def __init__(self, src=None):
14 self._buffers = []
14 self._buffers = []
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 self._reportuntrusted = True
16 self._reportuntrusted = True
17 self._ocfg = config.config() # overlay
17 self._ocfg = config.config() # overlay
18 self._tcfg = config.config() # trusted
18 self._tcfg = config.config() # trusted
19 self._ucfg = config.config() # untrusted
19 self._ucfg = config.config() # untrusted
20 self._trustusers = set()
20 self._trustusers = set()
21 self._trustgroups = set()
21 self._trustgroups = set()
22
22
23 if src:
23 if src:
24 self._tcfg = src._tcfg.copy()
24 self._tcfg = src._tcfg.copy()
25 self._ucfg = src._ucfg.copy()
25 self._ucfg = src._ucfg.copy()
26 self._ocfg = src._ocfg.copy()
26 self._ocfg = src._ocfg.copy()
27 self._trustusers = src._trustusers.copy()
27 self._trustusers = src._trustusers.copy()
28 self._trustgroups = src._trustgroups.copy()
28 self._trustgroups = src._trustgroups.copy()
29 self.environ = src.environ
29 self.environ = src.environ
30 self.fixconfig()
30 self.fixconfig()
31 else:
31 else:
32 # shared read-only environment
32 # shared read-only environment
33 self.environ = os.environ
33 self.environ = os.environ
34 # we always trust global config files
34 # we always trust global config files
35 for f in scmutil.rcpath():
35 for f in scmutil.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37
37
38 def copy(self):
38 def copy(self):
39 return self.__class__(self)
39 return self.__class__(self)
40
40
41 def _is_trusted(self, fp, f):
41 def _is_trusted(self, fp, f):
42 st = util.fstat(fp)
42 st = util.fstat(fp)
43 if util.isowner(st):
43 if util.isowner(st):
44 return True
44 return True
45
45
46 tusers, tgroups = self._trustusers, self._trustgroups
46 tusers, tgroups = self._trustusers, self._trustgroups
47 if '*' in tusers or '*' in tgroups:
47 if '*' in tusers or '*' in tgroups:
48 return True
48 return True
49
49
50 user = util.username(st.st_uid)
50 user = util.username(st.st_uid)
51 group = util.groupname(st.st_gid)
51 group = util.groupname(st.st_gid)
52 if user in tusers or group in tgroups or user == util.username():
52 if user in tusers or group in tgroups or user == util.username():
53 return True
53 return True
54
54
55 if self._reportuntrusted:
55 if self._reportuntrusted:
56 self.warn(_('Not trusting file %s from untrusted '
56 self.warn(_('Not trusting file %s from untrusted '
57 'user %s, group %s\n') % (f, user, group))
57 'user %s, group %s\n') % (f, user, group))
58 return False
58 return False
59
59
60 def readconfig(self, filename, root=None, trust=False,
60 def readconfig(self, filename, root=None, trust=False,
61 sections=None, remap=None):
61 sections=None, remap=None):
62 try:
62 try:
63 fp = open(filename)
63 fp = open(filename)
64 except IOError:
64 except IOError:
65 if not sections: # ignore unless we were looking for something
65 if not sections: # ignore unless we were looking for something
66 return
66 return
67 raise
67 raise
68
68
69 cfg = config.config()
69 cfg = config.config()
70 trusted = sections or trust or self._is_trusted(fp, filename)
70 trusted = sections or trust or self._is_trusted(fp, filename)
71
71
72 try:
72 try:
73 cfg.read(filename, fp, sections=sections, remap=remap)
73 cfg.read(filename, fp, sections=sections, remap=remap)
74 except error.ConfigError, inst:
74 except error.ConfigError, inst:
75 if trusted:
75 if trusted:
76 raise
76 raise
77 self.warn(_("Ignored: %s\n") % str(inst))
77 self.warn(_("Ignored: %s\n") % str(inst))
78
78
79 if self.plain():
79 if self.plain():
80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
81 'logtemplate', 'style',
81 'logtemplate', 'style',
82 'traceback', 'verbose'):
82 'traceback', 'verbose'):
83 if k in cfg['ui']:
83 if k in cfg['ui']:
84 del cfg['ui'][k]
84 del cfg['ui'][k]
85 for k, v in cfg.items('defaults'):
85 for k, v in cfg.items('defaults'):
86 del cfg['defaults'][k]
86 del cfg['defaults'][k]
87 # Don't remove aliases from the configuration if in the exceptionlist
87 # Don't remove aliases from the configuration if in the exceptionlist
88 if self.plain('alias'):
88 if self.plain('alias'):
89 for k, v in cfg.items('alias'):
89 for k, v in cfg.items('alias'):
90 del cfg['alias'][k]
90 del cfg['alias'][k]
91
91
92 if trusted:
92 if trusted:
93 self._tcfg.update(cfg)
93 self._tcfg.update(cfg)
94 self._tcfg.update(self._ocfg)
94 self._tcfg.update(self._ocfg)
95 self._ucfg.update(cfg)
95 self._ucfg.update(cfg)
96 self._ucfg.update(self._ocfg)
96 self._ucfg.update(self._ocfg)
97
97
98 if root is None:
98 if root is None:
99 root = os.path.expanduser('~')
99 root = os.path.expanduser('~')
100 self.fixconfig(root=root)
100 self.fixconfig(root=root)
101
101
102 def fixconfig(self, root=None, section=None):
102 def fixconfig(self, root=None, section=None):
103 if section in (None, 'paths'):
103 if section in (None, 'paths'):
104 # expand vars and ~
104 # expand vars and ~
105 # translate paths relative to root (or home) into absolute paths
105 # translate paths relative to root (or home) into absolute paths
106 root = root or os.getcwd()
106 root = root or os.getcwd()
107 for c in self._tcfg, self._ucfg, self._ocfg:
107 for c in self._tcfg, self._ucfg, self._ocfg:
108 for n, p in c.items('paths'):
108 for n, p in c.items('paths'):
109 if not p:
109 if not p:
110 continue
110 continue
111 if '%%' in p:
111 if '%%' in p:
112 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
112 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
113 % (n, p, self.configsource('paths', n)))
113 % (n, p, self.configsource('paths', n)))
114 p = p.replace('%%', '%')
114 p = p.replace('%%', '%')
115 p = util.expandpath(p)
115 p = util.expandpath(p)
116 if not util.hasscheme(p) and not os.path.isabs(p):
116 if not util.hasscheme(p) and not os.path.isabs(p):
117 p = os.path.normpath(os.path.join(root, p))
117 p = os.path.normpath(os.path.join(root, p))
118 c.set("paths", n, p)
118 c.set("paths", n, p)
119
119
120 if section in (None, 'ui'):
120 if section in (None, 'ui'):
121 # update ui options
121 # update ui options
122 self.debugflag = self.configbool('ui', 'debug')
122 self.debugflag = self.configbool('ui', 'debug')
123 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
123 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
124 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
124 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
125 if self.verbose and self.quiet:
125 if self.verbose and self.quiet:
126 self.quiet = self.verbose = False
126 self.quiet = self.verbose = False
127 self._reportuntrusted = self.debugflag or self.configbool("ui",
127 self._reportuntrusted = self.debugflag or self.configbool("ui",
128 "report_untrusted", True)
128 "report_untrusted", True)
129 self.tracebackflag = self.configbool('ui', 'traceback', False)
129 self.tracebackflag = self.configbool('ui', 'traceback', False)
130
130
131 if section in (None, 'trusted'):
131 if section in (None, 'trusted'):
132 # update trust information
132 # update trust information
133 self._trustusers.update(self.configlist('trusted', 'users'))
133 self._trustusers.update(self.configlist('trusted', 'users'))
134 self._trustgroups.update(self.configlist('trusted', 'groups'))
134 self._trustgroups.update(self.configlist('trusted', 'groups'))
135
135
136 def setconfig(self, section, name, value, overlay=True):
136 def setconfig(self, section, name, value, overlay=True):
137 if overlay:
137 if overlay:
138 self._ocfg.set(section, name, value)
138 self._ocfg.set(section, name, value)
139 self._tcfg.set(section, name, value)
139 self._tcfg.set(section, name, value)
140 self._ucfg.set(section, name, value)
140 self._ucfg.set(section, name, value)
141 self.fixconfig(section=section)
141 self.fixconfig(section=section)
142
142
143 def _data(self, untrusted):
143 def _data(self, untrusted):
144 return untrusted and self._ucfg or self._tcfg
144 return untrusted and self._ucfg or self._tcfg
145
145
146 def configsource(self, section, name, untrusted=False):
146 def configsource(self, section, name, untrusted=False):
147 return self._data(untrusted).source(section, name) or 'none'
147 return self._data(untrusted).source(section, name) or 'none'
148
148
149 def config(self, section, name, default=None, untrusted=False):
149 def config(self, section, name, default=None, untrusted=False):
150 value = self._data(untrusted).get(section, name, default)
150 value = self._data(untrusted).get(section, name, default)
151 if self.debugflag and not untrusted and self._reportuntrusted:
151 if self.debugflag and not untrusted and self._reportuntrusted:
152 uvalue = self._ucfg.get(section, name)
152 uvalue = self._ucfg.get(section, name)
153 if uvalue is not None and uvalue != value:
153 if uvalue is not None and uvalue != value:
154 self.debug(_("ignoring untrusted configuration option "
154 self.debug(_("ignoring untrusted configuration option "
155 "%s.%s = %s\n") % (section, name, uvalue))
155 "%s.%s = %s\n") % (section, name, uvalue))
156 return value
156 return value
157
157
158 def configpath(self, section, name, default=None, untrusted=False):
158 def configpath(self, section, name, default=None, untrusted=False):
159 'get a path config item, expanded relative to config file'
159 'get a path config item, expanded relative to config file'
160 v = self.config(section, name, default, untrusted)
160 v = self.config(section, name, default, untrusted)
161 if not os.path.isabs(v) or "://" not in v:
161 if not os.path.isabs(v) or "://" not in v:
162 src = self.configsource(section, name, untrusted)
162 src = self.configsource(section, name, untrusted)
163 if ':' in src:
163 if ':' in src:
164 base = os.path.dirname(src.rsplit(':'))
164 base = os.path.dirname(src.rsplit(':'))
165 v = os.path.join(base, os.path.expanduser(v))
165 v = os.path.join(base, os.path.expanduser(v))
166 return v
166 return v
167
167
168 def configbool(self, section, name, default=False, untrusted=False):
168 def configbool(self, section, name, default=False, untrusted=False):
169 """parse a configuration element as a boolean
169 """parse a configuration element as a boolean
170
170
171 >>> u = ui(); s = 'foo'
171 >>> u = ui(); s = 'foo'
172 >>> u.setconfig(s, 'true', 'yes')
172 >>> u.setconfig(s, 'true', 'yes')
173 >>> u.configbool(s, 'true')
173 >>> u.configbool(s, 'true')
174 True
174 True
175 >>> u.setconfig(s, 'false', 'no')
175 >>> u.setconfig(s, 'false', 'no')
176 >>> u.configbool(s, 'false')
176 >>> u.configbool(s, 'false')
177 False
177 False
178 >>> u.configbool(s, 'unknown')
178 >>> u.configbool(s, 'unknown')
179 False
179 False
180 >>> u.configbool(s, 'unknown', True)
180 >>> u.configbool(s, 'unknown', True)
181 True
181 True
182 >>> u.setconfig(s, 'invalid', 'somevalue')
182 >>> u.setconfig(s, 'invalid', 'somevalue')
183 >>> u.configbool(s, 'invalid')
183 >>> u.configbool(s, 'invalid')
184 Traceback (most recent call last):
184 Traceback (most recent call last):
185 ...
185 ...
186 ConfigError: foo.invalid is not a boolean ('somevalue')
186 ConfigError: foo.invalid is not a boolean ('somevalue')
187 """
187 """
188
188
189 v = self.config(section, name, None, untrusted)
189 v = self.config(section, name, None, untrusted)
190 if v is None:
190 if v is None:
191 return default
191 return default
192 if isinstance(v, bool):
192 if isinstance(v, bool):
193 return v
193 return v
194 b = util.parsebool(v)
194 b = util.parsebool(v)
195 if b is None:
195 if b is None:
196 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
196 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
197 % (section, name, v))
197 % (section, name, v))
198 return b
198 return b
199
199
200 def configint(self, section, name, default=None, untrusted=False):
200 def configint(self, section, name, default=None, untrusted=False):
201 """parse a configuration element as an integer
201 """parse a configuration element as an integer
202
202
203 >>> u = ui(); s = 'foo'
203 >>> u = ui(); s = 'foo'
204 >>> u.setconfig(s, 'int1', '42')
204 >>> u.setconfig(s, 'int1', '42')
205 >>> u.configint(s, 'int1')
205 >>> u.configint(s, 'int1')
206 42
206 42
207 >>> u.setconfig(s, 'int2', '-42')
207 >>> u.setconfig(s, 'int2', '-42')
208 >>> u.configint(s, 'int2')
208 >>> u.configint(s, 'int2')
209 -42
209 -42
210 >>> u.configint(s, 'unknown', 7)
210 >>> u.configint(s, 'unknown', 7)
211 7
211 7
212 >>> u.setconfig(s, 'invalid', 'somevalue')
212 >>> u.setconfig(s, 'invalid', 'somevalue')
213 >>> u.configint(s, 'invalid')
213 >>> u.configint(s, 'invalid')
214 Traceback (most recent call last):
214 Traceback (most recent call last):
215 ...
215 ...
216 ConfigError: foo.invalid is not an integer ('somevalue')
216 ConfigError: foo.invalid is not an integer ('somevalue')
217 """
217 """
218
218
219 v = self.config(section, name, None, untrusted)
219 v = self.config(section, name, None, untrusted)
220 if v is None:
220 if v is None:
221 return default
221 return default
222 try:
222 try:
223 return int(v)
223 return int(v)
224 except ValueError:
224 except ValueError:
225 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
225 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
226 % (section, name, v))
226 % (section, name, v))
227
227
228 def configlist(self, section, name, default=None, untrusted=False):
228 def configlist(self, section, name, default=None, untrusted=False):
229 """parse a configuration element as a list of comma/space separated
229 """parse a configuration element as a list of comma/space separated
230 strings
230 strings
231
231
232 >>> u = ui(); s = 'foo'
232 >>> u = ui(); s = 'foo'
233 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
233 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
234 >>> u.configlist(s, 'list1')
234 >>> u.configlist(s, 'list1')
235 ['this', 'is', 'a small', 'test']
235 ['this', 'is', 'a small', 'test']
236 """
236 """
237
237
238 def _parse_plain(parts, s, offset):
238 def _parse_plain(parts, s, offset):
239 whitespace = False
239 whitespace = False
240 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
240 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
241 whitespace = True
241 whitespace = True
242 offset += 1
242 offset += 1
243 if offset >= len(s):
243 if offset >= len(s):
244 return None, parts, offset
244 return None, parts, offset
245 if whitespace:
245 if whitespace:
246 parts.append('')
246 parts.append('')
247 if s[offset] == '"' and not parts[-1]:
247 if s[offset] == '"' and not parts[-1]:
248 return _parse_quote, parts, offset + 1
248 return _parse_quote, parts, offset + 1
249 elif s[offset] == '"' and parts[-1][-1] == '\\':
249 elif s[offset] == '"' and parts[-1][-1] == '\\':
250 parts[-1] = parts[-1][:-1] + s[offset]
250 parts[-1] = parts[-1][:-1] + s[offset]
251 return _parse_plain, parts, offset + 1
251 return _parse_plain, parts, offset + 1
252 parts[-1] += s[offset]
252 parts[-1] += s[offset]
253 return _parse_plain, parts, offset + 1
253 return _parse_plain, parts, offset + 1
254
254
255 def _parse_quote(parts, s, offset):
255 def _parse_quote(parts, s, offset):
256 if offset < len(s) and s[offset] == '"': # ""
256 if offset < len(s) and s[offset] == '"': # ""
257 parts.append('')
257 parts.append('')
258 offset += 1
258 offset += 1
259 while offset < len(s) and (s[offset].isspace() or
259 while offset < len(s) and (s[offset].isspace() or
260 s[offset] == ','):
260 s[offset] == ','):
261 offset += 1
261 offset += 1
262 return _parse_plain, parts, offset
262 return _parse_plain, parts, offset
263
263
264 while offset < len(s) and s[offset] != '"':
264 while offset < len(s) and s[offset] != '"':
265 if (s[offset] == '\\' and offset + 1 < len(s)
265 if (s[offset] == '\\' and offset + 1 < len(s)
266 and s[offset + 1] == '"'):
266 and s[offset + 1] == '"'):
267 offset += 1
267 offset += 1
268 parts[-1] += '"'
268 parts[-1] += '"'
269 else:
269 else:
270 parts[-1] += s[offset]
270 parts[-1] += s[offset]
271 offset += 1
271 offset += 1
272
272
273 if offset >= len(s):
273 if offset >= len(s):
274 real_parts = _configlist(parts[-1])
274 real_parts = _configlist(parts[-1])
275 if not real_parts:
275 if not real_parts:
276 parts[-1] = '"'
276 parts[-1] = '"'
277 else:
277 else:
278 real_parts[0] = '"' + real_parts[0]
278 real_parts[0] = '"' + real_parts[0]
279 parts = parts[:-1]
279 parts = parts[:-1]
280 parts.extend(real_parts)
280 parts.extend(real_parts)
281 return None, parts, offset
281 return None, parts, offset
282
282
283 offset += 1
283 offset += 1
284 while offset < len(s) and s[offset] in [' ', ',']:
284 while offset < len(s) and s[offset] in [' ', ',']:
285 offset += 1
285 offset += 1
286
286
287 if offset < len(s):
287 if offset < len(s):
288 if offset + 1 == len(s) and s[offset] == '"':
288 if offset + 1 == len(s) and s[offset] == '"':
289 parts[-1] += '"'
289 parts[-1] += '"'
290 offset += 1
290 offset += 1
291 else:
291 else:
292 parts.append('')
292 parts.append('')
293 else:
293 else:
294 return None, parts, offset
294 return None, parts, offset
295
295
296 return _parse_plain, parts, offset
296 return _parse_plain, parts, offset
297
297
298 def _configlist(s):
298 def _configlist(s):
299 s = s.rstrip(' ,')
299 s = s.rstrip(' ,')
300 if not s:
300 if not s:
301 return []
301 return []
302 parser, parts, offset = _parse_plain, [''], 0
302 parser, parts, offset = _parse_plain, [''], 0
303 while parser:
303 while parser:
304 parser, parts, offset = parser(parts, s, offset)
304 parser, parts, offset = parser(parts, s, offset)
305 return parts
305 return parts
306
306
307 result = self.config(section, name, untrusted=untrusted)
307 result = self.config(section, name, untrusted=untrusted)
308 if result is None:
308 if result is None:
309 result = default or []
309 result = default or []
310 if isinstance(result, basestring):
310 if isinstance(result, basestring):
311 result = _configlist(result.lstrip(' ,\n'))
311 result = _configlist(result.lstrip(' ,\n'))
312 if result is None:
312 if result is None:
313 result = default or []
313 result = default or []
314 return result
314 return result
315
315
316 def has_section(self, section, untrusted=False):
316 def has_section(self, section, untrusted=False):
317 '''tell whether section exists in config.'''
317 '''tell whether section exists in config.'''
318 return section in self._data(untrusted)
318 return section in self._data(untrusted)
319
319
320 def configitems(self, section, untrusted=False):
320 def configitems(self, section, untrusted=False):
321 items = self._data(untrusted).items(section)
321 items = self._data(untrusted).items(section)
322 if self.debugflag and not untrusted and self._reportuntrusted:
322 if self.debugflag and not untrusted and self._reportuntrusted:
323 for k, v in self._ucfg.items(section):
323 for k, v in self._ucfg.items(section):
324 if self._tcfg.get(section, k) != v:
324 if self._tcfg.get(section, k) != v:
325 self.debug(_("ignoring untrusted configuration option "
325 self.debug(_("ignoring untrusted configuration option "
326 "%s.%s = %s\n") % (section, k, v))
326 "%s.%s = %s\n") % (section, k, v))
327 return items
327 return items
328
328
329 def walkconfig(self, untrusted=False):
329 def walkconfig(self, untrusted=False):
330 cfg = self._data(untrusted)
330 cfg = self._data(untrusted)
331 for section in cfg.sections():
331 for section in cfg.sections():
332 for name, value in self.configitems(section, untrusted):
332 for name, value in self.configitems(section, untrusted):
333 yield section, name, value
333 yield section, name, value
334
334
335 def plain(self, feature=None):
335 def plain(self, feature=None):
336 '''is plain mode active?
336 '''is plain mode active?
337
337
338 Plain mode means that all configuration variables which affect
338 Plain mode means that all configuration variables which affect
339 the behavior and output of Mercurial should be
339 the behavior and output of Mercurial should be
340 ignored. Additionally, the output should be stable,
340 ignored. Additionally, the output should be stable,
341 reproducible and suitable for use in scripts or applications.
341 reproducible and suitable for use in scripts or applications.
342
342
343 The only way to trigger plain mode is by setting either the
343 The only way to trigger plain mode is by setting either the
344 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
344 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
345
345
346 The return value can either be
346 The return value can either be
347 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
347 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
348 - True otherwise
348 - True otherwise
349 '''
349 '''
350 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
350 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
351 return False
351 return False
352 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
352 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
353 if feature and exceptions:
353 if feature and exceptions:
354 return feature not in exceptions
354 return feature not in exceptions
355 return True
355 return True
356
356
357 def username(self):
357 def username(self):
358 """Return default username to be used in commits.
358 """Return default username to be used in commits.
359
359
360 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
360 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
361 and stop searching if one of these is set.
361 and stop searching if one of these is set.
362 If not found and ui.askusername is True, ask the user, else use
362 If not found and ui.askusername is True, ask the user, else use
363 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
363 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
364 """
364 """
365 user = os.environ.get("HGUSER")
365 user = os.environ.get("HGUSER")
366 if user is None:
366 if user is None:
367 user = self.config("ui", "username")
367 user = self.config("ui", "username")
368 if user is not None:
368 if user is not None:
369 user = os.path.expandvars(user)
369 user = os.path.expandvars(user)
370 if user is None:
370 if user is None:
371 user = os.environ.get("EMAIL")
371 user = os.environ.get("EMAIL")
372 if user is None and self.configbool("ui", "askusername"):
372 if user is None and self.configbool("ui", "askusername"):
373 user = self.prompt(_("enter a commit username:"), default=None)
373 user = self.prompt(_("enter a commit username:"), default=None)
374 if user is None and not self.interactive():
374 if user is None and not self.interactive():
375 try:
375 try:
376 user = '%s@%s' % (util.getuser(), socket.getfqdn())
376 user = '%s@%s' % (util.getuser(), socket.getfqdn())
377 self.warn(_("No username found, using '%s' instead\n") % user)
377 self.warn(_("No username found, using '%s' instead\n") % user)
378 except KeyError:
378 except KeyError:
379 pass
379 pass
380 if not user:
380 if not user:
381 raise util.Abort(_('no username supplied (see "hg help config")'))
381 raise util.Abort(_('no username supplied (see "hg help config")'))
382 if "\n" in user:
382 if "\n" in user:
383 raise util.Abort(_("username %s contains a newline\n") % repr(user))
383 raise util.Abort(_("username %s contains a newline\n") % repr(user))
384 return user
384 return user
385
385
386 def shortuser(self, user):
386 def shortuser(self, user):
387 """Return a short representation of a user name or email address."""
387 """Return a short representation of a user name or email address."""
388 if not self.verbose:
388 if not self.verbose:
389 user = util.shortuser(user)
389 user = util.shortuser(user)
390 return user
390 return user
391
391
392 def expandpath(self, loc, default=None):
392 def expandpath(self, loc, default=None):
393 """Return repository location relative to cwd or from [paths]"""
393 """Return repository location relative to cwd or from [paths]"""
394 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
394 if util.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
395 return loc
395 return loc
396
396
397 path = self.config('paths', loc)
397 path = self.config('paths', loc)
398 if not path and default is not None:
398 if not path and default is not None:
399 path = self.config('paths', default)
399 path = self.config('paths', default)
400 return path or loc
400 return path or loc
401
401
402 def pushbuffer(self):
402 def pushbuffer(self):
403 self._buffers.append([])
403 self._buffers.append([])
404
404
405 def popbuffer(self, labeled=False):
405 def popbuffer(self, labeled=False):
406 '''pop the last buffer and return the buffered output
406 '''pop the last buffer and return the buffered output
407
407
408 If labeled is True, any labels associated with buffered
408 If labeled is True, any labels associated with buffered
409 output will be handled. By default, this has no effect
409 output will be handled. By default, this has no effect
410 on the output returned, but extensions and GUI tools may
410 on the output returned, but extensions and GUI tools may
411 handle this argument and returned styled output. If output
411 handle this argument and returned styled output. If output
412 is being buffered so it can be captured and parsed or
412 is being buffered so it can be captured and parsed or
413 processed, labeled should not be set to True.
413 processed, labeled should not be set to True.
414 '''
414 '''
415 return "".join(self._buffers.pop())
415 return "".join(self._buffers.pop())
416
416
417 def write(self, *args, **opts):
417 def write(self, *args, **opts):
418 '''write args to output
418 '''write args to output
419
419
420 By default, this method simply writes to the buffer or stdout,
420 By default, this method simply writes to the buffer or stdout,
421 but extensions or GUI tools may override this method,
421 but extensions or GUI tools may override this method,
422 write_err(), popbuffer(), and label() to style output from
422 write_err(), popbuffer(), and label() to style output from
423 various parts of hg.
423 various parts of hg.
424
424
425 An optional keyword argument, "label", can be passed in.
425 An optional keyword argument, "label", can be passed in.
426 This should be a string containing label names separated by
426 This should be a string containing label names separated by
427 space. Label names take the form of "topic.type". For example,
427 space. Label names take the form of "topic.type". For example,
428 ui.debug() issues a label of "ui.debug".
428 ui.debug() issues a label of "ui.debug".
429
429
430 When labeling output for a specific command, a label of
430 When labeling output for a specific command, a label of
431 "cmdname.type" is recommended. For example, status issues
431 "cmdname.type" is recommended. For example, status issues
432 a label of "status.modified" for modified files.
432 a label of "status.modified" for modified files.
433 '''
433 '''
434 if self._buffers:
434 if self._buffers:
435 self._buffers[-1].extend([str(a) for a in args])
435 self._buffers[-1].extend([str(a) for a in args])
436 else:
436 else:
437 for a in args:
437 for a in args:
438 sys.stdout.write(str(a))
438 sys.stdout.write(str(a))
439
439
440 def write_err(self, *args, **opts):
440 def write_err(self, *args, **opts):
441 try:
441 try:
442 if not getattr(sys.stdout, 'closed', False):
442 if not getattr(sys.stdout, 'closed', False):
443 sys.stdout.flush()
443 sys.stdout.flush()
444 for a in args:
444 for a in args:
445 sys.stderr.write(str(a))
445 sys.stderr.write(str(a))
446 # stderr may be buffered under win32 when redirected to files,
446 # stderr may be buffered under win32 when redirected to files,
447 # including stdout.
447 # including stdout.
448 if not getattr(sys.stderr, 'closed', False):
448 if not getattr(sys.stderr, 'closed', False):
449 sys.stderr.flush()
449 sys.stderr.flush()
450 except IOError, inst:
450 except IOError, inst:
451 if inst.errno not in (errno.EPIPE, errno.EIO):
451 if inst.errno not in (errno.EPIPE, errno.EIO):
452 raise
452 raise
453
453
454 def flush(self):
454 def flush(self):
455 try: sys.stdout.flush()
455 try: sys.stdout.flush()
456 except: pass
456 except: pass
457 try: sys.stderr.flush()
457 try: sys.stderr.flush()
458 except: pass
458 except: pass
459
459
460 def interactive(self):
460 def interactive(self):
461 '''is interactive input allowed?
461 '''is interactive input allowed?
462
462
463 An interactive session is a session where input can be reasonably read
463 An interactive session is a session where input can be reasonably read
464 from `sys.stdin'. If this function returns false, any attempt to read
464 from `sys.stdin'. If this function returns false, any attempt to read
465 from stdin should fail with an error, unless a sensible default has been
465 from stdin should fail with an error, unless a sensible default has been
466 specified.
466 specified.
467
467
468 Interactiveness is triggered by the value of the `ui.interactive'
468 Interactiveness is triggered by the value of the `ui.interactive'
469 configuration variable or - if it is unset - when `sys.stdin' points
469 configuration variable or - if it is unset - when `sys.stdin' points
470 to a terminal device.
470 to a terminal device.
471
471
472 This function refers to input only; for output, see `ui.formatted()'.
472 This function refers to input only; for output, see `ui.formatted()'.
473 '''
473 '''
474 i = self.configbool("ui", "interactive", None)
474 i = self.configbool("ui", "interactive", None)
475 if i is None:
475 if i is None:
476 try:
476 # some environments replace stdin without implementing isatty
477 return sys.stdin.isatty()
477 # usually those are non-interactive
478 except AttributeError:
478 return util.isatty(sys.stdin)
479 # some environments replace stdin without implementing isatty
480 # usually those are non-interactive
481 return False
482
479
483 return i
480 return i
484
481
485 def termwidth(self):
482 def termwidth(self):
486 '''how wide is the terminal in columns?
483 '''how wide is the terminal in columns?
487 '''
484 '''
488 if 'COLUMNS' in os.environ:
485 if 'COLUMNS' in os.environ:
489 try:
486 try:
490 return int(os.environ['COLUMNS'])
487 return int(os.environ['COLUMNS'])
491 except ValueError:
488 except ValueError:
492 pass
489 pass
493 return util.termwidth()
490 return util.termwidth()
494
491
495 def formatted(self):
492 def formatted(self):
496 '''should formatted output be used?
493 '''should formatted output be used?
497
494
498 It is often desirable to format the output to suite the output medium.
495 It is often desirable to format the output to suite the output medium.
499 Examples of this are truncating long lines or colorizing messages.
496 Examples of this are truncating long lines or colorizing messages.
500 However, this is not often not desirable when piping output into other
497 However, this is not often not desirable when piping output into other
501 utilities, e.g. `grep'.
498 utilities, e.g. `grep'.
502
499
503 Formatted output is triggered by the value of the `ui.formatted'
500 Formatted output is triggered by the value of the `ui.formatted'
504 configuration variable or - if it is unset - when `sys.stdout' points
501 configuration variable or - if it is unset - when `sys.stdout' points
505 to a terminal device. Please note that `ui.formatted' should be
502 to a terminal device. Please note that `ui.formatted' should be
506 considered an implementation detail; it is not intended for use outside
503 considered an implementation detail; it is not intended for use outside
507 Mercurial or its extensions.
504 Mercurial or its extensions.
508
505
509 This function refers to output only; for input, see `ui.interactive()'.
506 This function refers to output only; for input, see `ui.interactive()'.
510 This function always returns false when in plain mode, see `ui.plain()'.
507 This function always returns false when in plain mode, see `ui.plain()'.
511 '''
508 '''
512 if self.plain():
509 if self.plain():
513 return False
510 return False
514
511
515 i = self.configbool("ui", "formatted", None)
512 i = self.configbool("ui", "formatted", None)
516 if i is None:
513 if i is None:
517 try:
514 # some environments replace stdout without implementing isatty
518 return sys.stdout.isatty()
515 # usually those are non-interactive
519 except AttributeError:
516 return util.isatty(sys.stdout)
520 # some environments replace stdout without implementing isatty
521 # usually those are non-interactive
522 return False
523
517
524 return i
518 return i
525
519
526 def _readline(self, prompt=''):
520 def _readline(self, prompt=''):
527 if sys.stdin.isatty():
521 if util.isatty(sys.stdin):
528 try:
522 try:
529 # magically add command line editing support, where
523 # magically add command line editing support, where
530 # available
524 # available
531 import readline
525 import readline
532 # force demandimport to really load the module
526 # force demandimport to really load the module
533 readline.read_history_file
527 readline.read_history_file
534 # windows sometimes raises something other than ImportError
528 # windows sometimes raises something other than ImportError
535 except Exception:
529 except Exception:
536 pass
530 pass
537 line = raw_input(prompt)
531 line = raw_input(prompt)
538 # When stdin is in binary mode on Windows, it can cause
532 # When stdin is in binary mode on Windows, it can cause
539 # raw_input() to emit an extra trailing carriage return
533 # raw_input() to emit an extra trailing carriage return
540 if os.linesep == '\r\n' and line and line[-1] == '\r':
534 if os.linesep == '\r\n' and line and line[-1] == '\r':
541 line = line[:-1]
535 line = line[:-1]
542 return line
536 return line
543
537
544 def prompt(self, msg, default="y"):
538 def prompt(self, msg, default="y"):
545 """Prompt user with msg, read response.
539 """Prompt user with msg, read response.
546 If ui is not interactive, the default is returned.
540 If ui is not interactive, the default is returned.
547 """
541 """
548 if not self.interactive():
542 if not self.interactive():
549 self.write(msg, ' ', default, "\n")
543 self.write(msg, ' ', default, "\n")
550 return default
544 return default
551 try:
545 try:
552 r = self._readline(self.label(msg, 'ui.prompt') + ' ')
546 r = self._readline(self.label(msg, 'ui.prompt') + ' ')
553 if not r:
547 if not r:
554 return default
548 return default
555 return r
549 return r
556 except EOFError:
550 except EOFError:
557 raise util.Abort(_('response expected'))
551 raise util.Abort(_('response expected'))
558
552
559 def promptchoice(self, msg, choices, default=0):
553 def promptchoice(self, msg, choices, default=0):
560 """Prompt user with msg, read response, and ensure it matches
554 """Prompt user with msg, read response, and ensure it matches
561 one of the provided choices. The index of the choice is returned.
555 one of the provided choices. The index of the choice is returned.
562 choices is a sequence of acceptable responses with the format:
556 choices is a sequence of acceptable responses with the format:
563 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
557 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
564 If ui is not interactive, the default is returned.
558 If ui is not interactive, the default is returned.
565 """
559 """
566 resps = [s[s.index('&')+1].lower() for s in choices]
560 resps = [s[s.index('&')+1].lower() for s in choices]
567 while True:
561 while True:
568 r = self.prompt(msg, resps[default])
562 r = self.prompt(msg, resps[default])
569 if r.lower() in resps:
563 if r.lower() in resps:
570 return resps.index(r.lower())
564 return resps.index(r.lower())
571 self.write(_("unrecognized response\n"))
565 self.write(_("unrecognized response\n"))
572
566
573 def getpass(self, prompt=None, default=None):
567 def getpass(self, prompt=None, default=None):
574 if not self.interactive():
568 if not self.interactive():
575 return default
569 return default
576 try:
570 try:
577 return getpass.getpass(prompt or _('password: '))
571 return getpass.getpass(prompt or _('password: '))
578 except EOFError:
572 except EOFError:
579 raise util.Abort(_('response expected'))
573 raise util.Abort(_('response expected'))
580 def status(self, *msg, **opts):
574 def status(self, *msg, **opts):
581 '''write status message to output (if ui.quiet is False)
575 '''write status message to output (if ui.quiet is False)
582
576
583 This adds an output label of "ui.status".
577 This adds an output label of "ui.status".
584 '''
578 '''
585 if not self.quiet:
579 if not self.quiet:
586 opts['label'] = opts.get('label', '') + ' ui.status'
580 opts['label'] = opts.get('label', '') + ' ui.status'
587 self.write(*msg, **opts)
581 self.write(*msg, **opts)
588 def warn(self, *msg, **opts):
582 def warn(self, *msg, **opts):
589 '''write warning message to output (stderr)
583 '''write warning message to output (stderr)
590
584
591 This adds an output label of "ui.warning".
585 This adds an output label of "ui.warning".
592 '''
586 '''
593 opts['label'] = opts.get('label', '') + ' ui.warning'
587 opts['label'] = opts.get('label', '') + ' ui.warning'
594 self.write_err(*msg, **opts)
588 self.write_err(*msg, **opts)
595 def note(self, *msg, **opts):
589 def note(self, *msg, **opts):
596 '''write note to output (if ui.verbose is True)
590 '''write note to output (if ui.verbose is True)
597
591
598 This adds an output label of "ui.note".
592 This adds an output label of "ui.note".
599 '''
593 '''
600 if self.verbose:
594 if self.verbose:
601 opts['label'] = opts.get('label', '') + ' ui.note'
595 opts['label'] = opts.get('label', '') + ' ui.note'
602 self.write(*msg, **opts)
596 self.write(*msg, **opts)
603 def debug(self, *msg, **opts):
597 def debug(self, *msg, **opts):
604 '''write debug message to output (if ui.debugflag is True)
598 '''write debug message to output (if ui.debugflag is True)
605
599
606 This adds an output label of "ui.debug".
600 This adds an output label of "ui.debug".
607 '''
601 '''
608 if self.debugflag:
602 if self.debugflag:
609 opts['label'] = opts.get('label', '') + ' ui.debug'
603 opts['label'] = opts.get('label', '') + ' ui.debug'
610 self.write(*msg, **opts)
604 self.write(*msg, **opts)
611 def edit(self, text, user):
605 def edit(self, text, user):
612 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
606 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
613 text=True)
607 text=True)
614 try:
608 try:
615 f = os.fdopen(fd, "w")
609 f = os.fdopen(fd, "w")
616 f.write(text)
610 f.write(text)
617 f.close()
611 f.close()
618
612
619 editor = self.geteditor()
613 editor = self.geteditor()
620
614
621 util.system("%s \"%s\"" % (editor, name),
615 util.system("%s \"%s\"" % (editor, name),
622 environ={'HGUSER': user},
616 environ={'HGUSER': user},
623 onerr=util.Abort, errprefix=_("edit failed"))
617 onerr=util.Abort, errprefix=_("edit failed"))
624
618
625 f = open(name)
619 f = open(name)
626 t = f.read()
620 t = f.read()
627 f.close()
621 f.close()
628 finally:
622 finally:
629 os.unlink(name)
623 os.unlink(name)
630
624
631 return t
625 return t
632
626
633 def traceback(self, exc=None):
627 def traceback(self, exc=None):
634 '''print exception traceback if traceback printing enabled.
628 '''print exception traceback if traceback printing enabled.
635 only to call in exception handler. returns true if traceback
629 only to call in exception handler. returns true if traceback
636 printed.'''
630 printed.'''
637 if self.tracebackflag:
631 if self.tracebackflag:
638 if exc:
632 if exc:
639 traceback.print_exception(exc[0], exc[1], exc[2])
633 traceback.print_exception(exc[0], exc[1], exc[2])
640 else:
634 else:
641 traceback.print_exc()
635 traceback.print_exc()
642 return self.tracebackflag
636 return self.tracebackflag
643
637
644 def geteditor(self):
638 def geteditor(self):
645 '''return editor to use'''
639 '''return editor to use'''
646 return (os.environ.get("HGEDITOR") or
640 return (os.environ.get("HGEDITOR") or
647 self.config("ui", "editor") or
641 self.config("ui", "editor") or
648 os.environ.get("VISUAL") or
642 os.environ.get("VISUAL") or
649 os.environ.get("EDITOR", "vi"))
643 os.environ.get("EDITOR", "vi"))
650
644
651 def progress(self, topic, pos, item="", unit="", total=None):
645 def progress(self, topic, pos, item="", unit="", total=None):
652 '''show a progress message
646 '''show a progress message
653
647
654 With stock hg, this is simply a debug message that is hidden
648 With stock hg, this is simply a debug message that is hidden
655 by default, but with extensions or GUI tools it may be
649 by default, but with extensions or GUI tools it may be
656 visible. 'topic' is the current operation, 'item' is a
650 visible. 'topic' is the current operation, 'item' is a
657 non-numeric marker of the current position (ie the currently
651 non-numeric marker of the current position (ie the currently
658 in-process file), 'pos' is the current numeric position (ie
652 in-process file), 'pos' is the current numeric position (ie
659 revision, bytes, etc.), unit is a corresponding unit label,
653 revision, bytes, etc.), unit is a corresponding unit label,
660 and total is the highest expected pos.
654 and total is the highest expected pos.
661
655
662 Multiple nested topics may be active at a time.
656 Multiple nested topics may be active at a time.
663
657
664 All topics should be marked closed by setting pos to None at
658 All topics should be marked closed by setting pos to None at
665 termination.
659 termination.
666 '''
660 '''
667
661
668 if pos is None or not self.debugflag:
662 if pos is None or not self.debugflag:
669 return
663 return
670
664
671 if unit:
665 if unit:
672 unit = ' ' + unit
666 unit = ' ' + unit
673 if item:
667 if item:
674 item = ' ' + item
668 item = ' ' + item
675
669
676 if total:
670 if total:
677 pct = 100.0 * pos / total
671 pct = 100.0 * pos / total
678 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
672 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
679 % (topic, item, pos, total, unit, pct))
673 % (topic, item, pos, total, unit, pct))
680 else:
674 else:
681 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
675 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
682
676
683 def log(self, service, message):
677 def log(self, service, message):
684 '''hook for logging facility extensions
678 '''hook for logging facility extensions
685
679
686 service should be a readily-identifiable subsystem, which will
680 service should be a readily-identifiable subsystem, which will
687 allow filtering.
681 allow filtering.
688 message should be a newline-terminated string to log.
682 message should be a newline-terminated string to log.
689 '''
683 '''
690 pass
684 pass
691
685
692 def label(self, msg, label):
686 def label(self, msg, label):
693 '''style msg based on supplied label
687 '''style msg based on supplied label
694
688
695 Like ui.write(), this just returns msg unchanged, but extensions
689 Like ui.write(), this just returns msg unchanged, but extensions
696 and GUI tools can override it to allow styling output without
690 and GUI tools can override it to allow styling output without
697 writing it.
691 writing it.
698
692
699 ui.write(s, 'label') is equivalent to
693 ui.write(s, 'label') is equivalent to
700 ui.write(ui.label(s, 'label')).
694 ui.write(ui.label(s, 'label')).
701 '''
695 '''
702 return msg
696 return msg
@@ -1,1593 +1,1599
1 # util.py - Mercurial utility functions and platform specfic implementations
1 # util.py - Mercurial utility functions and platform specfic implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specfic implementations.
10 """Mercurial utility functions and platform specfic implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from i18n import _
16 from i18n import _
17 import error, osutil, encoding
17 import error, osutil, encoding
18 import errno, re, shutil, sys, tempfile, traceback
18 import errno, re, shutil, sys, tempfile, traceback
19 import os, time, calendar, textwrap, unicodedata, signal
19 import os, time, calendar, textwrap, unicodedata, signal
20 import imp, socket, urllib
20 import imp, socket, urllib
21
21
22 # Python compatibility
22 # Python compatibility
23
23
24 def sha1(s):
24 def sha1(s):
25 return _fastsha1(s)
25 return _fastsha1(s)
26
26
27 def _fastsha1(s):
27 def _fastsha1(s):
28 # This function will import sha1 from hashlib or sha (whichever is
28 # This function will import sha1 from hashlib or sha (whichever is
29 # available) and overwrite itself with it on the first call.
29 # available) and overwrite itself with it on the first call.
30 # Subsequent calls will go directly to the imported function.
30 # Subsequent calls will go directly to the imported function.
31 if sys.version_info >= (2, 5):
31 if sys.version_info >= (2, 5):
32 from hashlib import sha1 as _sha1
32 from hashlib import sha1 as _sha1
33 else:
33 else:
34 from sha import sha as _sha1
34 from sha import sha as _sha1
35 global _fastsha1, sha1
35 global _fastsha1, sha1
36 _fastsha1 = sha1 = _sha1
36 _fastsha1 = sha1 = _sha1
37 return _sha1(s)
37 return _sha1(s)
38
38
39 import __builtin__
39 import __builtin__
40
40
41 if sys.version_info[0] < 3:
41 if sys.version_info[0] < 3:
42 def fakebuffer(sliceable, offset=0):
42 def fakebuffer(sliceable, offset=0):
43 return sliceable[offset:]
43 return sliceable[offset:]
44 else:
44 else:
45 def fakebuffer(sliceable, offset=0):
45 def fakebuffer(sliceable, offset=0):
46 return memoryview(sliceable)[offset:]
46 return memoryview(sliceable)[offset:]
47 try:
47 try:
48 buffer
48 buffer
49 except NameError:
49 except NameError:
50 __builtin__.buffer = fakebuffer
50 __builtin__.buffer = fakebuffer
51
51
52 import subprocess
52 import subprocess
53 closefds = os.name == 'posix'
53 closefds = os.name == 'posix'
54
54
55 def popen2(cmd, env=None, newlines=False):
55 def popen2(cmd, env=None, newlines=False):
56 # Setting bufsize to -1 lets the system decide the buffer size.
56 # Setting bufsize to -1 lets the system decide the buffer size.
57 # The default for bufsize is 0, meaning unbuffered. This leads to
57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 universal_newlines=newlines,
62 universal_newlines=newlines,
63 env=env)
63 env=env)
64 return p.stdin, p.stdout
64 return p.stdin, p.stdout
65
65
66 def popen3(cmd, env=None, newlines=False):
66 def popen3(cmd, env=None, newlines=False):
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 close_fds=closefds,
68 close_fds=closefds,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 stderr=subprocess.PIPE,
70 stderr=subprocess.PIPE,
71 universal_newlines=newlines,
71 universal_newlines=newlines,
72 env=env)
72 env=env)
73 return p.stdin, p.stdout, p.stderr
73 return p.stdin, p.stdout, p.stderr
74
74
75 def version():
75 def version():
76 """Return version information if available."""
76 """Return version information if available."""
77 try:
77 try:
78 import __version__
78 import __version__
79 return __version__.version
79 return __version__.version
80 except ImportError:
80 except ImportError:
81 return 'unknown'
81 return 'unknown'
82
82
83 # used by parsedate
83 # used by parsedate
84 defaultdateformats = (
84 defaultdateformats = (
85 '%Y-%m-%d %H:%M:%S',
85 '%Y-%m-%d %H:%M:%S',
86 '%Y-%m-%d %I:%M:%S%p',
86 '%Y-%m-%d %I:%M:%S%p',
87 '%Y-%m-%d %H:%M',
87 '%Y-%m-%d %H:%M',
88 '%Y-%m-%d %I:%M%p',
88 '%Y-%m-%d %I:%M%p',
89 '%Y-%m-%d',
89 '%Y-%m-%d',
90 '%m-%d',
90 '%m-%d',
91 '%m/%d',
91 '%m/%d',
92 '%m/%d/%y',
92 '%m/%d/%y',
93 '%m/%d/%Y',
93 '%m/%d/%Y',
94 '%a %b %d %H:%M:%S %Y',
94 '%a %b %d %H:%M:%S %Y',
95 '%a %b %d %I:%M:%S%p %Y',
95 '%a %b %d %I:%M:%S%p %Y',
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 '%b %d %H:%M:%S %Y',
97 '%b %d %H:%M:%S %Y',
98 '%b %d %I:%M:%S%p %Y',
98 '%b %d %I:%M:%S%p %Y',
99 '%b %d %H:%M:%S',
99 '%b %d %H:%M:%S',
100 '%b %d %I:%M:%S%p',
100 '%b %d %I:%M:%S%p',
101 '%b %d %H:%M',
101 '%b %d %H:%M',
102 '%b %d %I:%M%p',
102 '%b %d %I:%M%p',
103 '%b %d %Y',
103 '%b %d %Y',
104 '%b %d',
104 '%b %d',
105 '%H:%M:%S',
105 '%H:%M:%S',
106 '%I:%M:%S%p',
106 '%I:%M:%S%p',
107 '%H:%M',
107 '%H:%M',
108 '%I:%M%p',
108 '%I:%M%p',
109 )
109 )
110
110
111 extendeddateformats = defaultdateformats + (
111 extendeddateformats = defaultdateformats + (
112 "%Y",
112 "%Y",
113 "%Y-%m",
113 "%Y-%m",
114 "%b",
114 "%b",
115 "%b %Y",
115 "%b %Y",
116 )
116 )
117
117
118 def cachefunc(func):
118 def cachefunc(func):
119 '''cache the result of function calls'''
119 '''cache the result of function calls'''
120 # XXX doesn't handle keywords args
120 # XXX doesn't handle keywords args
121 cache = {}
121 cache = {}
122 if func.func_code.co_argcount == 1:
122 if func.func_code.co_argcount == 1:
123 # we gain a small amount of time because
123 # we gain a small amount of time because
124 # we don't need to pack/unpack the list
124 # we don't need to pack/unpack the list
125 def f(arg):
125 def f(arg):
126 if arg not in cache:
126 if arg not in cache:
127 cache[arg] = func(arg)
127 cache[arg] = func(arg)
128 return cache[arg]
128 return cache[arg]
129 else:
129 else:
130 def f(*args):
130 def f(*args):
131 if args not in cache:
131 if args not in cache:
132 cache[args] = func(*args)
132 cache[args] = func(*args)
133 return cache[args]
133 return cache[args]
134
134
135 return f
135 return f
136
136
137 def lrucachefunc(func):
137 def lrucachefunc(func):
138 '''cache most recent results of function calls'''
138 '''cache most recent results of function calls'''
139 cache = {}
139 cache = {}
140 order = []
140 order = []
141 if func.func_code.co_argcount == 1:
141 if func.func_code.co_argcount == 1:
142 def f(arg):
142 def f(arg):
143 if arg not in cache:
143 if arg not in cache:
144 if len(cache) > 20:
144 if len(cache) > 20:
145 del cache[order.pop(0)]
145 del cache[order.pop(0)]
146 cache[arg] = func(arg)
146 cache[arg] = func(arg)
147 else:
147 else:
148 order.remove(arg)
148 order.remove(arg)
149 order.append(arg)
149 order.append(arg)
150 return cache[arg]
150 return cache[arg]
151 else:
151 else:
152 def f(*args):
152 def f(*args):
153 if args not in cache:
153 if args not in cache:
154 if len(cache) > 20:
154 if len(cache) > 20:
155 del cache[order.pop(0)]
155 del cache[order.pop(0)]
156 cache[args] = func(*args)
156 cache[args] = func(*args)
157 else:
157 else:
158 order.remove(args)
158 order.remove(args)
159 order.append(args)
159 order.append(args)
160 return cache[args]
160 return cache[args]
161
161
162 return f
162 return f
163
163
164 class propertycache(object):
164 class propertycache(object):
165 def __init__(self, func):
165 def __init__(self, func):
166 self.func = func
166 self.func = func
167 self.name = func.__name__
167 self.name = func.__name__
168 def __get__(self, obj, type=None):
168 def __get__(self, obj, type=None):
169 result = self.func(obj)
169 result = self.func(obj)
170 setattr(obj, self.name, result)
170 setattr(obj, self.name, result)
171 return result
171 return result
172
172
173 def pipefilter(s, cmd):
173 def pipefilter(s, cmd):
174 '''filter string S through command CMD, returning its output'''
174 '''filter string S through command CMD, returning its output'''
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 pout, perr = p.communicate(s)
177 pout, perr = p.communicate(s)
178 return pout
178 return pout
179
179
180 def tempfilter(s, cmd):
180 def tempfilter(s, cmd):
181 '''filter string S through a pair of temporary files with CMD.
181 '''filter string S through a pair of temporary files with CMD.
182 CMD is used as a template to create the real command to be run,
182 CMD is used as a template to create the real command to be run,
183 with the strings INFILE and OUTFILE replaced by the real names of
183 with the strings INFILE and OUTFILE replaced by the real names of
184 the temporary files generated.'''
184 the temporary files generated.'''
185 inname, outname = None, None
185 inname, outname = None, None
186 try:
186 try:
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 fp = os.fdopen(infd, 'wb')
188 fp = os.fdopen(infd, 'wb')
189 fp.write(s)
189 fp.write(s)
190 fp.close()
190 fp.close()
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 os.close(outfd)
192 os.close(outfd)
193 cmd = cmd.replace('INFILE', inname)
193 cmd = cmd.replace('INFILE', inname)
194 cmd = cmd.replace('OUTFILE', outname)
194 cmd = cmd.replace('OUTFILE', outname)
195 code = os.system(cmd)
195 code = os.system(cmd)
196 if sys.platform == 'OpenVMS' and code & 1:
196 if sys.platform == 'OpenVMS' and code & 1:
197 code = 0
197 code = 0
198 if code:
198 if code:
199 raise Abort(_("command '%s' failed: %s") %
199 raise Abort(_("command '%s' failed: %s") %
200 (cmd, explainexit(code)))
200 (cmd, explainexit(code)))
201 fp = open(outname, 'rb')
201 fp = open(outname, 'rb')
202 r = fp.read()
202 r = fp.read()
203 fp.close()
203 fp.close()
204 return r
204 return r
205 finally:
205 finally:
206 try:
206 try:
207 if inname:
207 if inname:
208 os.unlink(inname)
208 os.unlink(inname)
209 except OSError:
209 except OSError:
210 pass
210 pass
211 try:
211 try:
212 if outname:
212 if outname:
213 os.unlink(outname)
213 os.unlink(outname)
214 except OSError:
214 except OSError:
215 pass
215 pass
216
216
217 filtertable = {
217 filtertable = {
218 'tempfile:': tempfilter,
218 'tempfile:': tempfilter,
219 'pipe:': pipefilter,
219 'pipe:': pipefilter,
220 }
220 }
221
221
222 def filter(s, cmd):
222 def filter(s, cmd):
223 "filter a string through a command that transforms its input to its output"
223 "filter a string through a command that transforms its input to its output"
224 for name, fn in filtertable.iteritems():
224 for name, fn in filtertable.iteritems():
225 if cmd.startswith(name):
225 if cmd.startswith(name):
226 return fn(s, cmd[len(name):].lstrip())
226 return fn(s, cmd[len(name):].lstrip())
227 return pipefilter(s, cmd)
227 return pipefilter(s, cmd)
228
228
229 def binary(s):
229 def binary(s):
230 """return true if a string is binary data"""
230 """return true if a string is binary data"""
231 return bool(s and '\0' in s)
231 return bool(s and '\0' in s)
232
232
233 def increasingchunks(source, min=1024, max=65536):
233 def increasingchunks(source, min=1024, max=65536):
234 '''return no less than min bytes per chunk while data remains,
234 '''return no less than min bytes per chunk while data remains,
235 doubling min after each chunk until it reaches max'''
235 doubling min after each chunk until it reaches max'''
236 def log2(x):
236 def log2(x):
237 if not x:
237 if not x:
238 return 0
238 return 0
239 i = 0
239 i = 0
240 while x:
240 while x:
241 x >>= 1
241 x >>= 1
242 i += 1
242 i += 1
243 return i - 1
243 return i - 1
244
244
245 buf = []
245 buf = []
246 blen = 0
246 blen = 0
247 for chunk in source:
247 for chunk in source:
248 buf.append(chunk)
248 buf.append(chunk)
249 blen += len(chunk)
249 blen += len(chunk)
250 if blen >= min:
250 if blen >= min:
251 if min < max:
251 if min < max:
252 min = min << 1
252 min = min << 1
253 nmin = 1 << log2(blen)
253 nmin = 1 << log2(blen)
254 if nmin > min:
254 if nmin > min:
255 min = nmin
255 min = nmin
256 if min > max:
256 if min > max:
257 min = max
257 min = max
258 yield ''.join(buf)
258 yield ''.join(buf)
259 blen = 0
259 blen = 0
260 buf = []
260 buf = []
261 if buf:
261 if buf:
262 yield ''.join(buf)
262 yield ''.join(buf)
263
263
264 Abort = error.Abort
264 Abort = error.Abort
265
265
266 def always(fn):
266 def always(fn):
267 return True
267 return True
268
268
269 def never(fn):
269 def never(fn):
270 return False
270 return False
271
271
272 def pathto(root, n1, n2):
272 def pathto(root, n1, n2):
273 '''return the relative path from one place to another.
273 '''return the relative path from one place to another.
274 root should use os.sep to separate directories
274 root should use os.sep to separate directories
275 n1 should use os.sep to separate directories
275 n1 should use os.sep to separate directories
276 n2 should use "/" to separate directories
276 n2 should use "/" to separate directories
277 returns an os.sep-separated path.
277 returns an os.sep-separated path.
278
278
279 If n1 is a relative path, it's assumed it's
279 If n1 is a relative path, it's assumed it's
280 relative to root.
280 relative to root.
281 n2 should always be relative to root.
281 n2 should always be relative to root.
282 '''
282 '''
283 if not n1:
283 if not n1:
284 return localpath(n2)
284 return localpath(n2)
285 if os.path.isabs(n1):
285 if os.path.isabs(n1):
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
286 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
287 return os.path.join(root, localpath(n2))
287 return os.path.join(root, localpath(n2))
288 n2 = '/'.join((pconvert(root), n2))
288 n2 = '/'.join((pconvert(root), n2))
289 a, b = splitpath(n1), n2.split('/')
289 a, b = splitpath(n1), n2.split('/')
290 a.reverse()
290 a.reverse()
291 b.reverse()
291 b.reverse()
292 while a and b and a[-1] == b[-1]:
292 while a and b and a[-1] == b[-1]:
293 a.pop()
293 a.pop()
294 b.pop()
294 b.pop()
295 b.reverse()
295 b.reverse()
296 return os.sep.join((['..'] * len(a)) + b) or '.'
296 return os.sep.join((['..'] * len(a)) + b) or '.'
297
297
298 _hgexecutable = None
298 _hgexecutable = None
299
299
300 def mainfrozen():
300 def mainfrozen():
301 """return True if we are a frozen executable.
301 """return True if we are a frozen executable.
302
302
303 The code supports py2exe (most common, Windows only) and tools/freeze
303 The code supports py2exe (most common, Windows only) and tools/freeze
304 (portable, not much used).
304 (portable, not much used).
305 """
305 """
306 return (hasattr(sys, "frozen") or # new py2exe
306 return (hasattr(sys, "frozen") or # new py2exe
307 hasattr(sys, "importers") or # old py2exe
307 hasattr(sys, "importers") or # old py2exe
308 imp.is_frozen("__main__")) # tools/freeze
308 imp.is_frozen("__main__")) # tools/freeze
309
309
310 def hgexecutable():
310 def hgexecutable():
311 """return location of the 'hg' executable.
311 """return location of the 'hg' executable.
312
312
313 Defaults to $HG or 'hg' in the search path.
313 Defaults to $HG or 'hg' in the search path.
314 """
314 """
315 if _hgexecutable is None:
315 if _hgexecutable is None:
316 hg = os.environ.get('HG')
316 hg = os.environ.get('HG')
317 if hg:
317 if hg:
318 _sethgexecutable(hg)
318 _sethgexecutable(hg)
319 elif mainfrozen():
319 elif mainfrozen():
320 _sethgexecutable(sys.executable)
320 _sethgexecutable(sys.executable)
321 else:
321 else:
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
322 exe = findexe('hg') or os.path.basename(sys.argv[0])
323 _sethgexecutable(exe)
323 _sethgexecutable(exe)
324 return _hgexecutable
324 return _hgexecutable
325
325
326 def _sethgexecutable(path):
326 def _sethgexecutable(path):
327 """set location of the 'hg' executable"""
327 """set location of the 'hg' executable"""
328 global _hgexecutable
328 global _hgexecutable
329 _hgexecutable = path
329 _hgexecutable = path
330
330
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
331 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
332 '''enhanced shell command execution.
332 '''enhanced shell command execution.
333 run with environment maybe modified, maybe in different dir.
333 run with environment maybe modified, maybe in different dir.
334
334
335 if command fails and onerr is None, return status. if ui object,
335 if command fails and onerr is None, return status. if ui object,
336 print error message and return status, else raise onerr object as
336 print error message and return status, else raise onerr object as
337 exception.
337 exception.
338
338
339 if out is specified, it is assumed to be a file-like object that has a
339 if out is specified, it is assumed to be a file-like object that has a
340 write() method. stdout and stderr will be redirected to out.'''
340 write() method. stdout and stderr will be redirected to out.'''
341 try:
341 try:
342 sys.stdout.flush()
342 sys.stdout.flush()
343 except Exception:
343 except Exception:
344 pass
344 pass
345 def py2shell(val):
345 def py2shell(val):
346 'convert python object into string that is useful to shell'
346 'convert python object into string that is useful to shell'
347 if val is None or val is False:
347 if val is None or val is False:
348 return '0'
348 return '0'
349 if val is True:
349 if val is True:
350 return '1'
350 return '1'
351 return str(val)
351 return str(val)
352 origcmd = cmd
352 origcmd = cmd
353 cmd = quotecommand(cmd)
353 cmd = quotecommand(cmd)
354 env = dict(os.environ)
354 env = dict(os.environ)
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
355 env.update((k, py2shell(v)) for k, v in environ.iteritems())
356 env['HG'] = hgexecutable()
356 env['HG'] = hgexecutable()
357 if out is None:
357 if out is None:
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
358 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
359 env=env, cwd=cwd)
359 env=env, cwd=cwd)
360 else:
360 else:
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
361 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
362 env=env, cwd=cwd, stdout=subprocess.PIPE,
363 stderr=subprocess.STDOUT)
363 stderr=subprocess.STDOUT)
364 for line in proc.stdout:
364 for line in proc.stdout:
365 out.write(line)
365 out.write(line)
366 proc.wait()
366 proc.wait()
367 rc = proc.returncode
367 rc = proc.returncode
368 if sys.platform == 'OpenVMS' and rc & 1:
368 if sys.platform == 'OpenVMS' and rc & 1:
369 rc = 0
369 rc = 0
370 if rc and onerr:
370 if rc and onerr:
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
371 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
372 explainexit(rc)[0])
372 explainexit(rc)[0])
373 if errprefix:
373 if errprefix:
374 errmsg = '%s: %s' % (errprefix, errmsg)
374 errmsg = '%s: %s' % (errprefix, errmsg)
375 try:
375 try:
376 onerr.warn(errmsg + '\n')
376 onerr.warn(errmsg + '\n')
377 except AttributeError:
377 except AttributeError:
378 raise onerr(errmsg)
378 raise onerr(errmsg)
379 return rc
379 return rc
380
380
381 def checksignature(func):
381 def checksignature(func):
382 '''wrap a function with code to check for calling errors'''
382 '''wrap a function with code to check for calling errors'''
383 def check(*args, **kwargs):
383 def check(*args, **kwargs):
384 try:
384 try:
385 return func(*args, **kwargs)
385 return func(*args, **kwargs)
386 except TypeError:
386 except TypeError:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
387 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
388 raise error.SignatureError
388 raise error.SignatureError
389 raise
389 raise
390
390
391 return check
391 return check
392
392
393 def makedir(path, notindexed):
393 def makedir(path, notindexed):
394 os.mkdir(path)
394 os.mkdir(path)
395
395
396 def unlinkpath(f):
396 def unlinkpath(f):
397 """unlink and remove the directory if it is empty"""
397 """unlink and remove the directory if it is empty"""
398 os.unlink(f)
398 os.unlink(f)
399 # try removing directories that might now be empty
399 # try removing directories that might now be empty
400 try:
400 try:
401 os.removedirs(os.path.dirname(f))
401 os.removedirs(os.path.dirname(f))
402 except OSError:
402 except OSError:
403 pass
403 pass
404
404
405 def copyfile(src, dest):
405 def copyfile(src, dest):
406 "copy a file, preserving mode and atime/mtime"
406 "copy a file, preserving mode and atime/mtime"
407 if os.path.islink(src):
407 if os.path.islink(src):
408 try:
408 try:
409 os.unlink(dest)
409 os.unlink(dest)
410 except OSError:
410 except OSError:
411 pass
411 pass
412 os.symlink(os.readlink(src), dest)
412 os.symlink(os.readlink(src), dest)
413 else:
413 else:
414 try:
414 try:
415 shutil.copyfile(src, dest)
415 shutil.copyfile(src, dest)
416 shutil.copymode(src, dest)
416 shutil.copymode(src, dest)
417 except shutil.Error, inst:
417 except shutil.Error, inst:
418 raise Abort(str(inst))
418 raise Abort(str(inst))
419
419
420 def copyfiles(src, dst, hardlink=None):
420 def copyfiles(src, dst, hardlink=None):
421 """Copy a directory tree using hardlinks if possible"""
421 """Copy a directory tree using hardlinks if possible"""
422
422
423 if hardlink is None:
423 if hardlink is None:
424 hardlink = (os.stat(src).st_dev ==
424 hardlink = (os.stat(src).st_dev ==
425 os.stat(os.path.dirname(dst)).st_dev)
425 os.stat(os.path.dirname(dst)).st_dev)
426
426
427 num = 0
427 num = 0
428 if os.path.isdir(src):
428 if os.path.isdir(src):
429 os.mkdir(dst)
429 os.mkdir(dst)
430 for name, kind in osutil.listdir(src):
430 for name, kind in osutil.listdir(src):
431 srcname = os.path.join(src, name)
431 srcname = os.path.join(src, name)
432 dstname = os.path.join(dst, name)
432 dstname = os.path.join(dst, name)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
433 hardlink, n = copyfiles(srcname, dstname, hardlink)
434 num += n
434 num += n
435 else:
435 else:
436 if hardlink:
436 if hardlink:
437 try:
437 try:
438 oslink(src, dst)
438 oslink(src, dst)
439 except (IOError, OSError):
439 except (IOError, OSError):
440 hardlink = False
440 hardlink = False
441 shutil.copy(src, dst)
441 shutil.copy(src, dst)
442 else:
442 else:
443 shutil.copy(src, dst)
443 shutil.copy(src, dst)
444 num += 1
444 num += 1
445
445
446 return hardlink, num
446 return hardlink, num
447
447
448 _winreservednames = '''con prn aux nul
448 _winreservednames = '''con prn aux nul
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
449 com1 com2 com3 com4 com5 com6 com7 com8 com9
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
450 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
451 _winreservedchars = ':*?"<>|'
451 _winreservedchars = ':*?"<>|'
452 def checkwinfilename(path):
452 def checkwinfilename(path):
453 '''Check that the base-relative path is a valid filename on Windows.
453 '''Check that the base-relative path is a valid filename on Windows.
454 Returns None if the path is ok, or a UI string describing the problem.
454 Returns None if the path is ok, or a UI string describing the problem.
455
455
456 >>> checkwinfilename("just/a/normal/path")
456 >>> checkwinfilename("just/a/normal/path")
457 >>> checkwinfilename("foo/bar/con.xml")
457 >>> checkwinfilename("foo/bar/con.xml")
458 "filename contains 'con', which is reserved on Windows"
458 "filename contains 'con', which is reserved on Windows"
459 >>> checkwinfilename("foo/con.xml/bar")
459 >>> checkwinfilename("foo/con.xml/bar")
460 "filename contains 'con', which is reserved on Windows"
460 "filename contains 'con', which is reserved on Windows"
461 >>> checkwinfilename("foo/bar/xml.con")
461 >>> checkwinfilename("foo/bar/xml.con")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
462 >>> checkwinfilename("foo/bar/AUX/bla.txt")
463 "filename contains 'AUX', which is reserved on Windows"
463 "filename contains 'AUX', which is reserved on Windows"
464 >>> checkwinfilename("foo/bar/bla:.txt")
464 >>> checkwinfilename("foo/bar/bla:.txt")
465 "filename contains ':', which is reserved on Windows"
465 "filename contains ':', which is reserved on Windows"
466 >>> checkwinfilename("foo/bar/b\07la.txt")
466 >>> checkwinfilename("foo/bar/b\07la.txt")
467 "filename contains '\\\\x07', which is invalid on Windows"
467 "filename contains '\\\\x07', which is invalid on Windows"
468 >>> checkwinfilename("foo/bar/bla ")
468 >>> checkwinfilename("foo/bar/bla ")
469 "filename ends with ' ', which is not allowed on Windows"
469 "filename ends with ' ', which is not allowed on Windows"
470 '''
470 '''
471 for n in path.replace('\\', '/').split('/'):
471 for n in path.replace('\\', '/').split('/'):
472 if not n:
472 if not n:
473 continue
473 continue
474 for c in n:
474 for c in n:
475 if c in _winreservedchars:
475 if c in _winreservedchars:
476 return _("filename contains '%s', which is reserved "
476 return _("filename contains '%s', which is reserved "
477 "on Windows") % c
477 "on Windows") % c
478 if ord(c) <= 31:
478 if ord(c) <= 31:
479 return _("filename contains %r, which is invalid "
479 return _("filename contains %r, which is invalid "
480 "on Windows") % c
480 "on Windows") % c
481 base = n.split('.')[0]
481 base = n.split('.')[0]
482 if base and base.lower() in _winreservednames:
482 if base and base.lower() in _winreservednames:
483 return _("filename contains '%s', which is reserved "
483 return _("filename contains '%s', which is reserved "
484 "on Windows") % base
484 "on Windows") % base
485 t = n[-1]
485 t = n[-1]
486 if t in '. ':
486 if t in '. ':
487 return _("filename ends with '%s', which is not allowed "
487 return _("filename ends with '%s', which is not allowed "
488 "on Windows") % t
488 "on Windows") % t
489
489
490 def lookupreg(key, name=None, scope=None):
490 def lookupreg(key, name=None, scope=None):
491 return None
491 return None
492
492
493 def hidewindow():
493 def hidewindow():
494 """Hide current shell window.
494 """Hide current shell window.
495
495
496 Used to hide the window opened when starting asynchronous
496 Used to hide the window opened when starting asynchronous
497 child process under Windows, unneeded on other systems.
497 child process under Windows, unneeded on other systems.
498 """
498 """
499 pass
499 pass
500
500
501 if os.name == 'nt':
501 if os.name == 'nt':
502 checkosfilename = checkwinfilename
502 checkosfilename = checkwinfilename
503 from windows import *
503 from windows import *
504 else:
504 else:
505 from posix import *
505 from posix import *
506
506
507 def makelock(info, pathname):
507 def makelock(info, pathname):
508 try:
508 try:
509 return os.symlink(info, pathname)
509 return os.symlink(info, pathname)
510 except OSError, why:
510 except OSError, why:
511 if why.errno == errno.EEXIST:
511 if why.errno == errno.EEXIST:
512 raise
512 raise
513 except AttributeError: # no symlink in os
513 except AttributeError: # no symlink in os
514 pass
514 pass
515
515
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
516 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
517 os.write(ld, info)
517 os.write(ld, info)
518 os.close(ld)
518 os.close(ld)
519
519
520 def readlock(pathname):
520 def readlock(pathname):
521 try:
521 try:
522 return os.readlink(pathname)
522 return os.readlink(pathname)
523 except OSError, why:
523 except OSError, why:
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
524 if why.errno not in (errno.EINVAL, errno.ENOSYS):
525 raise
525 raise
526 except AttributeError: # no symlink in os
526 except AttributeError: # no symlink in os
527 pass
527 pass
528 fp = posixfile(pathname)
528 fp = posixfile(pathname)
529 r = fp.read()
529 r = fp.read()
530 fp.close()
530 fp.close()
531 return r
531 return r
532
532
533 def fstat(fp):
533 def fstat(fp):
534 '''stat file object that may not have fileno method.'''
534 '''stat file object that may not have fileno method.'''
535 try:
535 try:
536 return os.fstat(fp.fileno())
536 return os.fstat(fp.fileno())
537 except AttributeError:
537 except AttributeError:
538 return os.stat(fp.name)
538 return os.stat(fp.name)
539
539
540 # File system features
540 # File system features
541
541
542 def checkcase(path):
542 def checkcase(path):
543 """
543 """
544 Check whether the given path is on a case-sensitive filesystem
544 Check whether the given path is on a case-sensitive filesystem
545
545
546 Requires a path (like /foo/.hg) ending with a foldable final
546 Requires a path (like /foo/.hg) ending with a foldable final
547 directory component.
547 directory component.
548 """
548 """
549 s1 = os.stat(path)
549 s1 = os.stat(path)
550 d, b = os.path.split(path)
550 d, b = os.path.split(path)
551 p2 = os.path.join(d, b.upper())
551 p2 = os.path.join(d, b.upper())
552 if path == p2:
552 if path == p2:
553 p2 = os.path.join(d, b.lower())
553 p2 = os.path.join(d, b.lower())
554 try:
554 try:
555 s2 = os.stat(p2)
555 s2 = os.stat(p2)
556 if s2 == s1:
556 if s2 == s1:
557 return False
557 return False
558 return True
558 return True
559 except OSError:
559 except OSError:
560 return True
560 return True
561
561
562 _fspathcache = {}
562 _fspathcache = {}
563 def fspath(name, root):
563 def fspath(name, root):
564 '''Get name in the case stored in the filesystem
564 '''Get name in the case stored in the filesystem
565
565
566 The name is either relative to root, or it is an absolute path starting
566 The name is either relative to root, or it is an absolute path starting
567 with root. Note that this function is unnecessary, and should not be
567 with root. Note that this function is unnecessary, and should not be
568 called, for case-sensitive filesystems (simply because it's expensive).
568 called, for case-sensitive filesystems (simply because it's expensive).
569 '''
569 '''
570 # If name is absolute, make it relative
570 # If name is absolute, make it relative
571 if name.lower().startswith(root.lower()):
571 if name.lower().startswith(root.lower()):
572 l = len(root)
572 l = len(root)
573 if name[l] == os.sep or name[l] == os.altsep:
573 if name[l] == os.sep or name[l] == os.altsep:
574 l = l + 1
574 l = l + 1
575 name = name[l:]
575 name = name[l:]
576
576
577 if not os.path.lexists(os.path.join(root, name)):
577 if not os.path.lexists(os.path.join(root, name)):
578 return None
578 return None
579
579
580 seps = os.sep
580 seps = os.sep
581 if os.altsep:
581 if os.altsep:
582 seps = seps + os.altsep
582 seps = seps + os.altsep
583 # Protect backslashes. This gets silly very quickly.
583 # Protect backslashes. This gets silly very quickly.
584 seps.replace('\\','\\\\')
584 seps.replace('\\','\\\\')
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
585 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
586 dir = os.path.normcase(os.path.normpath(root))
586 dir = os.path.normcase(os.path.normpath(root))
587 result = []
587 result = []
588 for part, sep in pattern.findall(name):
588 for part, sep in pattern.findall(name):
589 if sep:
589 if sep:
590 result.append(sep)
590 result.append(sep)
591 continue
591 continue
592
592
593 if dir not in _fspathcache:
593 if dir not in _fspathcache:
594 _fspathcache[dir] = os.listdir(dir)
594 _fspathcache[dir] = os.listdir(dir)
595 contents = _fspathcache[dir]
595 contents = _fspathcache[dir]
596
596
597 lpart = part.lower()
597 lpart = part.lower()
598 lenp = len(part)
598 lenp = len(part)
599 for n in contents:
599 for n in contents:
600 if lenp == len(n) and n.lower() == lpart:
600 if lenp == len(n) and n.lower() == lpart:
601 result.append(n)
601 result.append(n)
602 break
602 break
603 else:
603 else:
604 # Cannot happen, as the file exists!
604 # Cannot happen, as the file exists!
605 result.append(part)
605 result.append(part)
606 dir = os.path.join(dir, lpart)
606 dir = os.path.join(dir, lpart)
607
607
608 return ''.join(result)
608 return ''.join(result)
609
609
610 def checknlink(testfile):
610 def checknlink(testfile):
611 '''check whether hardlink count reporting works properly'''
611 '''check whether hardlink count reporting works properly'''
612
612
613 # testfile may be open, so we need a separate file for checking to
613 # testfile may be open, so we need a separate file for checking to
614 # work around issue2543 (or testfile may get lost on Samba shares)
614 # work around issue2543 (or testfile may get lost on Samba shares)
615 f1 = testfile + ".hgtmp1"
615 f1 = testfile + ".hgtmp1"
616 if os.path.lexists(f1):
616 if os.path.lexists(f1):
617 return False
617 return False
618 try:
618 try:
619 posixfile(f1, 'w').close()
619 posixfile(f1, 'w').close()
620 except IOError:
620 except IOError:
621 return False
621 return False
622
622
623 f2 = testfile + ".hgtmp2"
623 f2 = testfile + ".hgtmp2"
624 fd = None
624 fd = None
625 try:
625 try:
626 try:
626 try:
627 oslink(f1, f2)
627 oslink(f1, f2)
628 except OSError:
628 except OSError:
629 return False
629 return False
630
630
631 # nlinks() may behave differently for files on Windows shares if
631 # nlinks() may behave differently for files on Windows shares if
632 # the file is open.
632 # the file is open.
633 fd = posixfile(f2)
633 fd = posixfile(f2)
634 return nlinks(f2) > 1
634 return nlinks(f2) > 1
635 finally:
635 finally:
636 if fd is not None:
636 if fd is not None:
637 fd.close()
637 fd.close()
638 for f in (f1, f2):
638 for f in (f1, f2):
639 try:
639 try:
640 os.unlink(f)
640 os.unlink(f)
641 except OSError:
641 except OSError:
642 pass
642 pass
643
643
644 return False
644 return False
645
645
646 def endswithsep(path):
646 def endswithsep(path):
647 '''Check path ends with os.sep or os.altsep.'''
647 '''Check path ends with os.sep or os.altsep.'''
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
648 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
649
649
650 def splitpath(path):
650 def splitpath(path):
651 '''Split path by os.sep.
651 '''Split path by os.sep.
652 Note that this function does not use os.altsep because this is
652 Note that this function does not use os.altsep because this is
653 an alternative of simple "xxx.split(os.sep)".
653 an alternative of simple "xxx.split(os.sep)".
654 It is recommended to use os.path.normpath() before using this
654 It is recommended to use os.path.normpath() before using this
655 function if need.'''
655 function if need.'''
656 return path.split(os.sep)
656 return path.split(os.sep)
657
657
658 def gui():
658 def gui():
659 '''Are we running in a GUI?'''
659 '''Are we running in a GUI?'''
660 if sys.platform == 'darwin':
660 if sys.platform == 'darwin':
661 if 'SSH_CONNECTION' in os.environ:
661 if 'SSH_CONNECTION' in os.environ:
662 # handle SSH access to a box where the user is logged in
662 # handle SSH access to a box where the user is logged in
663 return False
663 return False
664 elif getattr(osutil, 'isgui', None):
664 elif getattr(osutil, 'isgui', None):
665 # check if a CoreGraphics session is available
665 # check if a CoreGraphics session is available
666 return osutil.isgui()
666 return osutil.isgui()
667 else:
667 else:
668 # pure build; use a safe default
668 # pure build; use a safe default
669 return True
669 return True
670 else:
670 else:
671 return os.name == "nt" or os.environ.get("DISPLAY")
671 return os.name == "nt" or os.environ.get("DISPLAY")
672
672
673 def mktempcopy(name, emptyok=False, createmode=None):
673 def mktempcopy(name, emptyok=False, createmode=None):
674 """Create a temporary file with the same contents from name
674 """Create a temporary file with the same contents from name
675
675
676 The permission bits are copied from the original file.
676 The permission bits are copied from the original file.
677
677
678 If the temporary file is going to be truncated immediately, you
678 If the temporary file is going to be truncated immediately, you
679 can use emptyok=True as an optimization.
679 can use emptyok=True as an optimization.
680
680
681 Returns the name of the temporary file.
681 Returns the name of the temporary file.
682 """
682 """
683 d, fn = os.path.split(name)
683 d, fn = os.path.split(name)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
684 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
685 os.close(fd)
685 os.close(fd)
686 # Temporary files are created with mode 0600, which is usually not
686 # Temporary files are created with mode 0600, which is usually not
687 # what we want. If the original file already exists, just copy
687 # what we want. If the original file already exists, just copy
688 # its mode. Otherwise, manually obey umask.
688 # its mode. Otherwise, manually obey umask.
689 try:
689 try:
690 st_mode = os.lstat(name).st_mode & 0777
690 st_mode = os.lstat(name).st_mode & 0777
691 except OSError, inst:
691 except OSError, inst:
692 if inst.errno != errno.ENOENT:
692 if inst.errno != errno.ENOENT:
693 raise
693 raise
694 st_mode = createmode
694 st_mode = createmode
695 if st_mode is None:
695 if st_mode is None:
696 st_mode = ~umask
696 st_mode = ~umask
697 st_mode &= 0666
697 st_mode &= 0666
698 os.chmod(temp, st_mode)
698 os.chmod(temp, st_mode)
699 if emptyok:
699 if emptyok:
700 return temp
700 return temp
701 try:
701 try:
702 try:
702 try:
703 ifp = posixfile(name, "rb")
703 ifp = posixfile(name, "rb")
704 except IOError, inst:
704 except IOError, inst:
705 if inst.errno == errno.ENOENT:
705 if inst.errno == errno.ENOENT:
706 return temp
706 return temp
707 if not getattr(inst, 'filename', None):
707 if not getattr(inst, 'filename', None):
708 inst.filename = name
708 inst.filename = name
709 raise
709 raise
710 ofp = posixfile(temp, "wb")
710 ofp = posixfile(temp, "wb")
711 for chunk in filechunkiter(ifp):
711 for chunk in filechunkiter(ifp):
712 ofp.write(chunk)
712 ofp.write(chunk)
713 ifp.close()
713 ifp.close()
714 ofp.close()
714 ofp.close()
715 except:
715 except:
716 try: os.unlink(temp)
716 try: os.unlink(temp)
717 except: pass
717 except: pass
718 raise
718 raise
719 return temp
719 return temp
720
720
721 class atomictempfile(object):
721 class atomictempfile(object):
722 '''writeable file object that atomically updates a file
722 '''writeable file object that atomically updates a file
723
723
724 All writes will go to a temporary copy of the original file. Call
724 All writes will go to a temporary copy of the original file. Call
725 rename() when you are done writing, and atomictempfile will rename
725 rename() when you are done writing, and atomictempfile will rename
726 the temporary copy to the original name, making the changes visible.
726 the temporary copy to the original name, making the changes visible.
727
727
728 Unlike other file-like objects, close() discards your writes by
728 Unlike other file-like objects, close() discards your writes by
729 simply deleting the temporary file.
729 simply deleting the temporary file.
730 '''
730 '''
731 def __init__(self, name, mode='w+b', createmode=None):
731 def __init__(self, name, mode='w+b', createmode=None):
732 self.__name = name # permanent name
732 self.__name = name # permanent name
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
733 self._tempname = mktempcopy(name, emptyok=('w' in mode),
734 createmode=createmode)
734 createmode=createmode)
735 self._fp = posixfile(self._tempname, mode)
735 self._fp = posixfile(self._tempname, mode)
736
736
737 # delegated methods
737 # delegated methods
738 self.write = self._fp.write
738 self.write = self._fp.write
739 self.fileno = self._fp.fileno
739 self.fileno = self._fp.fileno
740
740
741 def rename(self):
741 def rename(self):
742 if not self._fp.closed:
742 if not self._fp.closed:
743 self._fp.close()
743 self._fp.close()
744 rename(self._tempname, localpath(self.__name))
744 rename(self._tempname, localpath(self.__name))
745
745
746 def close(self):
746 def close(self):
747 if not self._fp.closed:
747 if not self._fp.closed:
748 try:
748 try:
749 os.unlink(self._tempname)
749 os.unlink(self._tempname)
750 except OSError:
750 except OSError:
751 pass
751 pass
752 self._fp.close()
752 self._fp.close()
753
753
754 def __del__(self):
754 def __del__(self):
755 if hasattr(self, '_fp'): # constructor actually did something
755 if hasattr(self, '_fp'): # constructor actually did something
756 self.close()
756 self.close()
757
757
758 def makedirs(name, mode=None):
758 def makedirs(name, mode=None):
759 """recursive directory creation with parent mode inheritance"""
759 """recursive directory creation with parent mode inheritance"""
760 parent = os.path.abspath(os.path.dirname(name))
760 parent = os.path.abspath(os.path.dirname(name))
761 try:
761 try:
762 os.mkdir(name)
762 os.mkdir(name)
763 if mode is not None:
763 if mode is not None:
764 os.chmod(name, mode)
764 os.chmod(name, mode)
765 return
765 return
766 except OSError, err:
766 except OSError, err:
767 if err.errno == errno.EEXIST:
767 if err.errno == errno.EEXIST:
768 return
768 return
769 if not name or parent == name or err.errno != errno.ENOENT:
769 if not name or parent == name or err.errno != errno.ENOENT:
770 raise
770 raise
771 makedirs(parent, mode)
771 makedirs(parent, mode)
772 makedirs(name, mode)
772 makedirs(name, mode)
773
773
774 def readfile(path):
774 def readfile(path):
775 fp = open(path, 'rb')
775 fp = open(path, 'rb')
776 try:
776 try:
777 return fp.read()
777 return fp.read()
778 finally:
778 finally:
779 fp.close()
779 fp.close()
780
780
781 def writefile(path, text):
781 def writefile(path, text):
782 fp = open(path, 'wb')
782 fp = open(path, 'wb')
783 try:
783 try:
784 fp.write(text)
784 fp.write(text)
785 finally:
785 finally:
786 fp.close()
786 fp.close()
787
787
788 def appendfile(path, text):
788 def appendfile(path, text):
789 fp = open(path, 'ab')
789 fp = open(path, 'ab')
790 try:
790 try:
791 fp.write(text)
791 fp.write(text)
792 finally:
792 finally:
793 fp.close()
793 fp.close()
794
794
795 class chunkbuffer(object):
795 class chunkbuffer(object):
796 """Allow arbitrary sized chunks of data to be efficiently read from an
796 """Allow arbitrary sized chunks of data to be efficiently read from an
797 iterator over chunks of arbitrary size."""
797 iterator over chunks of arbitrary size."""
798
798
799 def __init__(self, in_iter):
799 def __init__(self, in_iter):
800 """in_iter is the iterator that's iterating over the input chunks.
800 """in_iter is the iterator that's iterating over the input chunks.
801 targetsize is how big a buffer to try to maintain."""
801 targetsize is how big a buffer to try to maintain."""
802 def splitbig(chunks):
802 def splitbig(chunks):
803 for chunk in chunks:
803 for chunk in chunks:
804 if len(chunk) > 2**20:
804 if len(chunk) > 2**20:
805 pos = 0
805 pos = 0
806 while pos < len(chunk):
806 while pos < len(chunk):
807 end = pos + 2 ** 18
807 end = pos + 2 ** 18
808 yield chunk[pos:end]
808 yield chunk[pos:end]
809 pos = end
809 pos = end
810 else:
810 else:
811 yield chunk
811 yield chunk
812 self.iter = splitbig(in_iter)
812 self.iter = splitbig(in_iter)
813 self._queue = []
813 self._queue = []
814
814
815 def read(self, l):
815 def read(self, l):
816 """Read L bytes of data from the iterator of chunks of data.
816 """Read L bytes of data from the iterator of chunks of data.
817 Returns less than L bytes if the iterator runs dry."""
817 Returns less than L bytes if the iterator runs dry."""
818 left = l
818 left = l
819 buf = ''
819 buf = ''
820 queue = self._queue
820 queue = self._queue
821 while left > 0:
821 while left > 0:
822 # refill the queue
822 # refill the queue
823 if not queue:
823 if not queue:
824 target = 2**18
824 target = 2**18
825 for chunk in self.iter:
825 for chunk in self.iter:
826 queue.append(chunk)
826 queue.append(chunk)
827 target -= len(chunk)
827 target -= len(chunk)
828 if target <= 0:
828 if target <= 0:
829 break
829 break
830 if not queue:
830 if not queue:
831 break
831 break
832
832
833 chunk = queue.pop(0)
833 chunk = queue.pop(0)
834 left -= len(chunk)
834 left -= len(chunk)
835 if left < 0:
835 if left < 0:
836 queue.insert(0, chunk[left:])
836 queue.insert(0, chunk[left:])
837 buf += chunk[:left]
837 buf += chunk[:left]
838 else:
838 else:
839 buf += chunk
839 buf += chunk
840
840
841 return buf
841 return buf
842
842
843 def filechunkiter(f, size=65536, limit=None):
843 def filechunkiter(f, size=65536, limit=None):
844 """Create a generator that produces the data in the file size
844 """Create a generator that produces the data in the file size
845 (default 65536) bytes at a time, up to optional limit (default is
845 (default 65536) bytes at a time, up to optional limit (default is
846 to read all data). Chunks may be less than size bytes if the
846 to read all data). Chunks may be less than size bytes if the
847 chunk is the last chunk in the file, or the file is a socket or
847 chunk is the last chunk in the file, or the file is a socket or
848 some other type of file that sometimes reads less data than is
848 some other type of file that sometimes reads less data than is
849 requested."""
849 requested."""
850 assert size >= 0
850 assert size >= 0
851 assert limit is None or limit >= 0
851 assert limit is None or limit >= 0
852 while True:
852 while True:
853 if limit is None:
853 if limit is None:
854 nbytes = size
854 nbytes = size
855 else:
855 else:
856 nbytes = min(limit, size)
856 nbytes = min(limit, size)
857 s = nbytes and f.read(nbytes)
857 s = nbytes and f.read(nbytes)
858 if not s:
858 if not s:
859 break
859 break
860 if limit:
860 if limit:
861 limit -= len(s)
861 limit -= len(s)
862 yield s
862 yield s
863
863
864 def makedate():
864 def makedate():
865 lt = time.localtime()
865 lt = time.localtime()
866 if lt[8] == 1 and time.daylight:
866 if lt[8] == 1 and time.daylight:
867 tz = time.altzone
867 tz = time.altzone
868 else:
868 else:
869 tz = time.timezone
869 tz = time.timezone
870 t = time.mktime(lt)
870 t = time.mktime(lt)
871 if t < 0:
871 if t < 0:
872 hint = _("check your clock")
872 hint = _("check your clock")
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
873 raise Abort(_("negative timestamp: %d") % t, hint=hint)
874 return t, tz
874 return t, tz
875
875
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
876 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
877 """represent a (unixtime, offset) tuple as a localized time.
877 """represent a (unixtime, offset) tuple as a localized time.
878 unixtime is seconds since the epoch, and offset is the time zone's
878 unixtime is seconds since the epoch, and offset is the time zone's
879 number of seconds away from UTC. if timezone is false, do not
879 number of seconds away from UTC. if timezone is false, do not
880 append time zone to string."""
880 append time zone to string."""
881 t, tz = date or makedate()
881 t, tz = date or makedate()
882 if t < 0:
882 if t < 0:
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
883 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
884 tz = 0
884 tz = 0
885 if "%1" in format or "%2" in format:
885 if "%1" in format or "%2" in format:
886 sign = (tz > 0) and "-" or "+"
886 sign = (tz > 0) and "-" or "+"
887 minutes = abs(tz) // 60
887 minutes = abs(tz) // 60
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
888 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
889 format = format.replace("%2", "%02d" % (minutes % 60))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
890 s = time.strftime(format, time.gmtime(float(t) - tz))
891 return s
891 return s
892
892
893 def shortdate(date=None):
893 def shortdate(date=None):
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
894 """turn (timestamp, tzoff) tuple into iso 8631 date."""
895 return datestr(date, format='%Y-%m-%d')
895 return datestr(date, format='%Y-%m-%d')
896
896
897 def strdate(string, format, defaults=[]):
897 def strdate(string, format, defaults=[]):
898 """parse a localized time string and return a (unixtime, offset) tuple.
898 """parse a localized time string and return a (unixtime, offset) tuple.
899 if the string cannot be parsed, ValueError is raised."""
899 if the string cannot be parsed, ValueError is raised."""
900 def timezone(string):
900 def timezone(string):
901 tz = string.split()[-1]
901 tz = string.split()[-1]
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
902 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
903 sign = (tz[0] == "+") and 1 or -1
903 sign = (tz[0] == "+") and 1 or -1
904 hours = int(tz[1:3])
904 hours = int(tz[1:3])
905 minutes = int(tz[3:5])
905 minutes = int(tz[3:5])
906 return -sign * (hours * 60 + minutes) * 60
906 return -sign * (hours * 60 + minutes) * 60
907 if tz == "GMT" or tz == "UTC":
907 if tz == "GMT" or tz == "UTC":
908 return 0
908 return 0
909 return None
909 return None
910
910
911 # NOTE: unixtime = localunixtime + offset
911 # NOTE: unixtime = localunixtime + offset
912 offset, date = timezone(string), string
912 offset, date = timezone(string), string
913 if offset is not None:
913 if offset is not None:
914 date = " ".join(string.split()[:-1])
914 date = " ".join(string.split()[:-1])
915
915
916 # add missing elements from defaults
916 # add missing elements from defaults
917 usenow = False # default to using biased defaults
917 usenow = False # default to using biased defaults
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
918 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
919 found = [True for p in part if ("%"+p) in format]
919 found = [True for p in part if ("%"+p) in format]
920 if not found:
920 if not found:
921 date += "@" + defaults[part][usenow]
921 date += "@" + defaults[part][usenow]
922 format += "@%" + part[0]
922 format += "@%" + part[0]
923 else:
923 else:
924 # We've found a specific time element, less specific time
924 # We've found a specific time element, less specific time
925 # elements are relative to today
925 # elements are relative to today
926 usenow = True
926 usenow = True
927
927
928 timetuple = time.strptime(date, format)
928 timetuple = time.strptime(date, format)
929 localunixtime = int(calendar.timegm(timetuple))
929 localunixtime = int(calendar.timegm(timetuple))
930 if offset is None:
930 if offset is None:
931 # local timezone
931 # local timezone
932 unixtime = int(time.mktime(timetuple))
932 unixtime = int(time.mktime(timetuple))
933 offset = unixtime - localunixtime
933 offset = unixtime - localunixtime
934 else:
934 else:
935 unixtime = localunixtime + offset
935 unixtime = localunixtime + offset
936 return unixtime, offset
936 return unixtime, offset
937
937
938 def parsedate(date, formats=None, bias={}):
938 def parsedate(date, formats=None, bias={}):
939 """parse a localized date/time and return a (unixtime, offset) tuple.
939 """parse a localized date/time and return a (unixtime, offset) tuple.
940
940
941 The date may be a "unixtime offset" string or in one of the specified
941 The date may be a "unixtime offset" string or in one of the specified
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
942 formats. If the date already is a (unixtime, offset) tuple, it is returned.
943 """
943 """
944 if not date:
944 if not date:
945 return 0, 0
945 return 0, 0
946 if isinstance(date, tuple) and len(date) == 2:
946 if isinstance(date, tuple) and len(date) == 2:
947 return date
947 return date
948 if not formats:
948 if not formats:
949 formats = defaultdateformats
949 formats = defaultdateformats
950 date = date.strip()
950 date = date.strip()
951 try:
951 try:
952 when, offset = map(int, date.split(' '))
952 when, offset = map(int, date.split(' '))
953 except ValueError:
953 except ValueError:
954 # fill out defaults
954 # fill out defaults
955 now = makedate()
955 now = makedate()
956 defaults = {}
956 defaults = {}
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
957 for part in ("d", "mb", "yY", "HI", "M", "S"):
958 # this piece is for rounding the specific end of unknowns
958 # this piece is for rounding the specific end of unknowns
959 b = bias.get(part)
959 b = bias.get(part)
960 if b is None:
960 if b is None:
961 if part[0] in "HMS":
961 if part[0] in "HMS":
962 b = "00"
962 b = "00"
963 else:
963 else:
964 b = "0"
964 b = "0"
965
965
966 # this piece is for matching the generic end to today's date
966 # this piece is for matching the generic end to today's date
967 n = datestr(now, "%" + part[0])
967 n = datestr(now, "%" + part[0])
968
968
969 defaults[part] = (b, n)
969 defaults[part] = (b, n)
970
970
971 for format in formats:
971 for format in formats:
972 try:
972 try:
973 when, offset = strdate(date, format, defaults)
973 when, offset = strdate(date, format, defaults)
974 except (ValueError, OverflowError):
974 except (ValueError, OverflowError):
975 pass
975 pass
976 else:
976 else:
977 break
977 break
978 else:
978 else:
979 raise Abort(_('invalid date: %r') % date)
979 raise Abort(_('invalid date: %r') % date)
980 # validate explicit (probably user-specified) date and
980 # validate explicit (probably user-specified) date and
981 # time zone offset. values must fit in signed 32 bits for
981 # time zone offset. values must fit in signed 32 bits for
982 # current 32-bit linux runtimes. timezones go from UTC-12
982 # current 32-bit linux runtimes. timezones go from UTC-12
983 # to UTC+14
983 # to UTC+14
984 if abs(when) > 0x7fffffff:
984 if abs(when) > 0x7fffffff:
985 raise Abort(_('date exceeds 32 bits: %d') % when)
985 raise Abort(_('date exceeds 32 bits: %d') % when)
986 if when < 0:
986 if when < 0:
987 raise Abort(_('negative date value: %d') % when)
987 raise Abort(_('negative date value: %d') % when)
988 if offset < -50400 or offset > 43200:
988 if offset < -50400 or offset > 43200:
989 raise Abort(_('impossible time zone offset: %d') % offset)
989 raise Abort(_('impossible time zone offset: %d') % offset)
990 return when, offset
990 return when, offset
991
991
992 def matchdate(date):
992 def matchdate(date):
993 """Return a function that matches a given date match specifier
993 """Return a function that matches a given date match specifier
994
994
995 Formats include:
995 Formats include:
996
996
997 '{date}' match a given date to the accuracy provided
997 '{date}' match a given date to the accuracy provided
998
998
999 '<{date}' on or before a given date
999 '<{date}' on or before a given date
1000
1000
1001 '>{date}' on or after a given date
1001 '>{date}' on or after a given date
1002
1002
1003 >>> p1 = parsedate("10:29:59")
1003 >>> p1 = parsedate("10:29:59")
1004 >>> p2 = parsedate("10:30:00")
1004 >>> p2 = parsedate("10:30:00")
1005 >>> p3 = parsedate("10:30:59")
1005 >>> p3 = parsedate("10:30:59")
1006 >>> p4 = parsedate("10:31:00")
1006 >>> p4 = parsedate("10:31:00")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1007 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1008 >>> f = matchdate("10:30")
1008 >>> f = matchdate("10:30")
1009 >>> f(p1[0])
1009 >>> f(p1[0])
1010 False
1010 False
1011 >>> f(p2[0])
1011 >>> f(p2[0])
1012 True
1012 True
1013 >>> f(p3[0])
1013 >>> f(p3[0])
1014 True
1014 True
1015 >>> f(p4[0])
1015 >>> f(p4[0])
1016 False
1016 False
1017 >>> f(p5[0])
1017 >>> f(p5[0])
1018 False
1018 False
1019 """
1019 """
1020
1020
1021 def lower(date):
1021 def lower(date):
1022 d = dict(mb="1", d="1")
1022 d = dict(mb="1", d="1")
1023 return parsedate(date, extendeddateformats, d)[0]
1023 return parsedate(date, extendeddateformats, d)[0]
1024
1024
1025 def upper(date):
1025 def upper(date):
1026 d = dict(mb="12", HI="23", M="59", S="59")
1026 d = dict(mb="12", HI="23", M="59", S="59")
1027 for days in ("31", "30", "29"):
1027 for days in ("31", "30", "29"):
1028 try:
1028 try:
1029 d["d"] = days
1029 d["d"] = days
1030 return parsedate(date, extendeddateformats, d)[0]
1030 return parsedate(date, extendeddateformats, d)[0]
1031 except:
1031 except:
1032 pass
1032 pass
1033 d["d"] = "28"
1033 d["d"] = "28"
1034 return parsedate(date, extendeddateformats, d)[0]
1034 return parsedate(date, extendeddateformats, d)[0]
1035
1035
1036 date = date.strip()
1036 date = date.strip()
1037
1037
1038 if not date:
1038 if not date:
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1039 raise Abort(_("dates cannot consist entirely of whitespace"))
1040 elif date[0] == "<":
1040 elif date[0] == "<":
1041 if not date[1:]:
1041 if not date[1:]:
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1042 raise Abort(_("invalid day spec, use '<DATE'"))
1043 when = upper(date[1:])
1043 when = upper(date[1:])
1044 return lambda x: x <= when
1044 return lambda x: x <= when
1045 elif date[0] == ">":
1045 elif date[0] == ">":
1046 if not date[1:]:
1046 if not date[1:]:
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1047 raise Abort(_("invalid day spec, use '>DATE'"))
1048 when = lower(date[1:])
1048 when = lower(date[1:])
1049 return lambda x: x >= when
1049 return lambda x: x >= when
1050 elif date[0] == "-":
1050 elif date[0] == "-":
1051 try:
1051 try:
1052 days = int(date[1:])
1052 days = int(date[1:])
1053 except ValueError:
1053 except ValueError:
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1054 raise Abort(_("invalid day spec: %s") % date[1:])
1055 if days < 0:
1055 if days < 0:
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1056 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1057 % date[1:])
1057 % date[1:])
1058 when = makedate()[0] - days * 3600 * 24
1058 when = makedate()[0] - days * 3600 * 24
1059 return lambda x: x >= when
1059 return lambda x: x >= when
1060 elif " to " in date:
1060 elif " to " in date:
1061 a, b = date.split(" to ")
1061 a, b = date.split(" to ")
1062 start, stop = lower(a), upper(b)
1062 start, stop = lower(a), upper(b)
1063 return lambda x: x >= start and x <= stop
1063 return lambda x: x >= start and x <= stop
1064 else:
1064 else:
1065 start, stop = lower(date), upper(date)
1065 start, stop = lower(date), upper(date)
1066 return lambda x: x >= start and x <= stop
1066 return lambda x: x >= start and x <= stop
1067
1067
1068 def shortuser(user):
1068 def shortuser(user):
1069 """Return a short representation of a user name or email address."""
1069 """Return a short representation of a user name or email address."""
1070 f = user.find('@')
1070 f = user.find('@')
1071 if f >= 0:
1071 if f >= 0:
1072 user = user[:f]
1072 user = user[:f]
1073 f = user.find('<')
1073 f = user.find('<')
1074 if f >= 0:
1074 if f >= 0:
1075 user = user[f + 1:]
1075 user = user[f + 1:]
1076 f = user.find(' ')
1076 f = user.find(' ')
1077 if f >= 0:
1077 if f >= 0:
1078 user = user[:f]
1078 user = user[:f]
1079 f = user.find('.')
1079 f = user.find('.')
1080 if f >= 0:
1080 if f >= 0:
1081 user = user[:f]
1081 user = user[:f]
1082 return user
1082 return user
1083
1083
1084 def email(author):
1084 def email(author):
1085 '''get email of author.'''
1085 '''get email of author.'''
1086 r = author.find('>')
1086 r = author.find('>')
1087 if r == -1:
1087 if r == -1:
1088 r = None
1088 r = None
1089 return author[author.find('<') + 1:r]
1089 return author[author.find('<') + 1:r]
1090
1090
1091 def _ellipsis(text, maxlength):
1091 def _ellipsis(text, maxlength):
1092 if len(text) <= maxlength:
1092 if len(text) <= maxlength:
1093 return text, False
1093 return text, False
1094 else:
1094 else:
1095 return "%s..." % (text[:maxlength - 3]), True
1095 return "%s..." % (text[:maxlength - 3]), True
1096
1096
1097 def ellipsis(text, maxlength=400):
1097 def ellipsis(text, maxlength=400):
1098 """Trim string to at most maxlength (default: 400) characters."""
1098 """Trim string to at most maxlength (default: 400) characters."""
1099 try:
1099 try:
1100 # use unicode not to split at intermediate multi-byte sequence
1100 # use unicode not to split at intermediate multi-byte sequence
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1101 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1102 maxlength)
1102 maxlength)
1103 if not truncated:
1103 if not truncated:
1104 return text
1104 return text
1105 return utext.encode(encoding.encoding)
1105 return utext.encode(encoding.encoding)
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1106 except (UnicodeDecodeError, UnicodeEncodeError):
1107 return _ellipsis(text, maxlength)[0]
1107 return _ellipsis(text, maxlength)[0]
1108
1108
1109 def bytecount(nbytes):
1109 def bytecount(nbytes):
1110 '''return byte count formatted as readable string, with units'''
1110 '''return byte count formatted as readable string, with units'''
1111
1111
1112 units = (
1112 units = (
1113 (100, 1 << 30, _('%.0f GB')),
1113 (100, 1 << 30, _('%.0f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1114 (10, 1 << 30, _('%.1f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1115 (1, 1 << 30, _('%.2f GB')),
1116 (100, 1 << 20, _('%.0f MB')),
1116 (100, 1 << 20, _('%.0f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1117 (10, 1 << 20, _('%.1f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1118 (1, 1 << 20, _('%.2f MB')),
1119 (100, 1 << 10, _('%.0f KB')),
1119 (100, 1 << 10, _('%.0f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1120 (10, 1 << 10, _('%.1f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1121 (1, 1 << 10, _('%.2f KB')),
1122 (1, 1, _('%.0f bytes')),
1122 (1, 1, _('%.0f bytes')),
1123 )
1123 )
1124
1124
1125 for multiplier, divisor, format in units:
1125 for multiplier, divisor, format in units:
1126 if nbytes >= divisor * multiplier:
1126 if nbytes >= divisor * multiplier:
1127 return format % (nbytes / float(divisor))
1127 return format % (nbytes / float(divisor))
1128 return units[-1][2] % nbytes
1128 return units[-1][2] % nbytes
1129
1129
1130 def uirepr(s):
1130 def uirepr(s):
1131 # Avoid double backslash in Windows path repr()
1131 # Avoid double backslash in Windows path repr()
1132 return repr(s).replace('\\\\', '\\')
1132 return repr(s).replace('\\\\', '\\')
1133
1133
1134 # delay import of textwrap
1134 # delay import of textwrap
1135 def MBTextWrapper(**kwargs):
1135 def MBTextWrapper(**kwargs):
1136 class tw(textwrap.TextWrapper):
1136 class tw(textwrap.TextWrapper):
1137 """
1137 """
1138 Extend TextWrapper for double-width characters.
1138 Extend TextWrapper for double-width characters.
1139
1139
1140 Some Asian characters use two terminal columns instead of one.
1140 Some Asian characters use two terminal columns instead of one.
1141 A good example of this behavior can be seen with u'\u65e5\u672c',
1141 A good example of this behavior can be seen with u'\u65e5\u672c',
1142 the two Japanese characters for "Japan":
1142 the two Japanese characters for "Japan":
1143 len() returns 2, but when printed to a terminal, they eat 4 columns.
1143 len() returns 2, but when printed to a terminal, they eat 4 columns.
1144
1144
1145 (Note that this has nothing to do whatsoever with unicode
1145 (Note that this has nothing to do whatsoever with unicode
1146 representation, or encoding of the underlying string)
1146 representation, or encoding of the underlying string)
1147 """
1147 """
1148 def __init__(self, **kwargs):
1148 def __init__(self, **kwargs):
1149 textwrap.TextWrapper.__init__(self, **kwargs)
1149 textwrap.TextWrapper.__init__(self, **kwargs)
1150
1150
1151 def _cutdown(self, str, space_left):
1151 def _cutdown(self, str, space_left):
1152 l = 0
1152 l = 0
1153 ucstr = unicode(str, encoding.encoding)
1153 ucstr = unicode(str, encoding.encoding)
1154 colwidth = unicodedata.east_asian_width
1154 colwidth = unicodedata.east_asian_width
1155 for i in xrange(len(ucstr)):
1155 for i in xrange(len(ucstr)):
1156 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1156 l += colwidth(ucstr[i]) in 'WFA' and 2 or 1
1157 if space_left < l:
1157 if space_left < l:
1158 return (ucstr[:i].encode(encoding.encoding),
1158 return (ucstr[:i].encode(encoding.encoding),
1159 ucstr[i:].encode(encoding.encoding))
1159 ucstr[i:].encode(encoding.encoding))
1160 return str, ''
1160 return str, ''
1161
1161
1162 # overriding of base class
1162 # overriding of base class
1163 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1163 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1164 space_left = max(width - cur_len, 1)
1164 space_left = max(width - cur_len, 1)
1165
1165
1166 if self.break_long_words:
1166 if self.break_long_words:
1167 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1167 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1168 cur_line.append(cut)
1168 cur_line.append(cut)
1169 reversed_chunks[-1] = res
1169 reversed_chunks[-1] = res
1170 elif not cur_line:
1170 elif not cur_line:
1171 cur_line.append(reversed_chunks.pop())
1171 cur_line.append(reversed_chunks.pop())
1172
1172
1173 global MBTextWrapper
1173 global MBTextWrapper
1174 MBTextWrapper = tw
1174 MBTextWrapper = tw
1175 return tw(**kwargs)
1175 return tw(**kwargs)
1176
1176
1177 def wrap(line, width, initindent='', hangindent=''):
1177 def wrap(line, width, initindent='', hangindent=''):
1178 maxindent = max(len(hangindent), len(initindent))
1178 maxindent = max(len(hangindent), len(initindent))
1179 if width <= maxindent:
1179 if width <= maxindent:
1180 # adjust for weird terminal size
1180 # adjust for weird terminal size
1181 width = max(78, maxindent + 1)
1181 width = max(78, maxindent + 1)
1182 wrapper = MBTextWrapper(width=width,
1182 wrapper = MBTextWrapper(width=width,
1183 initial_indent=initindent,
1183 initial_indent=initindent,
1184 subsequent_indent=hangindent)
1184 subsequent_indent=hangindent)
1185 return wrapper.fill(line)
1185 return wrapper.fill(line)
1186
1186
1187 def iterlines(iterator):
1187 def iterlines(iterator):
1188 for chunk in iterator:
1188 for chunk in iterator:
1189 for line in chunk.splitlines():
1189 for line in chunk.splitlines():
1190 yield line
1190 yield line
1191
1191
1192 def expandpath(path):
1192 def expandpath(path):
1193 return os.path.expanduser(os.path.expandvars(path))
1193 return os.path.expanduser(os.path.expandvars(path))
1194
1194
1195 def hgcmd():
1195 def hgcmd():
1196 """Return the command used to execute current hg
1196 """Return the command used to execute current hg
1197
1197
1198 This is different from hgexecutable() because on Windows we want
1198 This is different from hgexecutable() because on Windows we want
1199 to avoid things opening new shell windows like batch files, so we
1199 to avoid things opening new shell windows like batch files, so we
1200 get either the python call or current executable.
1200 get either the python call or current executable.
1201 """
1201 """
1202 if mainfrozen():
1202 if mainfrozen():
1203 return [sys.executable]
1203 return [sys.executable]
1204 return gethgcmd()
1204 return gethgcmd()
1205
1205
1206 def rundetached(args, condfn):
1206 def rundetached(args, condfn):
1207 """Execute the argument list in a detached process.
1207 """Execute the argument list in a detached process.
1208
1208
1209 condfn is a callable which is called repeatedly and should return
1209 condfn is a callable which is called repeatedly and should return
1210 True once the child process is known to have started successfully.
1210 True once the child process is known to have started successfully.
1211 At this point, the child process PID is returned. If the child
1211 At this point, the child process PID is returned. If the child
1212 process fails to start or finishes before condfn() evaluates to
1212 process fails to start or finishes before condfn() evaluates to
1213 True, return -1.
1213 True, return -1.
1214 """
1214 """
1215 # Windows case is easier because the child process is either
1215 # Windows case is easier because the child process is either
1216 # successfully starting and validating the condition or exiting
1216 # successfully starting and validating the condition or exiting
1217 # on failure. We just poll on its PID. On Unix, if the child
1217 # on failure. We just poll on its PID. On Unix, if the child
1218 # process fails to start, it will be left in a zombie state until
1218 # process fails to start, it will be left in a zombie state until
1219 # the parent wait on it, which we cannot do since we expect a long
1219 # the parent wait on it, which we cannot do since we expect a long
1220 # running process on success. Instead we listen for SIGCHLD telling
1220 # running process on success. Instead we listen for SIGCHLD telling
1221 # us our child process terminated.
1221 # us our child process terminated.
1222 terminated = set()
1222 terminated = set()
1223 def handler(signum, frame):
1223 def handler(signum, frame):
1224 terminated.add(os.wait())
1224 terminated.add(os.wait())
1225 prevhandler = None
1225 prevhandler = None
1226 if hasattr(signal, 'SIGCHLD'):
1226 if hasattr(signal, 'SIGCHLD'):
1227 prevhandler = signal.signal(signal.SIGCHLD, handler)
1227 prevhandler = signal.signal(signal.SIGCHLD, handler)
1228 try:
1228 try:
1229 pid = spawndetached(args)
1229 pid = spawndetached(args)
1230 while not condfn():
1230 while not condfn():
1231 if ((pid in terminated or not testpid(pid))
1231 if ((pid in terminated or not testpid(pid))
1232 and not condfn()):
1232 and not condfn()):
1233 return -1
1233 return -1
1234 time.sleep(0.1)
1234 time.sleep(0.1)
1235 return pid
1235 return pid
1236 finally:
1236 finally:
1237 if prevhandler is not None:
1237 if prevhandler is not None:
1238 signal.signal(signal.SIGCHLD, prevhandler)
1238 signal.signal(signal.SIGCHLD, prevhandler)
1239
1239
1240 try:
1240 try:
1241 any, all = any, all
1241 any, all = any, all
1242 except NameError:
1242 except NameError:
1243 def any(iterable):
1243 def any(iterable):
1244 for i in iterable:
1244 for i in iterable:
1245 if i:
1245 if i:
1246 return True
1246 return True
1247 return False
1247 return False
1248
1248
1249 def all(iterable):
1249 def all(iterable):
1250 for i in iterable:
1250 for i in iterable:
1251 if not i:
1251 if not i:
1252 return False
1252 return False
1253 return True
1253 return True
1254
1254
1255 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1255 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1256 """Return the result of interpolating items in the mapping into string s.
1256 """Return the result of interpolating items in the mapping into string s.
1257
1257
1258 prefix is a single character string, or a two character string with
1258 prefix is a single character string, or a two character string with
1259 a backslash as the first character if the prefix needs to be escaped in
1259 a backslash as the first character if the prefix needs to be escaped in
1260 a regular expression.
1260 a regular expression.
1261
1261
1262 fn is an optional function that will be applied to the replacement text
1262 fn is an optional function that will be applied to the replacement text
1263 just before replacement.
1263 just before replacement.
1264
1264
1265 escape_prefix is an optional flag that allows using doubled prefix for
1265 escape_prefix is an optional flag that allows using doubled prefix for
1266 its escaping.
1266 its escaping.
1267 """
1267 """
1268 fn = fn or (lambda s: s)
1268 fn = fn or (lambda s: s)
1269 patterns = '|'.join(mapping.keys())
1269 patterns = '|'.join(mapping.keys())
1270 if escape_prefix:
1270 if escape_prefix:
1271 patterns += '|' + prefix
1271 patterns += '|' + prefix
1272 if len(prefix) > 1:
1272 if len(prefix) > 1:
1273 prefix_char = prefix[1:]
1273 prefix_char = prefix[1:]
1274 else:
1274 else:
1275 prefix_char = prefix
1275 prefix_char = prefix
1276 mapping[prefix_char] = prefix_char
1276 mapping[prefix_char] = prefix_char
1277 r = re.compile(r'%s(%s)' % (prefix, patterns))
1277 r = re.compile(r'%s(%s)' % (prefix, patterns))
1278 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1278 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1279
1279
1280 def getport(port):
1280 def getport(port):
1281 """Return the port for a given network service.
1281 """Return the port for a given network service.
1282
1282
1283 If port is an integer, it's returned as is. If it's a string, it's
1283 If port is an integer, it's returned as is. If it's a string, it's
1284 looked up using socket.getservbyname(). If there's no matching
1284 looked up using socket.getservbyname(). If there's no matching
1285 service, util.Abort is raised.
1285 service, util.Abort is raised.
1286 """
1286 """
1287 try:
1287 try:
1288 return int(port)
1288 return int(port)
1289 except ValueError:
1289 except ValueError:
1290 pass
1290 pass
1291
1291
1292 try:
1292 try:
1293 return socket.getservbyname(port)
1293 return socket.getservbyname(port)
1294 except socket.error:
1294 except socket.error:
1295 raise Abort(_("no port number associated with service '%s'") % port)
1295 raise Abort(_("no port number associated with service '%s'") % port)
1296
1296
1297 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1297 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1298 '0': False, 'no': False, 'false': False, 'off': False,
1298 '0': False, 'no': False, 'false': False, 'off': False,
1299 'never': False}
1299 'never': False}
1300
1300
1301 def parsebool(s):
1301 def parsebool(s):
1302 """Parse s into a boolean.
1302 """Parse s into a boolean.
1303
1303
1304 If s is not a valid boolean, returns None.
1304 If s is not a valid boolean, returns None.
1305 """
1305 """
1306 return _booleans.get(s.lower(), None)
1306 return _booleans.get(s.lower(), None)
1307
1307
1308 _hexdig = '0123456789ABCDEFabcdef'
1308 _hexdig = '0123456789ABCDEFabcdef'
1309 _hextochr = dict((a + b, chr(int(a + b, 16)))
1309 _hextochr = dict((a + b, chr(int(a + b, 16)))
1310 for a in _hexdig for b in _hexdig)
1310 for a in _hexdig for b in _hexdig)
1311
1311
1312 def _urlunquote(s):
1312 def _urlunquote(s):
1313 """unquote('abc%20def') -> 'abc def'."""
1313 """unquote('abc%20def') -> 'abc def'."""
1314 res = s.split('%')
1314 res = s.split('%')
1315 # fastpath
1315 # fastpath
1316 if len(res) == 1:
1316 if len(res) == 1:
1317 return s
1317 return s
1318 s = res[0]
1318 s = res[0]
1319 for item in res[1:]:
1319 for item in res[1:]:
1320 try:
1320 try:
1321 s += _hextochr[item[:2]] + item[2:]
1321 s += _hextochr[item[:2]] + item[2:]
1322 except KeyError:
1322 except KeyError:
1323 s += '%' + item
1323 s += '%' + item
1324 except UnicodeDecodeError:
1324 except UnicodeDecodeError:
1325 s += unichr(int(item[:2], 16)) + item[2:]
1325 s += unichr(int(item[:2], 16)) + item[2:]
1326 return s
1326 return s
1327
1327
1328 class url(object):
1328 class url(object):
1329 r"""Reliable URL parser.
1329 r"""Reliable URL parser.
1330
1330
1331 This parses URLs and provides attributes for the following
1331 This parses URLs and provides attributes for the following
1332 components:
1332 components:
1333
1333
1334 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1334 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1335
1335
1336 Missing components are set to None. The only exception is
1336 Missing components are set to None. The only exception is
1337 fragment, which is set to '' if present but empty.
1337 fragment, which is set to '' if present but empty.
1338
1338
1339 If parsefragment is False, fragment is included in query. If
1339 If parsefragment is False, fragment is included in query. If
1340 parsequery is False, query is included in path. If both are
1340 parsequery is False, query is included in path. If both are
1341 False, both fragment and query are included in path.
1341 False, both fragment and query are included in path.
1342
1342
1343 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1343 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1344
1344
1345 Note that for backward compatibility reasons, bundle URLs do not
1345 Note that for backward compatibility reasons, bundle URLs do not
1346 take host names. That means 'bundle://../' has a path of '../'.
1346 take host names. That means 'bundle://../' has a path of '../'.
1347
1347
1348 Examples:
1348 Examples:
1349
1349
1350 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1350 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1351 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1351 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1352 >>> url('ssh://[::1]:2200//home/joe/repo')
1352 >>> url('ssh://[::1]:2200//home/joe/repo')
1353 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1353 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1354 >>> url('file:///home/joe/repo')
1354 >>> url('file:///home/joe/repo')
1355 <url scheme: 'file', path: '/home/joe/repo'>
1355 <url scheme: 'file', path: '/home/joe/repo'>
1356 >>> url('bundle:foo')
1356 >>> url('bundle:foo')
1357 <url scheme: 'bundle', path: 'foo'>
1357 <url scheme: 'bundle', path: 'foo'>
1358 >>> url('bundle://../foo')
1358 >>> url('bundle://../foo')
1359 <url scheme: 'bundle', path: '../foo'>
1359 <url scheme: 'bundle', path: '../foo'>
1360 >>> url(r'c:\foo\bar')
1360 >>> url(r'c:\foo\bar')
1361 <url path: 'c:\\foo\\bar'>
1361 <url path: 'c:\\foo\\bar'>
1362
1362
1363 Authentication credentials:
1363 Authentication credentials:
1364
1364
1365 >>> url('ssh://joe:xyz@x/repo')
1365 >>> url('ssh://joe:xyz@x/repo')
1366 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1366 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1367 >>> url('ssh://joe@x/repo')
1367 >>> url('ssh://joe@x/repo')
1368 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1368 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1369
1369
1370 Query strings and fragments:
1370 Query strings and fragments:
1371
1371
1372 >>> url('http://host/a?b#c')
1372 >>> url('http://host/a?b#c')
1373 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1373 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1374 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1374 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1375 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1375 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1376 """
1376 """
1377
1377
1378 _safechars = "!~*'()+"
1378 _safechars = "!~*'()+"
1379 _safepchars = "/!~*'()+"
1379 _safepchars = "/!~*'()+"
1380 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1380 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1381
1381
1382 def __init__(self, path, parsequery=True, parsefragment=True):
1382 def __init__(self, path, parsequery=True, parsefragment=True):
1383 # We slowly chomp away at path until we have only the path left
1383 # We slowly chomp away at path until we have only the path left
1384 self.scheme = self.user = self.passwd = self.host = None
1384 self.scheme = self.user = self.passwd = self.host = None
1385 self.port = self.path = self.query = self.fragment = None
1385 self.port = self.path = self.query = self.fragment = None
1386 self._localpath = True
1386 self._localpath = True
1387 self._hostport = ''
1387 self._hostport = ''
1388 self._origpath = path
1388 self._origpath = path
1389
1389
1390 # special case for Windows drive letters
1390 # special case for Windows drive letters
1391 if hasdriveletter(path):
1391 if hasdriveletter(path):
1392 self.path = path
1392 self.path = path
1393 return
1393 return
1394
1394
1395 # For compatibility reasons, we can't handle bundle paths as
1395 # For compatibility reasons, we can't handle bundle paths as
1396 # normal URLS
1396 # normal URLS
1397 if path.startswith('bundle:'):
1397 if path.startswith('bundle:'):
1398 self.scheme = 'bundle'
1398 self.scheme = 'bundle'
1399 path = path[7:]
1399 path = path[7:]
1400 if path.startswith('//'):
1400 if path.startswith('//'):
1401 path = path[2:]
1401 path = path[2:]
1402 self.path = path
1402 self.path = path
1403 return
1403 return
1404
1404
1405 if self._matchscheme(path):
1405 if self._matchscheme(path):
1406 parts = path.split(':', 1)
1406 parts = path.split(':', 1)
1407 if parts[0]:
1407 if parts[0]:
1408 self.scheme, path = parts
1408 self.scheme, path = parts
1409 self._localpath = False
1409 self._localpath = False
1410
1410
1411 if not path:
1411 if not path:
1412 path = None
1412 path = None
1413 if self._localpath:
1413 if self._localpath:
1414 self.path = ''
1414 self.path = ''
1415 return
1415 return
1416 else:
1416 else:
1417 if parsefragment and '#' in path:
1417 if parsefragment and '#' in path:
1418 path, self.fragment = path.split('#', 1)
1418 path, self.fragment = path.split('#', 1)
1419 if not path:
1419 if not path:
1420 path = None
1420 path = None
1421 if self._localpath:
1421 if self._localpath:
1422 self.path = path
1422 self.path = path
1423 return
1423 return
1424
1424
1425 if parsequery and '?' in path:
1425 if parsequery and '?' in path:
1426 path, self.query = path.split('?', 1)
1426 path, self.query = path.split('?', 1)
1427 if not path:
1427 if not path:
1428 path = None
1428 path = None
1429 if not self.query:
1429 if not self.query:
1430 self.query = None
1430 self.query = None
1431
1431
1432 # // is required to specify a host/authority
1432 # // is required to specify a host/authority
1433 if path and path.startswith('//'):
1433 if path and path.startswith('//'):
1434 parts = path[2:].split('/', 1)
1434 parts = path[2:].split('/', 1)
1435 if len(parts) > 1:
1435 if len(parts) > 1:
1436 self.host, path = parts
1436 self.host, path = parts
1437 path = path
1437 path = path
1438 else:
1438 else:
1439 self.host = parts[0]
1439 self.host = parts[0]
1440 path = None
1440 path = None
1441 if not self.host:
1441 if not self.host:
1442 self.host = None
1442 self.host = None
1443 if path:
1443 if path:
1444 path = '/' + path
1444 path = '/' + path
1445
1445
1446 if self.host and '@' in self.host:
1446 if self.host and '@' in self.host:
1447 self.user, self.host = self.host.rsplit('@', 1)
1447 self.user, self.host = self.host.rsplit('@', 1)
1448 if ':' in self.user:
1448 if ':' in self.user:
1449 self.user, self.passwd = self.user.split(':', 1)
1449 self.user, self.passwd = self.user.split(':', 1)
1450 if not self.host:
1450 if not self.host:
1451 self.host = None
1451 self.host = None
1452
1452
1453 # Don't split on colons in IPv6 addresses without ports
1453 # Don't split on colons in IPv6 addresses without ports
1454 if (self.host and ':' in self.host and
1454 if (self.host and ':' in self.host and
1455 not (self.host.startswith('[') and self.host.endswith(']'))):
1455 not (self.host.startswith('[') and self.host.endswith(']'))):
1456 self._hostport = self.host
1456 self._hostport = self.host
1457 self.host, self.port = self.host.rsplit(':', 1)
1457 self.host, self.port = self.host.rsplit(':', 1)
1458 if not self.host:
1458 if not self.host:
1459 self.host = None
1459 self.host = None
1460
1460
1461 if (self.host and self.scheme == 'file' and
1461 if (self.host and self.scheme == 'file' and
1462 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1462 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1463 raise Abort(_('file:// URLs can only refer to localhost'))
1463 raise Abort(_('file:// URLs can only refer to localhost'))
1464
1464
1465 self.path = path
1465 self.path = path
1466
1466
1467 for a in ('user', 'passwd', 'host', 'port',
1467 for a in ('user', 'passwd', 'host', 'port',
1468 'path', 'query', 'fragment'):
1468 'path', 'query', 'fragment'):
1469 v = getattr(self, a)
1469 v = getattr(self, a)
1470 if v is not None:
1470 if v is not None:
1471 setattr(self, a, _urlunquote(v))
1471 setattr(self, a, _urlunquote(v))
1472
1472
1473 def __repr__(self):
1473 def __repr__(self):
1474 attrs = []
1474 attrs = []
1475 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1475 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1476 'query', 'fragment'):
1476 'query', 'fragment'):
1477 v = getattr(self, a)
1477 v = getattr(self, a)
1478 if v is not None:
1478 if v is not None:
1479 attrs.append('%s: %r' % (a, v))
1479 attrs.append('%s: %r' % (a, v))
1480 return '<url %s>' % ', '.join(attrs)
1480 return '<url %s>' % ', '.join(attrs)
1481
1481
1482 def __str__(self):
1482 def __str__(self):
1483 r"""Join the URL's components back into a URL string.
1483 r"""Join the URL's components back into a URL string.
1484
1484
1485 Examples:
1485 Examples:
1486
1486
1487 >>> str(url('http://user:pw@host:80/?foo#bar'))
1487 >>> str(url('http://user:pw@host:80/?foo#bar'))
1488 'http://user:pw@host:80/?foo#bar'
1488 'http://user:pw@host:80/?foo#bar'
1489 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1489 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1490 'ssh://user:pw@[::1]:2200//home/joe#'
1490 'ssh://user:pw@[::1]:2200//home/joe#'
1491 >>> str(url('http://localhost:80//'))
1491 >>> str(url('http://localhost:80//'))
1492 'http://localhost:80//'
1492 'http://localhost:80//'
1493 >>> str(url('http://localhost:80/'))
1493 >>> str(url('http://localhost:80/'))
1494 'http://localhost:80/'
1494 'http://localhost:80/'
1495 >>> str(url('http://localhost:80'))
1495 >>> str(url('http://localhost:80'))
1496 'http://localhost:80/'
1496 'http://localhost:80/'
1497 >>> str(url('bundle:foo'))
1497 >>> str(url('bundle:foo'))
1498 'bundle:foo'
1498 'bundle:foo'
1499 >>> str(url('bundle://../foo'))
1499 >>> str(url('bundle://../foo'))
1500 'bundle:../foo'
1500 'bundle:../foo'
1501 >>> str(url('path'))
1501 >>> str(url('path'))
1502 'path'
1502 'path'
1503 >>> str(url('file:///tmp/foo/bar'))
1503 >>> str(url('file:///tmp/foo/bar'))
1504 'file:///tmp/foo/bar'
1504 'file:///tmp/foo/bar'
1505 >>> print url(r'bundle:foo\bar')
1505 >>> print url(r'bundle:foo\bar')
1506 bundle:foo\bar
1506 bundle:foo\bar
1507 """
1507 """
1508 if self._localpath:
1508 if self._localpath:
1509 s = self.path
1509 s = self.path
1510 if self.scheme == 'bundle':
1510 if self.scheme == 'bundle':
1511 s = 'bundle:' + s
1511 s = 'bundle:' + s
1512 if self.fragment:
1512 if self.fragment:
1513 s += '#' + self.fragment
1513 s += '#' + self.fragment
1514 return s
1514 return s
1515
1515
1516 s = self.scheme + ':'
1516 s = self.scheme + ':'
1517 if self.user or self.passwd or self.host:
1517 if self.user or self.passwd or self.host:
1518 s += '//'
1518 s += '//'
1519 elif self.scheme and (not self.path or self.path.startswith('/')):
1519 elif self.scheme and (not self.path or self.path.startswith('/')):
1520 s += '//'
1520 s += '//'
1521 if self.user:
1521 if self.user:
1522 s += urllib.quote(self.user, safe=self._safechars)
1522 s += urllib.quote(self.user, safe=self._safechars)
1523 if self.passwd:
1523 if self.passwd:
1524 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1524 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1525 if self.user or self.passwd:
1525 if self.user or self.passwd:
1526 s += '@'
1526 s += '@'
1527 if self.host:
1527 if self.host:
1528 if not (self.host.startswith('[') and self.host.endswith(']')):
1528 if not (self.host.startswith('[') and self.host.endswith(']')):
1529 s += urllib.quote(self.host)
1529 s += urllib.quote(self.host)
1530 else:
1530 else:
1531 s += self.host
1531 s += self.host
1532 if self.port:
1532 if self.port:
1533 s += ':' + urllib.quote(self.port)
1533 s += ':' + urllib.quote(self.port)
1534 if self.host:
1534 if self.host:
1535 s += '/'
1535 s += '/'
1536 if self.path:
1536 if self.path:
1537 s += urllib.quote(self.path, safe=self._safepchars)
1537 s += urllib.quote(self.path, safe=self._safepchars)
1538 if self.query:
1538 if self.query:
1539 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1539 s += '?' + urllib.quote(self.query, safe=self._safepchars)
1540 if self.fragment is not None:
1540 if self.fragment is not None:
1541 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1541 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1542 return s
1542 return s
1543
1543
1544 def authinfo(self):
1544 def authinfo(self):
1545 user, passwd = self.user, self.passwd
1545 user, passwd = self.user, self.passwd
1546 try:
1546 try:
1547 self.user, self.passwd = None, None
1547 self.user, self.passwd = None, None
1548 s = str(self)
1548 s = str(self)
1549 finally:
1549 finally:
1550 self.user, self.passwd = user, passwd
1550 self.user, self.passwd = user, passwd
1551 if not self.user:
1551 if not self.user:
1552 return (s, None)
1552 return (s, None)
1553 return (s, (None, (str(self), self.host),
1553 return (s, (None, (str(self), self.host),
1554 self.user, self.passwd or ''))
1554 self.user, self.passwd or ''))
1555
1555
1556 def localpath(self):
1556 def localpath(self):
1557 if self.scheme == 'file' or self.scheme == 'bundle':
1557 if self.scheme == 'file' or self.scheme == 'bundle':
1558 path = self.path or '/'
1558 path = self.path or '/'
1559 # For Windows, we need to promote hosts containing drive
1559 # For Windows, we need to promote hosts containing drive
1560 # letters to paths with drive letters.
1560 # letters to paths with drive letters.
1561 if hasdriveletter(self._hostport):
1561 if hasdriveletter(self._hostport):
1562 path = self._hostport + '/' + self.path
1562 path = self._hostport + '/' + self.path
1563 elif self.host is not None and self.path:
1563 elif self.host is not None and self.path:
1564 path = '/' + path
1564 path = '/' + path
1565 # We also need to handle the case of file:///C:/, which
1565 # We also need to handle the case of file:///C:/, which
1566 # should return C:/, not /C:/.
1566 # should return C:/, not /C:/.
1567 elif hasdriveletter(path):
1567 elif hasdriveletter(path):
1568 # Strip leading slash from paths with drive names
1568 # Strip leading slash from paths with drive names
1569 return path[1:]
1569 return path[1:]
1570 return path
1570 return path
1571 return self._origpath
1571 return self._origpath
1572
1572
1573 def hasscheme(path):
1573 def hasscheme(path):
1574 return bool(url(path).scheme)
1574 return bool(url(path).scheme)
1575
1575
1576 def hasdriveletter(path):
1576 def hasdriveletter(path):
1577 return path[1:2] == ':' and path[0:1].isalpha()
1577 return path[1:2] == ':' and path[0:1].isalpha()
1578
1578
1579 def localpath(path):
1579 def localpath(path):
1580 return url(path, parsequery=False, parsefragment=False).localpath()
1580 return url(path, parsequery=False, parsefragment=False).localpath()
1581
1581
1582 def hidepassword(u):
1582 def hidepassword(u):
1583 '''hide user credential in a url string'''
1583 '''hide user credential in a url string'''
1584 u = url(u)
1584 u = url(u)
1585 if u.passwd:
1585 if u.passwd:
1586 u.passwd = '***'
1586 u.passwd = '***'
1587 return str(u)
1587 return str(u)
1588
1588
1589 def removeauth(u):
1589 def removeauth(u):
1590 '''remove all authentication information from a url string'''
1590 '''remove all authentication information from a url string'''
1591 u = url(u)
1591 u = url(u)
1592 u.user = u.passwd = None
1592 u.user = u.passwd = None
1593 return str(u)
1593 return str(u)
1594
1595 def isatty(fd):
1596 try:
1597 return fd.isatty()
1598 except AttributeError:
1599 return False
General Comments 0
You need to be logged in to leave comments. Login now