##// END OF EJS Templates
merge with crew-stable
Alexis S. L. Carvalho -
r3906:1063a631 merge default
parent child Browse files
Show More
@@ -1,290 +1,290
1 # templater.py - template expansion for output
1 # templater.py - template expansion for output
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 from node import *
9 from node import *
10 import cgi, re, sys, os, time, urllib, util, textwrap
10 import cgi, re, sys, os, time, urllib, util, textwrap
11
11
12 def parsestring(s, quoted=True):
12 def parsestring(s, quoted=True):
13 '''parse a string using simple c-like syntax.
13 '''parse a string using simple c-like syntax.
14 string must be in quotes if quoted is True.'''
14 string must be in quotes if quoted is True.'''
15 if quoted:
15 if quoted:
16 if len(s) < 2 or s[0] != s[-1]:
16 if len(s) < 2 or s[0] != s[-1]:
17 raise SyntaxError(_('unmatched quotes'))
17 raise SyntaxError(_('unmatched quotes'))
18 return s[1:-1].decode('string_escape')
18 return s[1:-1].decode('string_escape')
19
19
20 return s.decode('string_escape')
20 return s.decode('string_escape')
21
21
22 class templater(object):
22 class templater(object):
23 '''template expansion engine.
23 '''template expansion engine.
24
24
25 template expansion works like this. a map file contains key=value
25 template expansion works like this. a map file contains key=value
26 pairs. if value is quoted, it is treated as string. otherwise, it
26 pairs. if value is quoted, it is treated as string. otherwise, it
27 is treated as name of template file.
27 is treated as name of template file.
28
28
29 templater is asked to expand a key in map. it looks up key, and
29 templater is asked to expand a key in map. it looks up key, and
30 looks for atrings like this: {foo}. it expands {foo} by looking up
30 looks for atrings like this: {foo}. it expands {foo} by looking up
31 foo in map, and substituting it. expansion is recursive: it stops
31 foo in map, and substituting it. expansion is recursive: it stops
32 when there is no more {foo} to replace.
32 when there is no more {foo} to replace.
33
33
34 expansion also allows formatting and filtering.
34 expansion also allows formatting and filtering.
35
35
36 format uses key to expand each item in list. syntax is
36 format uses key to expand each item in list. syntax is
37 {key%format}.
37 {key%format}.
38
38
39 filter uses function to transform value. syntax is
39 filter uses function to transform value. syntax is
40 {key|filter1|filter2|...}.'''
40 {key|filter1|filter2|...}.'''
41
41
42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
42 template_re = re.compile(r"(?:(?:#(?=[\w\|%]+#))|(?:{(?=[\w\|%]+})))"
43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
43 r"(\w+)(?:(?:%(\w+))|((?:\|\w+)*))[#}]")
44
44
45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
45 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
46 '''set up template engine.
46 '''set up template engine.
47 mapfile is name of file to read map definitions from.
47 mapfile is name of file to read map definitions from.
48 filters is dict of functions. each transforms a value into another.
48 filters is dict of functions. each transforms a value into another.
49 defaults is dict of default map definitions.'''
49 defaults is dict of default map definitions.'''
50 self.mapfile = mapfile or 'template'
50 self.mapfile = mapfile or 'template'
51 self.cache = cache.copy()
51 self.cache = cache.copy()
52 self.map = {}
52 self.map = {}
53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
53 self.base = (mapfile and os.path.dirname(mapfile)) or ''
54 self.filters = filters
54 self.filters = filters
55 self.defaults = defaults
55 self.defaults = defaults
56
56
57 if not mapfile:
57 if not mapfile:
58 return
58 return
59 i = 0
59 i = 0
60 for l in file(mapfile):
60 for l in file(mapfile):
61 l = l.strip()
61 l = l.strip()
62 i += 1
62 i += 1
63 if not l or l[0] in '#;': continue
63 if not l or l[0] in '#;': continue
64 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
64 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
65 if m:
65 if m:
66 key, val = m.groups()
66 key, val = m.groups()
67 if val[0] in "'\"":
67 if val[0] in "'\"":
68 try:
68 try:
69 self.cache[key] = parsestring(val)
69 self.cache[key] = parsestring(val)
70 except SyntaxError, inst:
70 except SyntaxError, inst:
71 raise SyntaxError('%s:%s: %s' %
71 raise SyntaxError('%s:%s: %s' %
72 (mapfile, i, inst.args[0]))
72 (mapfile, i, inst.args[0]))
73 else:
73 else:
74 self.map[key] = os.path.join(self.base, val)
74 self.map[key] = os.path.join(self.base, val)
75 else:
75 else:
76 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
76 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
77
77
78 def __contains__(self, key):
78 def __contains__(self, key):
79 return key in self.cache or key in self.map
79 return key in self.cache or key in self.map
80
80
81 def __call__(self, t, **map):
81 def __call__(self, t, **map):
82 '''perform expansion.
82 '''perform expansion.
83 t is name of map element to expand.
83 t is name of map element to expand.
84 map is added elements to use during expansion.'''
84 map is added elements to use during expansion.'''
85 if not self.cache.has_key(t):
85 if not self.cache.has_key(t):
86 try:
86 try:
87 self.cache[t] = file(self.map[t]).read()
87 self.cache[t] = file(self.map[t]).read()
88 except IOError, inst:
88 except IOError, inst:
89 raise IOError(inst.args[0], _('template file %s: %s') %
89 raise IOError(inst.args[0], _('template file %s: %s') %
90 (self.map[t], inst.args[1]))
90 (self.map[t], inst.args[1]))
91 tmpl = self.cache[t]
91 tmpl = self.cache[t]
92
92
93 while tmpl:
93 while tmpl:
94 m = self.template_re.search(tmpl)
94 m = self.template_re.search(tmpl)
95 if not m:
95 if not m:
96 yield tmpl
96 yield tmpl
97 break
97 break
98
98
99 start, end = m.span(0)
99 start, end = m.span(0)
100 key, format, fl = m.groups()
100 key, format, fl = m.groups()
101
101
102 if start:
102 if start:
103 yield tmpl[:start]
103 yield tmpl[:start]
104 tmpl = tmpl[end:]
104 tmpl = tmpl[end:]
105
105
106 if key in map:
106 if key in map:
107 v = map[key]
107 v = map[key]
108 else:
108 else:
109 v = self.defaults.get(key, "")
109 v = self.defaults.get(key, "")
110 if callable(v):
110 if callable(v):
111 v = v(**map)
111 v = v(**map)
112 if format:
112 if format:
113 if not hasattr(v, '__iter__'):
113 if not hasattr(v, '__iter__'):
114 raise SyntaxError(_("Error expanding '%s%s'")
114 raise SyntaxError(_("Error expanding '%s%s'")
115 % (key, format))
115 % (key, format))
116 lm = map.copy()
116 lm = map.copy()
117 for i in v:
117 for i in v:
118 lm.update(i)
118 lm.update(i)
119 yield self(format, **lm)
119 yield self(format, **lm)
120 else:
120 else:
121 if fl:
121 if fl:
122 for f in fl.split("|")[1:]:
122 for f in fl.split("|")[1:]:
123 v = self.filters[f](v)
123 v = self.filters[f](v)
124 yield v
124 yield v
125
125
126 agescales = [("second", 1),
126 agescales = [("second", 1),
127 ("minute", 60),
127 ("minute", 60),
128 ("hour", 3600),
128 ("hour", 3600),
129 ("day", 3600 * 24),
129 ("day", 3600 * 24),
130 ("week", 3600 * 24 * 7),
130 ("week", 3600 * 24 * 7),
131 ("month", 3600 * 24 * 30),
131 ("month", 3600 * 24 * 30),
132 ("year", 3600 * 24 * 365)]
132 ("year", 3600 * 24 * 365)]
133
133
134 agescales.reverse()
134 agescales.reverse()
135
135
136 def age(date):
136 def age(date):
137 '''turn a (timestamp, tzoff) tuple into an age string.'''
137 '''turn a (timestamp, tzoff) tuple into an age string.'''
138
138
139 def plural(t, c):
139 def plural(t, c):
140 if c == 1:
140 if c == 1:
141 return t
141 return t
142 return t + "s"
142 return t + "s"
143 def fmt(t, c):
143 def fmt(t, c):
144 return "%d %s" % (c, plural(t, c))
144 return "%d %s" % (c, plural(t, c))
145
145
146 now = time.time()
146 now = time.time()
147 then = date[0]
147 then = date[0]
148 delta = max(1, int(now - then))
148 delta = max(1, int(now - then))
149
149
150 for t, s in agescales:
150 for t, s in agescales:
151 n = delta / s
151 n = delta / s
152 if n >= 2 or s == 1:
152 if n >= 2 or s == 1:
153 return fmt(t, n)
153 return fmt(t, n)
154
154
155 def stringify(thing):
155 def stringify(thing):
156 '''turn nested template iterator into string.'''
156 '''turn nested template iterator into string.'''
157 if hasattr(thing, '__iter__'):
157 if hasattr(thing, '__iter__'):
158 return "".join([stringify(t) for t in thing if t is not None])
158 return "".join([stringify(t) for t in thing if t is not None])
159 return str(thing)
159 return str(thing)
160
160
161 para_re = None
161 para_re = None
162 space_re = None
162 space_re = None
163
163
164 def fill(text, width):
164 def fill(text, width):
165 '''fill many paragraphs.'''
165 '''fill many paragraphs.'''
166 global para_re, space_re
166 global para_re, space_re
167 if para_re is None:
167 if para_re is None:
168 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
168 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
169 space_re = re.compile(r' +')
169 space_re = re.compile(r' +')
170
170
171 def findparas():
171 def findparas():
172 start = 0
172 start = 0
173 while True:
173 while True:
174 m = para_re.search(text, start)
174 m = para_re.search(text, start)
175 if not m:
175 if not m:
176 w = len(text)
176 w = len(text)
177 while w > start and text[w-1].isspace(): w -= 1
177 while w > start and text[w-1].isspace(): w -= 1
178 yield text[start:w], text[w:]
178 yield text[start:w], text[w:]
179 break
179 break
180 yield text[start:m.start(0)], m.group(1)
180 yield text[start:m.start(0)], m.group(1)
181 start = m.end(1)
181 start = m.end(1)
182
182
183 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
183 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
184 for para, rest in findparas()])
184 for para, rest in findparas()])
185
185
186 def firstline(text):
186 def firstline(text):
187 '''return the first line of text'''
187 '''return the first line of text'''
188 try:
188 try:
189 return text.splitlines(1)[0].rstrip('\r\n')
189 return text.splitlines(1)[0].rstrip('\r\n')
190 except IndexError:
190 except IndexError:
191 return ''
191 return ''
192
192
193 def isodate(date):
193 def isodate(date):
194 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
194 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
195 return util.datestr(date, format='%Y-%m-%d %H:%M')
195 return util.datestr(date, format='%Y-%m-%d %H:%M')
196
196
197 def hgdate(date):
197 def hgdate(date):
198 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
198 '''turn a (timestamp, tzoff) tuple into an hg cset timestamp.'''
199 return "%d %d" % date
199 return "%d %d" % date
200
200
201 def nl2br(text):
201 def nl2br(text):
202 '''replace raw newlines with xhtml line breaks.'''
202 '''replace raw newlines with xhtml line breaks.'''
203 return text.replace('\n', '<br/>\n')
203 return text.replace('\n', '<br/>\n')
204
204
205 def obfuscate(text):
205 def obfuscate(text):
206 text = unicode(text, 'utf-8', 'replace')
206 text = unicode(text, util._encoding, 'replace')
207 return ''.join(['&#%d;' % ord(c) for c in text])
207 return ''.join(['&#%d;' % ord(c) for c in text])
208
208
209 def domain(author):
209 def domain(author):
210 '''get domain of author, or empty string if none.'''
210 '''get domain of author, or empty string if none.'''
211 f = author.find('@')
211 f = author.find('@')
212 if f == -1: return ''
212 if f == -1: return ''
213 author = author[f+1:]
213 author = author[f+1:]
214 f = author.find('>')
214 f = author.find('>')
215 if f >= 0: author = author[:f]
215 if f >= 0: author = author[:f]
216 return author
216 return author
217
217
218 def email(author):
218 def email(author):
219 '''get email of author.'''
219 '''get email of author.'''
220 r = author.find('>')
220 r = author.find('>')
221 if r == -1: r = None
221 if r == -1: r = None
222 return author[author.find('<')+1:r]
222 return author[author.find('<')+1:r]
223
223
224 def person(author):
224 def person(author):
225 '''get name of author, or else username.'''
225 '''get name of author, or else username.'''
226 f = author.find('<')
226 f = author.find('<')
227 if f == -1: return util.shortuser(author)
227 if f == -1: return util.shortuser(author)
228 return author[:f].rstrip()
228 return author[:f].rstrip()
229
229
230 def shortdate(date):
230 def shortdate(date):
231 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
231 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
232 return util.datestr(date, format='%Y-%m-%d', timezone=False)
232 return util.datestr(date, format='%Y-%m-%d', timezone=False)
233
233
234 def indent(text, prefix):
234 def indent(text, prefix):
235 '''indent each non-empty line of text after first with prefix.'''
235 '''indent each non-empty line of text after first with prefix.'''
236 lines = text.splitlines()
236 lines = text.splitlines()
237 num_lines = len(lines)
237 num_lines = len(lines)
238 def indenter():
238 def indenter():
239 for i in xrange(num_lines):
239 for i in xrange(num_lines):
240 l = lines[i]
240 l = lines[i]
241 if i and l.strip():
241 if i and l.strip():
242 yield prefix
242 yield prefix
243 yield l
243 yield l
244 if i < num_lines - 1 or text.endswith('\n'):
244 if i < num_lines - 1 or text.endswith('\n'):
245 yield '\n'
245 yield '\n'
246 return "".join(indenter())
246 return "".join(indenter())
247
247
248 common_filters = {
248 common_filters = {
249 "addbreaks": nl2br,
249 "addbreaks": nl2br,
250 "basename": os.path.basename,
250 "basename": os.path.basename,
251 "age": age,
251 "age": age,
252 "date": lambda x: util.datestr(x),
252 "date": lambda x: util.datestr(x),
253 "domain": domain,
253 "domain": domain,
254 "email": email,
254 "email": email,
255 "escape": lambda x: cgi.escape(x, True),
255 "escape": lambda x: cgi.escape(x, True),
256 "fill68": lambda x: fill(x, width=68),
256 "fill68": lambda x: fill(x, width=68),
257 "fill76": lambda x: fill(x, width=76),
257 "fill76": lambda x: fill(x, width=76),
258 "firstline": firstline,
258 "firstline": firstline,
259 "tabindent": lambda x: indent(x, '\t'),
259 "tabindent": lambda x: indent(x, '\t'),
260 "hgdate": hgdate,
260 "hgdate": hgdate,
261 "isodate": isodate,
261 "isodate": isodate,
262 "obfuscate": obfuscate,
262 "obfuscate": obfuscate,
263 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
263 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
264 "person": person,
264 "person": person,
265 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
265 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
266 "short": lambda x: x[:12],
266 "short": lambda x: x[:12],
267 "shortdate": shortdate,
267 "shortdate": shortdate,
268 "stringify": stringify,
268 "stringify": stringify,
269 "strip": lambda x: x.strip(),
269 "strip": lambda x: x.strip(),
270 "urlescape": lambda x: urllib.quote(x),
270 "urlescape": lambda x: urllib.quote(x),
271 "user": lambda x: util.shortuser(x),
271 "user": lambda x: util.shortuser(x),
272 "stringescape": lambda x: x.encode('string_escape'),
272 "stringescape": lambda x: x.encode('string_escape'),
273 }
273 }
274
274
275 def templatepath(name=None):
275 def templatepath(name=None):
276 '''return location of template file or directory (if no name).
276 '''return location of template file or directory (if no name).
277 returns None if not found.'''
277 returns None if not found.'''
278
278
279 # executable version (py2exe) doesn't support __file__
279 # executable version (py2exe) doesn't support __file__
280 if hasattr(sys, 'frozen'):
280 if hasattr(sys, 'frozen'):
281 module = sys.executable
281 module = sys.executable
282 else:
282 else:
283 module = __file__
283 module = __file__
284 for f in 'templates', '../templates':
284 for f in 'templates', '../templates':
285 fl = f.split('/')
285 fl = f.split('/')
286 if name: fl.append(name)
286 if name: fl.append(name)
287 p = os.path.join(os.path.dirname(module), *fl)
287 p = os.path.join(os.path.dirname(module), *fl)
288 if (name and os.path.exists(p)) or os.path.isdir(p):
288 if (name and os.path.exists(p)) or os.path.isdir(p):
289 return os.path.normpath(p)
289 return os.path.normpath(p)
290
290
@@ -1,1321 +1,1324
1 """
1 """
2 util.py - Mercurial utility functions and platform specfic implementations
2 util.py - Mercurial utility functions and platform specfic implementations
3
3
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7
7
8 This software may be used and distributed according to the terms
8 This software may be used and distributed according to the terms
9 of the GNU General Public License, incorporated herein by reference.
9 of the GNU General Public License, incorporated herein by reference.
10
10
11 This contains helper routines that are independent of the SCM core and hide
11 This contains helper routines that are independent of the SCM core and hide
12 platform-specific details from the core.
12 platform-specific details from the core.
13 """
13 """
14
14
15 from i18n import _
15 from i18n import _
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 import os, threading, time, calendar, ConfigParser, locale
17 import os, threading, time, calendar, ConfigParser, locale
18
18
19 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
19 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
20 or "ascii"
20 or "ascii"
21 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
21 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
22 _fallbackencoding = 'ISO-8859-1'
22 _fallbackencoding = 'ISO-8859-1'
23
23
24 def tolocal(s):
24 def tolocal(s):
25 """
25 """
26 Convert a string from internal UTF-8 to local encoding
26 Convert a string from internal UTF-8 to local encoding
27
27
28 All internal strings should be UTF-8 but some repos before the
28 All internal strings should be UTF-8 but some repos before the
29 implementation of locale support may contain latin1 or possibly
29 implementation of locale support may contain latin1 or possibly
30 other character sets. We attempt to decode everything strictly
30 other character sets. We attempt to decode everything strictly
31 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
31 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
32 replace unknown characters.
32 replace unknown characters.
33 """
33 """
34 for e in ('UTF-8', _fallbackencoding):
34 for e in ('UTF-8', _fallbackencoding):
35 try:
35 try:
36 u = s.decode(e) # attempt strict decoding
36 u = s.decode(e) # attempt strict decoding
37 return u.encode(_encoding, "replace")
37 return u.encode(_encoding, "replace")
38 except LookupError, k:
38 except LookupError, k:
39 raise Abort(_("%s, please check your locale settings") % k)
39 raise Abort(_("%s, please check your locale settings") % k)
40 except UnicodeDecodeError:
40 except UnicodeDecodeError:
41 pass
41 pass
42 u = s.decode("utf-8", "replace") # last ditch
42 u = s.decode("utf-8", "replace") # last ditch
43 return u.encode(_encoding, "replace")
43 return u.encode(_encoding, "replace")
44
44
45 def fromlocal(s):
45 def fromlocal(s):
46 """
46 """
47 Convert a string from the local character encoding to UTF-8
47 Convert a string from the local character encoding to UTF-8
48
48
49 We attempt to decode strings using the encoding mode set by
49 We attempt to decode strings using the encoding mode set by
50 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
50 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
51 characters will cause an error message. Other modes include
51 characters will cause an error message. Other modes include
52 'replace', which replaces unknown characters with a special
52 'replace', which replaces unknown characters with a special
53 Unicode character, and 'ignore', which drops the character.
53 Unicode character, and 'ignore', which drops the character.
54 """
54 """
55 try:
55 try:
56 return s.decode(_encoding, _encodingmode).encode("utf-8")
56 return s.decode(_encoding, _encodingmode).encode("utf-8")
57 except UnicodeDecodeError, inst:
57 except UnicodeDecodeError, inst:
58 sub = s[max(0, inst.start-10):inst.start+10]
58 sub = s[max(0, inst.start-10):inst.start+10]
59 raise Abort("decoding near '%s': %s!" % (sub, inst))
59 raise Abort("decoding near '%s': %s!" % (sub, inst))
60 except LookupError, k:
60 except LookupError, k:
61 raise Abort(_("%s, please check your locale settings") % k)
61 raise Abort(_("%s, please check your locale settings") % k)
62
62
63 def locallen(s):
63 def locallen(s):
64 """Find the length in characters of a local string"""
64 """Find the length in characters of a local string"""
65 return len(s.decode(_encoding, "replace"))
65 return len(s.decode(_encoding, "replace"))
66
66
67 def localsub(s, a, b=None):
67 def localsub(s, a, b=None):
68 try:
68 try:
69 u = s.decode(_encoding, _encodingmode)
69 u = s.decode(_encoding, _encodingmode)
70 if b is not None:
70 if b is not None:
71 u = u[a:b]
71 u = u[a:b]
72 else:
72 else:
73 u = u[:a]
73 u = u[:a]
74 return u.encode(_encoding, _encodingmode)
74 return u.encode(_encoding, _encodingmode)
75 except UnicodeDecodeError, inst:
75 except UnicodeDecodeError, inst:
76 sub = s[max(0, inst.start-10), inst.start+10]
76 sub = s[max(0, inst.start-10), inst.start+10]
77 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
77 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
78
78
79 # used by parsedate
79 # used by parsedate
80 defaultdateformats = (
80 defaultdateformats = (
81 '%Y-%m-%d %H:%M:%S',
81 '%Y-%m-%d %H:%M:%S',
82 '%Y-%m-%d %I:%M:%S%p',
82 '%Y-%m-%d %I:%M:%S%p',
83 '%Y-%m-%d %H:%M',
83 '%Y-%m-%d %H:%M',
84 '%Y-%m-%d %I:%M%p',
84 '%Y-%m-%d %I:%M%p',
85 '%Y-%m-%d',
85 '%Y-%m-%d',
86 '%m-%d',
86 '%m-%d',
87 '%m/%d',
87 '%m/%d',
88 '%m/%d/%y',
88 '%m/%d/%y',
89 '%m/%d/%Y',
89 '%m/%d/%Y',
90 '%a %b %d %H:%M:%S %Y',
90 '%a %b %d %H:%M:%S %Y',
91 '%a %b %d %I:%M:%S%p %Y',
91 '%a %b %d %I:%M:%S%p %Y',
92 '%b %d %H:%M:%S %Y',
92 '%b %d %H:%M:%S %Y',
93 '%b %d %I:%M:%S%p %Y',
93 '%b %d %I:%M:%S%p %Y',
94 '%b %d %H:%M:%S',
94 '%b %d %H:%M:%S',
95 '%b %d %I:%M:%S%p',
95 '%b %d %I:%M:%S%p',
96 '%b %d %H:%M',
96 '%b %d %H:%M',
97 '%b %d %I:%M%p',
97 '%b %d %I:%M%p',
98 '%b %d %Y',
98 '%b %d %Y',
99 '%b %d',
99 '%b %d',
100 '%H:%M:%S',
100 '%H:%M:%S',
101 '%I:%M:%SP',
101 '%I:%M:%SP',
102 '%H:%M',
102 '%H:%M',
103 '%I:%M%p',
103 '%I:%M%p',
104 )
104 )
105
105
106 extendeddateformats = defaultdateformats + (
106 extendeddateformats = defaultdateformats + (
107 "%Y",
107 "%Y",
108 "%Y-%m",
108 "%Y-%m",
109 "%b",
109 "%b",
110 "%b %Y",
110 "%b %Y",
111 )
111 )
112
112
113 class SignalInterrupt(Exception):
113 class SignalInterrupt(Exception):
114 """Exception raised on SIGTERM and SIGHUP."""
114 """Exception raised on SIGTERM and SIGHUP."""
115
115
116 # like SafeConfigParser but with case-sensitive keys
116 # like SafeConfigParser but with case-sensitive keys
117 class configparser(ConfigParser.SafeConfigParser):
117 class configparser(ConfigParser.SafeConfigParser):
118 def optionxform(self, optionstr):
118 def optionxform(self, optionstr):
119 return optionstr
119 return optionstr
120
120
121 def cachefunc(func):
121 def cachefunc(func):
122 '''cache the result of function calls'''
122 '''cache the result of function calls'''
123 # XXX doesn't handle keywords args
123 # XXX doesn't handle keywords args
124 cache = {}
124 cache = {}
125 if func.func_code.co_argcount == 1:
125 if func.func_code.co_argcount == 1:
126 # we gain a small amount of time because
126 # we gain a small amount of time because
127 # we don't need to pack/unpack the list
127 # we don't need to pack/unpack the list
128 def f(arg):
128 def f(arg):
129 if arg not in cache:
129 if arg not in cache:
130 cache[arg] = func(arg)
130 cache[arg] = func(arg)
131 return cache[arg]
131 return cache[arg]
132 else:
132 else:
133 def f(*args):
133 def f(*args):
134 if args not in cache:
134 if args not in cache:
135 cache[args] = func(*args)
135 cache[args] = func(*args)
136 return cache[args]
136 return cache[args]
137
137
138 return f
138 return f
139
139
140 def pipefilter(s, cmd):
140 def pipefilter(s, cmd):
141 '''filter string S through command CMD, returning its output'''
141 '''filter string S through command CMD, returning its output'''
142 (pout, pin) = popen2.popen2(cmd, -1, 'b')
142 (pout, pin) = popen2.popen2(cmd, -1, 'b')
143 def writer():
143 def writer():
144 try:
144 try:
145 pin.write(s)
145 pin.write(s)
146 pin.close()
146 pin.close()
147 except IOError, inst:
147 except IOError, inst:
148 if inst.errno != errno.EPIPE:
148 if inst.errno != errno.EPIPE:
149 raise
149 raise
150
150
151 # we should use select instead on UNIX, but this will work on most
151 # we should use select instead on UNIX, but this will work on most
152 # systems, including Windows
152 # systems, including Windows
153 w = threading.Thread(target=writer)
153 w = threading.Thread(target=writer)
154 w.start()
154 w.start()
155 f = pout.read()
155 f = pout.read()
156 pout.close()
156 pout.close()
157 w.join()
157 w.join()
158 return f
158 return f
159
159
160 def tempfilter(s, cmd):
160 def tempfilter(s, cmd):
161 '''filter string S through a pair of temporary files with CMD.
161 '''filter string S through a pair of temporary files with CMD.
162 CMD is used as a template to create the real command to be run,
162 CMD is used as a template to create the real command to be run,
163 with the strings INFILE and OUTFILE replaced by the real names of
163 with the strings INFILE and OUTFILE replaced by the real names of
164 the temporary files generated.'''
164 the temporary files generated.'''
165 inname, outname = None, None
165 inname, outname = None, None
166 try:
166 try:
167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
167 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
168 fp = os.fdopen(infd, 'wb')
168 fp = os.fdopen(infd, 'wb')
169 fp.write(s)
169 fp.write(s)
170 fp.close()
170 fp.close()
171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
171 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
172 os.close(outfd)
172 os.close(outfd)
173 cmd = cmd.replace('INFILE', inname)
173 cmd = cmd.replace('INFILE', inname)
174 cmd = cmd.replace('OUTFILE', outname)
174 cmd = cmd.replace('OUTFILE', outname)
175 code = os.system(cmd)
175 code = os.system(cmd)
176 if code: raise Abort(_("command '%s' failed: %s") %
176 if code: raise Abort(_("command '%s' failed: %s") %
177 (cmd, explain_exit(code)))
177 (cmd, explain_exit(code)))
178 return open(outname, 'rb').read()
178 return open(outname, 'rb').read()
179 finally:
179 finally:
180 try:
180 try:
181 if inname: os.unlink(inname)
181 if inname: os.unlink(inname)
182 except: pass
182 except: pass
183 try:
183 try:
184 if outname: os.unlink(outname)
184 if outname: os.unlink(outname)
185 except: pass
185 except: pass
186
186
187 filtertable = {
187 filtertable = {
188 'tempfile:': tempfilter,
188 'tempfile:': tempfilter,
189 'pipe:': pipefilter,
189 'pipe:': pipefilter,
190 }
190 }
191
191
192 def filter(s, cmd):
192 def filter(s, cmd):
193 "filter a string through a command that transforms its input to its output"
193 "filter a string through a command that transforms its input to its output"
194 for name, fn in filtertable.iteritems():
194 for name, fn in filtertable.iteritems():
195 if cmd.startswith(name):
195 if cmd.startswith(name):
196 return fn(s, cmd[len(name):].lstrip())
196 return fn(s, cmd[len(name):].lstrip())
197 return pipefilter(s, cmd)
197 return pipefilter(s, cmd)
198
198
199 def find_in_path(name, path, default=None):
199 def find_in_path(name, path, default=None):
200 '''find name in search path. path can be string (will be split
200 '''find name in search path. path can be string (will be split
201 with os.pathsep), or iterable thing that returns strings. if name
201 with os.pathsep), or iterable thing that returns strings. if name
202 found, return path to name. else return default.'''
202 found, return path to name. else return default.'''
203 if isinstance(path, str):
203 if isinstance(path, str):
204 path = path.split(os.pathsep)
204 path = path.split(os.pathsep)
205 for p in path:
205 for p in path:
206 p_name = os.path.join(p, name)
206 p_name = os.path.join(p, name)
207 if os.path.exists(p_name):
207 if os.path.exists(p_name):
208 return p_name
208 return p_name
209 return default
209 return default
210
210
211 def binary(s):
211 def binary(s):
212 """return true if a string is binary data using diff's heuristic"""
212 """return true if a string is binary data using diff's heuristic"""
213 if s and '\0' in s[:4096]:
213 if s and '\0' in s[:4096]:
214 return True
214 return True
215 return False
215 return False
216
216
217 def unique(g):
217 def unique(g):
218 """return the uniq elements of iterable g"""
218 """return the uniq elements of iterable g"""
219 seen = {}
219 seen = {}
220 l = []
220 l = []
221 for f in g:
221 for f in g:
222 if f not in seen:
222 if f not in seen:
223 seen[f] = 1
223 seen[f] = 1
224 l.append(f)
224 l.append(f)
225 return l
225 return l
226
226
227 class Abort(Exception):
227 class Abort(Exception):
228 """Raised if a command needs to print an error and exit."""
228 """Raised if a command needs to print an error and exit."""
229
229
230 class UnexpectedOutput(Abort):
230 class UnexpectedOutput(Abort):
231 """Raised to print an error with part of output and exit."""
231 """Raised to print an error with part of output and exit."""
232
232
233 def always(fn): return True
233 def always(fn): return True
234 def never(fn): return False
234 def never(fn): return False
235
235
236 def patkind(name, dflt_pat='glob'):
236 def patkind(name, dflt_pat='glob'):
237 """Split a string into an optional pattern kind prefix and the
237 """Split a string into an optional pattern kind prefix and the
238 actual pattern."""
238 actual pattern."""
239 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
239 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
240 if name.startswith(prefix + ':'): return name.split(':', 1)
240 if name.startswith(prefix + ':'): return name.split(':', 1)
241 return dflt_pat, name
241 return dflt_pat, name
242
242
243 def globre(pat, head='^', tail='$'):
243 def globre(pat, head='^', tail='$'):
244 "convert a glob pattern into a regexp"
244 "convert a glob pattern into a regexp"
245 i, n = 0, len(pat)
245 i, n = 0, len(pat)
246 res = ''
246 res = ''
247 group = False
247 group = False
248 def peek(): return i < n and pat[i]
248 def peek(): return i < n and pat[i]
249 while i < n:
249 while i < n:
250 c = pat[i]
250 c = pat[i]
251 i = i+1
251 i = i+1
252 if c == '*':
252 if c == '*':
253 if peek() == '*':
253 if peek() == '*':
254 i += 1
254 i += 1
255 res += '.*'
255 res += '.*'
256 else:
256 else:
257 res += '[^/]*'
257 res += '[^/]*'
258 elif c == '?':
258 elif c == '?':
259 res += '.'
259 res += '.'
260 elif c == '[':
260 elif c == '[':
261 j = i
261 j = i
262 if j < n and pat[j] in '!]':
262 if j < n and pat[j] in '!]':
263 j += 1
263 j += 1
264 while j < n and pat[j] != ']':
264 while j < n and pat[j] != ']':
265 j += 1
265 j += 1
266 if j >= n:
266 if j >= n:
267 res += '\\['
267 res += '\\['
268 else:
268 else:
269 stuff = pat[i:j].replace('\\','\\\\')
269 stuff = pat[i:j].replace('\\','\\\\')
270 i = j + 1
270 i = j + 1
271 if stuff[0] == '!':
271 if stuff[0] == '!':
272 stuff = '^' + stuff[1:]
272 stuff = '^' + stuff[1:]
273 elif stuff[0] == '^':
273 elif stuff[0] == '^':
274 stuff = '\\' + stuff
274 stuff = '\\' + stuff
275 res = '%s[%s]' % (res, stuff)
275 res = '%s[%s]' % (res, stuff)
276 elif c == '{':
276 elif c == '{':
277 group = True
277 group = True
278 res += '(?:'
278 res += '(?:'
279 elif c == '}' and group:
279 elif c == '}' and group:
280 res += ')'
280 res += ')'
281 group = False
281 group = False
282 elif c == ',' and group:
282 elif c == ',' and group:
283 res += '|'
283 res += '|'
284 elif c == '\\':
284 elif c == '\\':
285 p = peek()
285 p = peek()
286 if p:
286 if p:
287 i += 1
287 i += 1
288 res += re.escape(p)
288 res += re.escape(p)
289 else:
289 else:
290 res += re.escape(c)
290 res += re.escape(c)
291 else:
291 else:
292 res += re.escape(c)
292 res += re.escape(c)
293 return head + res + tail
293 return head + res + tail
294
294
295 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
295 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
296
296
297 def pathto(n1, n2):
297 def pathto(n1, n2):
298 '''return the relative path from one place to another.
298 '''return the relative path from one place to another.
299 n1 should use os.sep to separate directories
299 n1 should use os.sep to separate directories
300 n2 should use "/" to separate directories
300 n2 should use "/" to separate directories
301 returns an os.sep-separated path.
301 returns an os.sep-separated path.
302 '''
302 '''
303 if not n1: return localpath(n2)
303 if not n1: return localpath(n2)
304 a, b = n1.split(os.sep), n2.split('/')
304 a, b = n1.split(os.sep), n2.split('/')
305 a.reverse()
305 a.reverse()
306 b.reverse()
306 b.reverse()
307 while a and b and a[-1] == b[-1]:
307 while a and b and a[-1] == b[-1]:
308 a.pop()
308 a.pop()
309 b.pop()
309 b.pop()
310 b.reverse()
310 b.reverse()
311 return os.sep.join((['..'] * len(a)) + b)
311 return os.sep.join((['..'] * len(a)) + b)
312
312
313 def canonpath(root, cwd, myname):
313 def canonpath(root, cwd, myname):
314 """return the canonical path of myname, given cwd and root"""
314 """return the canonical path of myname, given cwd and root"""
315 if root == os.sep:
315 if root == os.sep:
316 rootsep = os.sep
316 rootsep = os.sep
317 elif root.endswith(os.sep):
317 elif root.endswith(os.sep):
318 rootsep = root
318 rootsep = root
319 else:
319 else:
320 rootsep = root + os.sep
320 rootsep = root + os.sep
321 name = myname
321 name = myname
322 if not os.path.isabs(name):
322 if not os.path.isabs(name):
323 name = os.path.join(root, cwd, name)
323 name = os.path.join(root, cwd, name)
324 name = os.path.normpath(name)
324 name = os.path.normpath(name)
325 if name != rootsep and name.startswith(rootsep):
325 if name != rootsep and name.startswith(rootsep):
326 name = name[len(rootsep):]
326 name = name[len(rootsep):]
327 audit_path(name)
327 audit_path(name)
328 return pconvert(name)
328 return pconvert(name)
329 elif name == root:
329 elif name == root:
330 return ''
330 return ''
331 else:
331 else:
332 # Determine whether `name' is in the hierarchy at or beneath `root',
332 # Determine whether `name' is in the hierarchy at or beneath `root',
333 # by iterating name=dirname(name) until that causes no change (can't
333 # by iterating name=dirname(name) until that causes no change (can't
334 # check name == '/', because that doesn't work on windows). For each
334 # check name == '/', because that doesn't work on windows). For each
335 # `name', compare dev/inode numbers. If they match, the list `rel'
335 # `name', compare dev/inode numbers. If they match, the list `rel'
336 # holds the reversed list of components making up the relative file
336 # holds the reversed list of components making up the relative file
337 # name we want.
337 # name we want.
338 root_st = os.stat(root)
338 root_st = os.stat(root)
339 rel = []
339 rel = []
340 while True:
340 while True:
341 try:
341 try:
342 name_st = os.stat(name)
342 name_st = os.stat(name)
343 except OSError:
343 except OSError:
344 break
344 break
345 if samestat(name_st, root_st):
345 if samestat(name_st, root_st):
346 rel.reverse()
346 rel.reverse()
347 name = os.path.join(*rel)
347 name = os.path.join(*rel)
348 audit_path(name)
348 audit_path(name)
349 return pconvert(name)
349 return pconvert(name)
350 dirname, basename = os.path.split(name)
350 dirname, basename = os.path.split(name)
351 rel.append(basename)
351 rel.append(basename)
352 if dirname == name:
352 if dirname == name:
353 break
353 break
354 name = dirname
354 name = dirname
355
355
356 raise Abort('%s not under root' % myname)
356 raise Abort('%s not under root' % myname)
357
357
358 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
358 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
359 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
359 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
360
360
361 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
361 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
362 if os.name == 'nt':
362 if os.name == 'nt':
363 dflt_pat = 'glob'
363 dflt_pat = 'glob'
364 else:
364 else:
365 dflt_pat = 'relpath'
365 dflt_pat = 'relpath'
366 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
366 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
367
367
368 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
368 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
369 """build a function to match a set of file patterns
369 """build a function to match a set of file patterns
370
370
371 arguments:
371 arguments:
372 canonroot - the canonical root of the tree you're matching against
372 canonroot - the canonical root of the tree you're matching against
373 cwd - the current working directory, if relevant
373 cwd - the current working directory, if relevant
374 names - patterns to find
374 names - patterns to find
375 inc - patterns to include
375 inc - patterns to include
376 exc - patterns to exclude
376 exc - patterns to exclude
377 head - a regex to prepend to patterns to control whether a match is rooted
377 head - a regex to prepend to patterns to control whether a match is rooted
378
378
379 a pattern is one of:
379 a pattern is one of:
380 'glob:<rooted glob>'
380 'glob:<rooted glob>'
381 're:<rooted regexp>'
381 're:<rooted regexp>'
382 'path:<rooted path>'
382 'path:<rooted path>'
383 'relglob:<relative glob>'
383 'relglob:<relative glob>'
384 'relpath:<relative path>'
384 'relpath:<relative path>'
385 'relre:<relative regexp>'
385 'relre:<relative regexp>'
386 '<rooted path or regexp>'
386 '<rooted path or regexp>'
387
387
388 returns:
388 returns:
389 a 3-tuple containing
389 a 3-tuple containing
390 - list of explicit non-pattern names passed in
390 - list of explicit non-pattern names passed in
391 - a bool match(filename) function
391 - a bool match(filename) function
392 - a bool indicating if any patterns were passed in
392 - a bool indicating if any patterns were passed in
393
393
394 todo:
394 todo:
395 make head regex a rooted bool
395 make head regex a rooted bool
396 """
396 """
397
397
398 def contains_glob(name):
398 def contains_glob(name):
399 for c in name:
399 for c in name:
400 if c in _globchars: return True
400 if c in _globchars: return True
401 return False
401 return False
402
402
403 def regex(kind, name, tail):
403 def regex(kind, name, tail):
404 '''convert a pattern into a regular expression'''
404 '''convert a pattern into a regular expression'''
405 if kind == 're':
405 if kind == 're':
406 return name
406 return name
407 elif kind == 'path':
407 elif kind == 'path':
408 return '^' + re.escape(name) + '(?:/|$)'
408 return '^' + re.escape(name) + '(?:/|$)'
409 elif kind == 'relglob':
409 elif kind == 'relglob':
410 return head + globre(name, '(?:|.*/)', tail)
410 return head + globre(name, '(?:|.*/)', tail)
411 elif kind == 'relpath':
411 elif kind == 'relpath':
412 return head + re.escape(name) + tail
412 return head + re.escape(name) + tail
413 elif kind == 'relre':
413 elif kind == 'relre':
414 if name.startswith('^'):
414 if name.startswith('^'):
415 return name
415 return name
416 return '.*' + name
416 return '.*' + name
417 return head + globre(name, '', tail)
417 return head + globre(name, '', tail)
418
418
419 def matchfn(pats, tail):
419 def matchfn(pats, tail):
420 """build a matching function from a set of patterns"""
420 """build a matching function from a set of patterns"""
421 if not pats:
421 if not pats:
422 return
422 return
423 matches = []
423 matches = []
424 for k, p in pats:
424 for k, p in pats:
425 try:
425 try:
426 pat = '(?:%s)' % regex(k, p, tail)
426 pat = '(?:%s)' % regex(k, p, tail)
427 matches.append(re.compile(pat).match)
427 matches.append(re.compile(pat).match)
428 except re.error:
428 except re.error:
429 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
429 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
430 else: raise Abort("invalid pattern (%s): %s" % (k, p))
430 else: raise Abort("invalid pattern (%s): %s" % (k, p))
431
431
432 def buildfn(text):
432 def buildfn(text):
433 for m in matches:
433 for m in matches:
434 r = m(text)
434 r = m(text)
435 if r:
435 if r:
436 return r
436 return r
437
437
438 return buildfn
438 return buildfn
439
439
440 def globprefix(pat):
440 def globprefix(pat):
441 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
441 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
442 root = []
442 root = []
443 for p in pat.split(os.sep):
443 for p in pat.split(os.sep):
444 if contains_glob(p): break
444 if contains_glob(p): break
445 root.append(p)
445 root.append(p)
446 return '/'.join(root)
446 return '/'.join(root)
447
447
448 pats = []
448 pats = []
449 files = []
449 files = []
450 roots = []
450 roots = []
451 for kind, name in [patkind(p, dflt_pat) for p in names]:
451 for kind, name in [patkind(p, dflt_pat) for p in names]:
452 if kind in ('glob', 'relpath'):
452 if kind in ('glob', 'relpath'):
453 name = canonpath(canonroot, cwd, name)
453 name = canonpath(canonroot, cwd, name)
454 if name == '':
454 if name == '':
455 kind, name = 'glob', '**'
455 kind, name = 'glob', '**'
456 if kind in ('glob', 'path', 're'):
456 if kind in ('glob', 'path', 're'):
457 pats.append((kind, name))
457 pats.append((kind, name))
458 if kind == 'glob':
458 if kind == 'glob':
459 root = globprefix(name)
459 root = globprefix(name)
460 if root: roots.append(root)
460 if root: roots.append(root)
461 elif kind == 'relpath':
461 elif kind == 'relpath':
462 files.append((kind, name))
462 files.append((kind, name))
463 roots.append(name)
463 roots.append(name)
464
464
465 patmatch = matchfn(pats, '$') or always
465 patmatch = matchfn(pats, '$') or always
466 filematch = matchfn(files, '(?:/|$)') or always
466 filematch = matchfn(files, '(?:/|$)') or always
467 incmatch = always
467 incmatch = always
468 if inc:
468 if inc:
469 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
469 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
470 incmatch = matchfn(inckinds, '(?:/|$)')
470 incmatch = matchfn(inckinds, '(?:/|$)')
471 excmatch = lambda fn: False
471 excmatch = lambda fn: False
472 if exc:
472 if exc:
473 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
473 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
474 excmatch = matchfn(exckinds, '(?:/|$)')
474 excmatch = matchfn(exckinds, '(?:/|$)')
475
475
476 return (roots,
476 return (roots,
477 lambda fn: (incmatch(fn) and not excmatch(fn) and
477 lambda fn: (incmatch(fn) and not excmatch(fn) and
478 (fn.endswith('/') or
478 (fn.endswith('/') or
479 (not pats and not files) or
479 (not pats and not files) or
480 (pats and patmatch(fn)) or
480 (pats and patmatch(fn)) or
481 (files and filematch(fn)))),
481 (files and filematch(fn)))),
482 (inc or exc or (pats and pats != [('glob', '**')])) and True)
482 (inc or exc or (pats and pats != [('glob', '**')])) and True)
483
483
484 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
484 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
485 '''enhanced shell command execution.
485 '''enhanced shell command execution.
486 run with environment maybe modified, maybe in different dir.
486 run with environment maybe modified, maybe in different dir.
487
487
488 if command fails and onerr is None, return status. if ui object,
488 if command fails and onerr is None, return status. if ui object,
489 print error message and return status, else raise onerr object as
489 print error message and return status, else raise onerr object as
490 exception.'''
490 exception.'''
491 def py2shell(val):
491 def py2shell(val):
492 'convert python object into string that is useful to shell'
492 'convert python object into string that is useful to shell'
493 if val in (None, False):
493 if val in (None, False):
494 return '0'
494 return '0'
495 if val == True:
495 if val == True:
496 return '1'
496 return '1'
497 return str(val)
497 return str(val)
498 oldenv = {}
498 oldenv = {}
499 for k in environ:
499 for k in environ:
500 oldenv[k] = os.environ.get(k)
500 oldenv[k] = os.environ.get(k)
501 if cwd is not None:
501 if cwd is not None:
502 oldcwd = os.getcwd()
502 oldcwd = os.getcwd()
503 origcmd = cmd
504 if os.name == 'nt':
505 cmd = '"%s"' % cmd
503 try:
506 try:
504 for k, v in environ.iteritems():
507 for k, v in environ.iteritems():
505 os.environ[k] = py2shell(v)
508 os.environ[k] = py2shell(v)
506 if cwd is not None and oldcwd != cwd:
509 if cwd is not None and oldcwd != cwd:
507 os.chdir(cwd)
510 os.chdir(cwd)
508 rc = os.system(cmd)
511 rc = os.system(cmd)
509 if rc and onerr:
512 if rc and onerr:
510 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
513 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
511 explain_exit(rc)[0])
514 explain_exit(rc)[0])
512 if errprefix:
515 if errprefix:
513 errmsg = '%s: %s' % (errprefix, errmsg)
516 errmsg = '%s: %s' % (errprefix, errmsg)
514 try:
517 try:
515 onerr.warn(errmsg + '\n')
518 onerr.warn(errmsg + '\n')
516 except AttributeError:
519 except AttributeError:
517 raise onerr(errmsg)
520 raise onerr(errmsg)
518 return rc
521 return rc
519 finally:
522 finally:
520 for k, v in oldenv.iteritems():
523 for k, v in oldenv.iteritems():
521 if v is None:
524 if v is None:
522 del os.environ[k]
525 del os.environ[k]
523 else:
526 else:
524 os.environ[k] = v
527 os.environ[k] = v
525 if cwd is not None and oldcwd != cwd:
528 if cwd is not None and oldcwd != cwd:
526 os.chdir(oldcwd)
529 os.chdir(oldcwd)
527
530
528 def rename(src, dst):
531 def rename(src, dst):
529 """forcibly rename a file"""
532 """forcibly rename a file"""
530 try:
533 try:
531 os.rename(src, dst)
534 os.rename(src, dst)
532 except OSError, err:
535 except OSError, err:
533 # on windows, rename to existing file is not allowed, so we
536 # on windows, rename to existing file is not allowed, so we
534 # must delete destination first. but if file is open, unlink
537 # must delete destination first. but if file is open, unlink
535 # schedules it for delete but does not delete it. rename
538 # schedules it for delete but does not delete it. rename
536 # happens immediately even for open files, so we create
539 # happens immediately even for open files, so we create
537 # temporary file, delete it, rename destination to that name,
540 # temporary file, delete it, rename destination to that name,
538 # then delete that. then rename is safe to do.
541 # then delete that. then rename is safe to do.
539 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
542 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
540 os.close(fd)
543 os.close(fd)
541 os.unlink(temp)
544 os.unlink(temp)
542 os.rename(dst, temp)
545 os.rename(dst, temp)
543 os.unlink(temp)
546 os.unlink(temp)
544 os.rename(src, dst)
547 os.rename(src, dst)
545
548
546 def unlink(f):
549 def unlink(f):
547 """unlink and remove the directory if it is empty"""
550 """unlink and remove the directory if it is empty"""
548 os.unlink(f)
551 os.unlink(f)
549 # try removing directories that might now be empty
552 # try removing directories that might now be empty
550 try:
553 try:
551 os.removedirs(os.path.dirname(f))
554 os.removedirs(os.path.dirname(f))
552 except OSError:
555 except OSError:
553 pass
556 pass
554
557
555 def copyfile(src, dest):
558 def copyfile(src, dest):
556 "copy a file, preserving mode"
559 "copy a file, preserving mode"
557 try:
560 try:
558 shutil.copyfile(src, dest)
561 shutil.copyfile(src, dest)
559 shutil.copymode(src, dest)
562 shutil.copymode(src, dest)
560 except shutil.Error, inst:
563 except shutil.Error, inst:
561 raise util.Abort(str(inst))
564 raise util.Abort(str(inst))
562
565
563 def copyfiles(src, dst, hardlink=None):
566 def copyfiles(src, dst, hardlink=None):
564 """Copy a directory tree using hardlinks if possible"""
567 """Copy a directory tree using hardlinks if possible"""
565
568
566 if hardlink is None:
569 if hardlink is None:
567 hardlink = (os.stat(src).st_dev ==
570 hardlink = (os.stat(src).st_dev ==
568 os.stat(os.path.dirname(dst)).st_dev)
571 os.stat(os.path.dirname(dst)).st_dev)
569
572
570 if os.path.isdir(src):
573 if os.path.isdir(src):
571 os.mkdir(dst)
574 os.mkdir(dst)
572 for name in os.listdir(src):
575 for name in os.listdir(src):
573 srcname = os.path.join(src, name)
576 srcname = os.path.join(src, name)
574 dstname = os.path.join(dst, name)
577 dstname = os.path.join(dst, name)
575 copyfiles(srcname, dstname, hardlink)
578 copyfiles(srcname, dstname, hardlink)
576 else:
579 else:
577 if hardlink:
580 if hardlink:
578 try:
581 try:
579 os_link(src, dst)
582 os_link(src, dst)
580 except (IOError, OSError):
583 except (IOError, OSError):
581 hardlink = False
584 hardlink = False
582 shutil.copy(src, dst)
585 shutil.copy(src, dst)
583 else:
586 else:
584 shutil.copy(src, dst)
587 shutil.copy(src, dst)
585
588
586 def audit_path(path):
589 def audit_path(path):
587 """Abort if path contains dangerous components"""
590 """Abort if path contains dangerous components"""
588 parts = os.path.normcase(path).split(os.sep)
591 parts = os.path.normcase(path).split(os.sep)
589 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
592 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
590 or os.pardir in parts):
593 or os.pardir in parts):
591 raise Abort(_("path contains illegal component: %s\n") % path)
594 raise Abort(_("path contains illegal component: %s\n") % path)
592
595
593 def _makelock_file(info, pathname):
596 def _makelock_file(info, pathname):
594 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
597 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
595 os.write(ld, info)
598 os.write(ld, info)
596 os.close(ld)
599 os.close(ld)
597
600
598 def _readlock_file(pathname):
601 def _readlock_file(pathname):
599 return posixfile(pathname).read()
602 return posixfile(pathname).read()
600
603
601 def nlinks(pathname):
604 def nlinks(pathname):
602 """Return number of hardlinks for the given file."""
605 """Return number of hardlinks for the given file."""
603 return os.lstat(pathname).st_nlink
606 return os.lstat(pathname).st_nlink
604
607
605 if hasattr(os, 'link'):
608 if hasattr(os, 'link'):
606 os_link = os.link
609 os_link = os.link
607 else:
610 else:
608 def os_link(src, dst):
611 def os_link(src, dst):
609 raise OSError(0, _("Hardlinks not supported"))
612 raise OSError(0, _("Hardlinks not supported"))
610
613
611 def fstat(fp):
614 def fstat(fp):
612 '''stat file object that may not have fileno method.'''
615 '''stat file object that may not have fileno method.'''
613 try:
616 try:
614 return os.fstat(fp.fileno())
617 return os.fstat(fp.fileno())
615 except AttributeError:
618 except AttributeError:
616 return os.stat(fp.name)
619 return os.stat(fp.name)
617
620
618 posixfile = file
621 posixfile = file
619
622
620 def is_win_9x():
623 def is_win_9x():
621 '''return true if run on windows 95, 98 or me.'''
624 '''return true if run on windows 95, 98 or me.'''
622 try:
625 try:
623 return sys.getwindowsversion()[3] == 1
626 return sys.getwindowsversion()[3] == 1
624 except AttributeError:
627 except AttributeError:
625 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
628 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
626
629
627 getuser_fallback = None
630 getuser_fallback = None
628
631
629 def getuser():
632 def getuser():
630 '''return name of current user'''
633 '''return name of current user'''
631 try:
634 try:
632 return getpass.getuser()
635 return getpass.getuser()
633 except ImportError:
636 except ImportError:
634 # import of pwd will fail on windows - try fallback
637 # import of pwd will fail on windows - try fallback
635 if getuser_fallback:
638 if getuser_fallback:
636 return getuser_fallback()
639 return getuser_fallback()
637 # raised if win32api not available
640 # raised if win32api not available
638 raise Abort(_('user name not available - set USERNAME '
641 raise Abort(_('user name not available - set USERNAME '
639 'environment variable'))
642 'environment variable'))
640
643
641 def username(uid=None):
644 def username(uid=None):
642 """Return the name of the user with the given uid.
645 """Return the name of the user with the given uid.
643
646
644 If uid is None, return the name of the current user."""
647 If uid is None, return the name of the current user."""
645 try:
648 try:
646 import pwd
649 import pwd
647 if uid is None:
650 if uid is None:
648 uid = os.getuid()
651 uid = os.getuid()
649 try:
652 try:
650 return pwd.getpwuid(uid)[0]
653 return pwd.getpwuid(uid)[0]
651 except KeyError:
654 except KeyError:
652 return str(uid)
655 return str(uid)
653 except ImportError:
656 except ImportError:
654 return None
657 return None
655
658
656 def groupname(gid=None):
659 def groupname(gid=None):
657 """Return the name of the group with the given gid.
660 """Return the name of the group with the given gid.
658
661
659 If gid is None, return the name of the current group."""
662 If gid is None, return the name of the current group."""
660 try:
663 try:
661 import grp
664 import grp
662 if gid is None:
665 if gid is None:
663 gid = os.getgid()
666 gid = os.getgid()
664 try:
667 try:
665 return grp.getgrgid(gid)[0]
668 return grp.getgrgid(gid)[0]
666 except KeyError:
669 except KeyError:
667 return str(gid)
670 return str(gid)
668 except ImportError:
671 except ImportError:
669 return None
672 return None
670
673
671 # File system features
674 # File system features
672
675
673 def checkfolding(path):
676 def checkfolding(path):
674 """
677 """
675 Check whether the given path is on a case-sensitive filesystem
678 Check whether the given path is on a case-sensitive filesystem
676
679
677 Requires a path (like /foo/.hg) ending with a foldable final
680 Requires a path (like /foo/.hg) ending with a foldable final
678 directory component.
681 directory component.
679 """
682 """
680 s1 = os.stat(path)
683 s1 = os.stat(path)
681 d, b = os.path.split(path)
684 d, b = os.path.split(path)
682 p2 = os.path.join(d, b.upper())
685 p2 = os.path.join(d, b.upper())
683 if path == p2:
686 if path == p2:
684 p2 = os.path.join(d, b.lower())
687 p2 = os.path.join(d, b.lower())
685 try:
688 try:
686 s2 = os.stat(p2)
689 s2 = os.stat(p2)
687 if s2 == s1:
690 if s2 == s1:
688 return False
691 return False
689 return True
692 return True
690 except:
693 except:
691 return True
694 return True
692
695
693 # Platform specific variants
696 # Platform specific variants
694 if os.name == 'nt':
697 if os.name == 'nt':
695 import msvcrt
698 import msvcrt
696 nulldev = 'NUL:'
699 nulldev = 'NUL:'
697
700
698 class winstdout:
701 class winstdout:
699 '''stdout on windows misbehaves if sent through a pipe'''
702 '''stdout on windows misbehaves if sent through a pipe'''
700
703
701 def __init__(self, fp):
704 def __init__(self, fp):
702 self.fp = fp
705 self.fp = fp
703
706
704 def __getattr__(self, key):
707 def __getattr__(self, key):
705 return getattr(self.fp, key)
708 return getattr(self.fp, key)
706
709
707 def close(self):
710 def close(self):
708 try:
711 try:
709 self.fp.close()
712 self.fp.close()
710 except: pass
713 except: pass
711
714
712 def write(self, s):
715 def write(self, s):
713 try:
716 try:
714 return self.fp.write(s)
717 return self.fp.write(s)
715 except IOError, inst:
718 except IOError, inst:
716 if inst.errno != 0: raise
719 if inst.errno != 0: raise
717 self.close()
720 self.close()
718 raise IOError(errno.EPIPE, 'Broken pipe')
721 raise IOError(errno.EPIPE, 'Broken pipe')
719
722
720 sys.stdout = winstdout(sys.stdout)
723 sys.stdout = winstdout(sys.stdout)
721
724
722 def system_rcpath():
725 def system_rcpath():
723 try:
726 try:
724 return system_rcpath_win32()
727 return system_rcpath_win32()
725 except:
728 except:
726 return [r'c:\mercurial\mercurial.ini']
729 return [r'c:\mercurial\mercurial.ini']
727
730
728 def os_rcpath():
731 def os_rcpath():
729 '''return default os-specific hgrc search path'''
732 '''return default os-specific hgrc search path'''
730 path = system_rcpath()
733 path = system_rcpath()
731 path.append(user_rcpath())
734 path.append(user_rcpath())
732 userprofile = os.environ.get('USERPROFILE')
735 userprofile = os.environ.get('USERPROFILE')
733 if userprofile:
736 if userprofile:
734 path.append(os.path.join(userprofile, 'mercurial.ini'))
737 path.append(os.path.join(userprofile, 'mercurial.ini'))
735 return path
738 return path
736
739
737 def user_rcpath():
740 def user_rcpath():
738 '''return os-specific hgrc search path to the user dir'''
741 '''return os-specific hgrc search path to the user dir'''
739 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
742 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
740
743
741 def parse_patch_output(output_line):
744 def parse_patch_output(output_line):
742 """parses the output produced by patch and returns the file name"""
745 """parses the output produced by patch and returns the file name"""
743 pf = output_line[14:]
746 pf = output_line[14:]
744 if pf[0] == '`':
747 if pf[0] == '`':
745 pf = pf[1:-1] # Remove the quotes
748 pf = pf[1:-1] # Remove the quotes
746 return pf
749 return pf
747
750
748 def testpid(pid):
751 def testpid(pid):
749 '''return False if pid dead, True if running or not known'''
752 '''return False if pid dead, True if running or not known'''
750 return True
753 return True
751
754
752 def is_exec(f, last):
755 def is_exec(f, last):
753 return last
756 return last
754
757
755 def set_exec(f, mode):
758 def set_exec(f, mode):
756 pass
759 pass
757
760
758 def set_binary(fd):
761 def set_binary(fd):
759 msvcrt.setmode(fd.fileno(), os.O_BINARY)
762 msvcrt.setmode(fd.fileno(), os.O_BINARY)
760
763
761 def pconvert(path):
764 def pconvert(path):
762 return path.replace("\\", "/")
765 return path.replace("\\", "/")
763
766
764 def localpath(path):
767 def localpath(path):
765 return path.replace('/', '\\')
768 return path.replace('/', '\\')
766
769
767 def normpath(path):
770 def normpath(path):
768 return pconvert(os.path.normpath(path))
771 return pconvert(os.path.normpath(path))
769
772
770 makelock = _makelock_file
773 makelock = _makelock_file
771 readlock = _readlock_file
774 readlock = _readlock_file
772
775
773 def samestat(s1, s2):
776 def samestat(s1, s2):
774 return False
777 return False
775
778
776 def shellquote(s):
779 def shellquote(s):
777 return '"%s"' % s.replace('"', '\\"')
780 return '"%s"' % s.replace('"', '\\"')
778
781
779 def explain_exit(code):
782 def explain_exit(code):
780 return _("exited with status %d") % code, code
783 return _("exited with status %d") % code, code
781
784
782 # if you change this stub into a real check, please try to implement the
785 # if you change this stub into a real check, please try to implement the
783 # username and groupname functions above, too.
786 # username and groupname functions above, too.
784 def isowner(fp, st=None):
787 def isowner(fp, st=None):
785 return True
788 return True
786
789
787 try:
790 try:
788 # override functions with win32 versions if possible
791 # override functions with win32 versions if possible
789 from util_win32 import *
792 from util_win32 import *
790 if not is_win_9x():
793 if not is_win_9x():
791 posixfile = posixfile_nt
794 posixfile = posixfile_nt
792 except ImportError:
795 except ImportError:
793 pass
796 pass
794
797
795 else:
798 else:
796 nulldev = '/dev/null'
799 nulldev = '/dev/null'
797
800
798 def rcfiles(path):
801 def rcfiles(path):
799 rcs = [os.path.join(path, 'hgrc')]
802 rcs = [os.path.join(path, 'hgrc')]
800 rcdir = os.path.join(path, 'hgrc.d')
803 rcdir = os.path.join(path, 'hgrc.d')
801 try:
804 try:
802 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
805 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
803 if f.endswith(".rc")])
806 if f.endswith(".rc")])
804 except OSError:
807 except OSError:
805 pass
808 pass
806 return rcs
809 return rcs
807
810
808 def os_rcpath():
811 def os_rcpath():
809 '''return default os-specific hgrc search path'''
812 '''return default os-specific hgrc search path'''
810 path = []
813 path = []
811 # old mod_python does not set sys.argv
814 # old mod_python does not set sys.argv
812 if len(getattr(sys, 'argv', [])) > 0:
815 if len(getattr(sys, 'argv', [])) > 0:
813 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
816 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
814 '/../etc/mercurial'))
817 '/../etc/mercurial'))
815 path.extend(rcfiles('/etc/mercurial'))
818 path.extend(rcfiles('/etc/mercurial'))
816 path.append(os.path.expanduser('~/.hgrc'))
819 path.append(os.path.expanduser('~/.hgrc'))
817 path = [os.path.normpath(f) for f in path]
820 path = [os.path.normpath(f) for f in path]
818 return path
821 return path
819
822
820 def parse_patch_output(output_line):
823 def parse_patch_output(output_line):
821 """parses the output produced by patch and returns the file name"""
824 """parses the output produced by patch and returns the file name"""
822 pf = output_line[14:]
825 pf = output_line[14:]
823 if pf.startswith("'") and pf.endswith("'") and " " in pf:
826 if pf.startswith("'") and pf.endswith("'") and " " in pf:
824 pf = pf[1:-1] # Remove the quotes
827 pf = pf[1:-1] # Remove the quotes
825 return pf
828 return pf
826
829
827 def is_exec(f, last):
830 def is_exec(f, last):
828 """check whether a file is executable"""
831 """check whether a file is executable"""
829 return (os.lstat(f).st_mode & 0100 != 0)
832 return (os.lstat(f).st_mode & 0100 != 0)
830
833
831 def set_exec(f, mode):
834 def set_exec(f, mode):
832 s = os.lstat(f).st_mode
835 s = os.lstat(f).st_mode
833 if (s & 0100 != 0) == mode:
836 if (s & 0100 != 0) == mode:
834 return
837 return
835 if mode:
838 if mode:
836 # Turn on +x for every +r bit when making a file executable
839 # Turn on +x for every +r bit when making a file executable
837 # and obey umask.
840 # and obey umask.
838 umask = os.umask(0)
841 umask = os.umask(0)
839 os.umask(umask)
842 os.umask(umask)
840 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
843 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
841 else:
844 else:
842 os.chmod(f, s & 0666)
845 os.chmod(f, s & 0666)
843
846
844 def set_binary(fd):
847 def set_binary(fd):
845 pass
848 pass
846
849
847 def pconvert(path):
850 def pconvert(path):
848 return path
851 return path
849
852
850 def localpath(path):
853 def localpath(path):
851 return path
854 return path
852
855
853 normpath = os.path.normpath
856 normpath = os.path.normpath
854 samestat = os.path.samestat
857 samestat = os.path.samestat
855
858
856 def makelock(info, pathname):
859 def makelock(info, pathname):
857 try:
860 try:
858 os.symlink(info, pathname)
861 os.symlink(info, pathname)
859 except OSError, why:
862 except OSError, why:
860 if why.errno == errno.EEXIST:
863 if why.errno == errno.EEXIST:
861 raise
864 raise
862 else:
865 else:
863 _makelock_file(info, pathname)
866 _makelock_file(info, pathname)
864
867
865 def readlock(pathname):
868 def readlock(pathname):
866 try:
869 try:
867 return os.readlink(pathname)
870 return os.readlink(pathname)
868 except OSError, why:
871 except OSError, why:
869 if why.errno == errno.EINVAL:
872 if why.errno == errno.EINVAL:
870 return _readlock_file(pathname)
873 return _readlock_file(pathname)
871 else:
874 else:
872 raise
875 raise
873
876
874 def shellquote(s):
877 def shellquote(s):
875 return "'%s'" % s.replace("'", "'\\''")
878 return "'%s'" % s.replace("'", "'\\''")
876
879
877 def testpid(pid):
880 def testpid(pid):
878 '''return False if pid dead, True if running or not sure'''
881 '''return False if pid dead, True if running or not sure'''
879 try:
882 try:
880 os.kill(pid, 0)
883 os.kill(pid, 0)
881 return True
884 return True
882 except OSError, inst:
885 except OSError, inst:
883 return inst.errno != errno.ESRCH
886 return inst.errno != errno.ESRCH
884
887
885 def explain_exit(code):
888 def explain_exit(code):
886 """return a 2-tuple (desc, code) describing a process's status"""
889 """return a 2-tuple (desc, code) describing a process's status"""
887 if os.WIFEXITED(code):
890 if os.WIFEXITED(code):
888 val = os.WEXITSTATUS(code)
891 val = os.WEXITSTATUS(code)
889 return _("exited with status %d") % val, val
892 return _("exited with status %d") % val, val
890 elif os.WIFSIGNALED(code):
893 elif os.WIFSIGNALED(code):
891 val = os.WTERMSIG(code)
894 val = os.WTERMSIG(code)
892 return _("killed by signal %d") % val, val
895 return _("killed by signal %d") % val, val
893 elif os.WIFSTOPPED(code):
896 elif os.WIFSTOPPED(code):
894 val = os.WSTOPSIG(code)
897 val = os.WSTOPSIG(code)
895 return _("stopped by signal %d") % val, val
898 return _("stopped by signal %d") % val, val
896 raise ValueError(_("invalid exit code"))
899 raise ValueError(_("invalid exit code"))
897
900
898 def isowner(fp, st=None):
901 def isowner(fp, st=None):
899 """Return True if the file object f belongs to the current user.
902 """Return True if the file object f belongs to the current user.
900
903
901 The return value of a util.fstat(f) may be passed as the st argument.
904 The return value of a util.fstat(f) may be passed as the st argument.
902 """
905 """
903 if st is None:
906 if st is None:
904 st = fstat(fp)
907 st = fstat(fp)
905 return st.st_uid == os.getuid()
908 return st.st_uid == os.getuid()
906
909
907 def _buildencodefun():
910 def _buildencodefun():
908 e = '_'
911 e = '_'
909 win_reserved = [ord(x) for x in '\\:*?"<>|']
912 win_reserved = [ord(x) for x in '\\:*?"<>|']
910 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
913 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
911 for x in (range(32) + range(126, 256) + win_reserved):
914 for x in (range(32) + range(126, 256) + win_reserved):
912 cmap[chr(x)] = "~%02x" % x
915 cmap[chr(x)] = "~%02x" % x
913 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
916 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
914 cmap[chr(x)] = e + chr(x).lower()
917 cmap[chr(x)] = e + chr(x).lower()
915 dmap = {}
918 dmap = {}
916 for k, v in cmap.iteritems():
919 for k, v in cmap.iteritems():
917 dmap[v] = k
920 dmap[v] = k
918 def decode(s):
921 def decode(s):
919 i = 0
922 i = 0
920 while i < len(s):
923 while i < len(s):
921 for l in xrange(1, 4):
924 for l in xrange(1, 4):
922 try:
925 try:
923 yield dmap[s[i:i+l]]
926 yield dmap[s[i:i+l]]
924 i += l
927 i += l
925 break
928 break
926 except KeyError:
929 except KeyError:
927 pass
930 pass
928 else:
931 else:
929 raise KeyError
932 raise KeyError
930 return (lambda s: "".join([cmap[c] for c in s]),
933 return (lambda s: "".join([cmap[c] for c in s]),
931 lambda s: "".join(list(decode(s))))
934 lambda s: "".join(list(decode(s))))
932
935
933 encodefilename, decodefilename = _buildencodefun()
936 encodefilename, decodefilename = _buildencodefun()
934
937
935 def encodedopener(openerfn, fn):
938 def encodedopener(openerfn, fn):
936 def o(path, *args, **kw):
939 def o(path, *args, **kw):
937 return openerfn(fn(path), *args, **kw)
940 return openerfn(fn(path), *args, **kw)
938 return o
941 return o
939
942
940 def opener(base, audit=True):
943 def opener(base, audit=True):
941 """
944 """
942 return a function that opens files relative to base
945 return a function that opens files relative to base
943
946
944 this function is used to hide the details of COW semantics and
947 this function is used to hide the details of COW semantics and
945 remote file access from higher level code.
948 remote file access from higher level code.
946 """
949 """
947 p = base
950 p = base
948 audit_p = audit
951 audit_p = audit
949
952
950 def mktempcopy(name):
953 def mktempcopy(name):
951 d, fn = os.path.split(name)
954 d, fn = os.path.split(name)
952 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
955 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
953 os.close(fd)
956 os.close(fd)
954 ofp = posixfile(temp, "wb")
957 ofp = posixfile(temp, "wb")
955 try:
958 try:
956 try:
959 try:
957 ifp = posixfile(name, "rb")
960 ifp = posixfile(name, "rb")
958 except IOError, inst:
961 except IOError, inst:
959 if not getattr(inst, 'filename', None):
962 if not getattr(inst, 'filename', None):
960 inst.filename = name
963 inst.filename = name
961 raise
964 raise
962 for chunk in filechunkiter(ifp):
965 for chunk in filechunkiter(ifp):
963 ofp.write(chunk)
966 ofp.write(chunk)
964 ifp.close()
967 ifp.close()
965 ofp.close()
968 ofp.close()
966 except:
969 except:
967 try: os.unlink(temp)
970 try: os.unlink(temp)
968 except: pass
971 except: pass
969 raise
972 raise
970 st = os.lstat(name)
973 st = os.lstat(name)
971 os.chmod(temp, st.st_mode)
974 os.chmod(temp, st.st_mode)
972 return temp
975 return temp
973
976
974 class atomictempfile(posixfile):
977 class atomictempfile(posixfile):
975 """the file will only be copied when rename is called"""
978 """the file will only be copied when rename is called"""
976 def __init__(self, name, mode):
979 def __init__(self, name, mode):
977 self.__name = name
980 self.__name = name
978 self.temp = mktempcopy(name)
981 self.temp = mktempcopy(name)
979 posixfile.__init__(self, self.temp, mode)
982 posixfile.__init__(self, self.temp, mode)
980 def rename(self):
983 def rename(self):
981 if not self.closed:
984 if not self.closed:
982 posixfile.close(self)
985 posixfile.close(self)
983 rename(self.temp, localpath(self.__name))
986 rename(self.temp, localpath(self.__name))
984 def __del__(self):
987 def __del__(self):
985 if not self.closed:
988 if not self.closed:
986 try:
989 try:
987 os.unlink(self.temp)
990 os.unlink(self.temp)
988 except: pass
991 except: pass
989 posixfile.close(self)
992 posixfile.close(self)
990
993
991 class atomicfile(atomictempfile):
994 class atomicfile(atomictempfile):
992 """the file will only be copied on close"""
995 """the file will only be copied on close"""
993 def __init__(self, name, mode):
996 def __init__(self, name, mode):
994 atomictempfile.__init__(self, name, mode)
997 atomictempfile.__init__(self, name, mode)
995 def close(self):
998 def close(self):
996 self.rename()
999 self.rename()
997 def __del__(self):
1000 def __del__(self):
998 self.rename()
1001 self.rename()
999
1002
1000 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1003 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1001 if audit_p:
1004 if audit_p:
1002 audit_path(path)
1005 audit_path(path)
1003 f = os.path.join(p, path)
1006 f = os.path.join(p, path)
1004
1007
1005 if not text:
1008 if not text:
1006 mode += "b" # for that other OS
1009 mode += "b" # for that other OS
1007
1010
1008 if mode[0] != "r":
1011 if mode[0] != "r":
1009 try:
1012 try:
1010 nlink = nlinks(f)
1013 nlink = nlinks(f)
1011 except OSError:
1014 except OSError:
1012 d = os.path.dirname(f)
1015 d = os.path.dirname(f)
1013 if not os.path.isdir(d):
1016 if not os.path.isdir(d):
1014 os.makedirs(d)
1017 os.makedirs(d)
1015 else:
1018 else:
1016 if atomic:
1019 if atomic:
1017 return atomicfile(f, mode)
1020 return atomicfile(f, mode)
1018 elif atomictemp:
1021 elif atomictemp:
1019 return atomictempfile(f, mode)
1022 return atomictempfile(f, mode)
1020 if nlink > 1:
1023 if nlink > 1:
1021 rename(mktempcopy(f), f)
1024 rename(mktempcopy(f), f)
1022 return posixfile(f, mode)
1025 return posixfile(f, mode)
1023
1026
1024 return o
1027 return o
1025
1028
1026 class chunkbuffer(object):
1029 class chunkbuffer(object):
1027 """Allow arbitrary sized chunks of data to be efficiently read from an
1030 """Allow arbitrary sized chunks of data to be efficiently read from an
1028 iterator over chunks of arbitrary size."""
1031 iterator over chunks of arbitrary size."""
1029
1032
1030 def __init__(self, in_iter, targetsize = 2**16):
1033 def __init__(self, in_iter, targetsize = 2**16):
1031 """in_iter is the iterator that's iterating over the input chunks.
1034 """in_iter is the iterator that's iterating over the input chunks.
1032 targetsize is how big a buffer to try to maintain."""
1035 targetsize is how big a buffer to try to maintain."""
1033 self.in_iter = iter(in_iter)
1036 self.in_iter = iter(in_iter)
1034 self.buf = ''
1037 self.buf = ''
1035 self.targetsize = int(targetsize)
1038 self.targetsize = int(targetsize)
1036 if self.targetsize <= 0:
1039 if self.targetsize <= 0:
1037 raise ValueError(_("targetsize must be greater than 0, was %d") %
1040 raise ValueError(_("targetsize must be greater than 0, was %d") %
1038 targetsize)
1041 targetsize)
1039 self.iterempty = False
1042 self.iterempty = False
1040
1043
1041 def fillbuf(self):
1044 def fillbuf(self):
1042 """Ignore target size; read every chunk from iterator until empty."""
1045 """Ignore target size; read every chunk from iterator until empty."""
1043 if not self.iterempty:
1046 if not self.iterempty:
1044 collector = cStringIO.StringIO()
1047 collector = cStringIO.StringIO()
1045 collector.write(self.buf)
1048 collector.write(self.buf)
1046 for ch in self.in_iter:
1049 for ch in self.in_iter:
1047 collector.write(ch)
1050 collector.write(ch)
1048 self.buf = collector.getvalue()
1051 self.buf = collector.getvalue()
1049 self.iterempty = True
1052 self.iterempty = True
1050
1053
1051 def read(self, l):
1054 def read(self, l):
1052 """Read L bytes of data from the iterator of chunks of data.
1055 """Read L bytes of data from the iterator of chunks of data.
1053 Returns less than L bytes if the iterator runs dry."""
1056 Returns less than L bytes if the iterator runs dry."""
1054 if l > len(self.buf) and not self.iterempty:
1057 if l > len(self.buf) and not self.iterempty:
1055 # Clamp to a multiple of self.targetsize
1058 # Clamp to a multiple of self.targetsize
1056 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1059 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1057 collector = cStringIO.StringIO()
1060 collector = cStringIO.StringIO()
1058 collector.write(self.buf)
1061 collector.write(self.buf)
1059 collected = len(self.buf)
1062 collected = len(self.buf)
1060 for chunk in self.in_iter:
1063 for chunk in self.in_iter:
1061 collector.write(chunk)
1064 collector.write(chunk)
1062 collected += len(chunk)
1065 collected += len(chunk)
1063 if collected >= targetsize:
1066 if collected >= targetsize:
1064 break
1067 break
1065 if collected < targetsize:
1068 if collected < targetsize:
1066 self.iterempty = True
1069 self.iterempty = True
1067 self.buf = collector.getvalue()
1070 self.buf = collector.getvalue()
1068 s, self.buf = self.buf[:l], buffer(self.buf, l)
1071 s, self.buf = self.buf[:l], buffer(self.buf, l)
1069 return s
1072 return s
1070
1073
1071 def filechunkiter(f, size=65536, limit=None):
1074 def filechunkiter(f, size=65536, limit=None):
1072 """Create a generator that produces the data in the file size
1075 """Create a generator that produces the data in the file size
1073 (default 65536) bytes at a time, up to optional limit (default is
1076 (default 65536) bytes at a time, up to optional limit (default is
1074 to read all data). Chunks may be less than size bytes if the
1077 to read all data). Chunks may be less than size bytes if the
1075 chunk is the last chunk in the file, or the file is a socket or
1078 chunk is the last chunk in the file, or the file is a socket or
1076 some other type of file that sometimes reads less data than is
1079 some other type of file that sometimes reads less data than is
1077 requested."""
1080 requested."""
1078 assert size >= 0
1081 assert size >= 0
1079 assert limit is None or limit >= 0
1082 assert limit is None or limit >= 0
1080 while True:
1083 while True:
1081 if limit is None: nbytes = size
1084 if limit is None: nbytes = size
1082 else: nbytes = min(limit, size)
1085 else: nbytes = min(limit, size)
1083 s = nbytes and f.read(nbytes)
1086 s = nbytes and f.read(nbytes)
1084 if not s: break
1087 if not s: break
1085 if limit: limit -= len(s)
1088 if limit: limit -= len(s)
1086 yield s
1089 yield s
1087
1090
1088 def makedate():
1091 def makedate():
1089 lt = time.localtime()
1092 lt = time.localtime()
1090 if lt[8] == 1 and time.daylight:
1093 if lt[8] == 1 and time.daylight:
1091 tz = time.altzone
1094 tz = time.altzone
1092 else:
1095 else:
1093 tz = time.timezone
1096 tz = time.timezone
1094 return time.mktime(lt), tz
1097 return time.mktime(lt), tz
1095
1098
1096 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1099 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1097 """represent a (unixtime, offset) tuple as a localized time.
1100 """represent a (unixtime, offset) tuple as a localized time.
1098 unixtime is seconds since the epoch, and offset is the time zone's
1101 unixtime is seconds since the epoch, and offset is the time zone's
1099 number of seconds away from UTC. if timezone is false, do not
1102 number of seconds away from UTC. if timezone is false, do not
1100 append time zone to string."""
1103 append time zone to string."""
1101 t, tz = date or makedate()
1104 t, tz = date or makedate()
1102 s = time.strftime(format, time.gmtime(float(t) - tz))
1105 s = time.strftime(format, time.gmtime(float(t) - tz))
1103 if timezone:
1106 if timezone:
1104 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1107 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1105 return s
1108 return s
1106
1109
1107 def strdate(string, format, defaults):
1110 def strdate(string, format, defaults):
1108 """parse a localized time string and return a (unixtime, offset) tuple.
1111 """parse a localized time string and return a (unixtime, offset) tuple.
1109 if the string cannot be parsed, ValueError is raised."""
1112 if the string cannot be parsed, ValueError is raised."""
1110 def timezone(string):
1113 def timezone(string):
1111 tz = string.split()[-1]
1114 tz = string.split()[-1]
1112 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1115 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1113 tz = int(tz)
1116 tz = int(tz)
1114 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1117 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1115 return offset
1118 return offset
1116 if tz == "GMT" or tz == "UTC":
1119 if tz == "GMT" or tz == "UTC":
1117 return 0
1120 return 0
1118 return None
1121 return None
1119
1122
1120 # NOTE: unixtime = localunixtime + offset
1123 # NOTE: unixtime = localunixtime + offset
1121 offset, date = timezone(string), string
1124 offset, date = timezone(string), string
1122 if offset != None:
1125 if offset != None:
1123 date = " ".join(string.split()[:-1])
1126 date = " ".join(string.split()[:-1])
1124
1127
1125 # add missing elements from defaults
1128 # add missing elements from defaults
1126 for part in defaults:
1129 for part in defaults:
1127 found = [True for p in part if ("%"+p) in format]
1130 found = [True for p in part if ("%"+p) in format]
1128 if not found:
1131 if not found:
1129 date += "@" + defaults[part]
1132 date += "@" + defaults[part]
1130 format += "@%" + part[0]
1133 format += "@%" + part[0]
1131
1134
1132 timetuple = time.strptime(date, format)
1135 timetuple = time.strptime(date, format)
1133 localunixtime = int(calendar.timegm(timetuple))
1136 localunixtime = int(calendar.timegm(timetuple))
1134 if offset is None:
1137 if offset is None:
1135 # local timezone
1138 # local timezone
1136 unixtime = int(time.mktime(timetuple))
1139 unixtime = int(time.mktime(timetuple))
1137 offset = unixtime - localunixtime
1140 offset = unixtime - localunixtime
1138 else:
1141 else:
1139 unixtime = localunixtime + offset
1142 unixtime = localunixtime + offset
1140 return unixtime, offset
1143 return unixtime, offset
1141
1144
1142 def parsedate(string, formats=None, defaults=None):
1145 def parsedate(string, formats=None, defaults=None):
1143 """parse a localized time string and return a (unixtime, offset) tuple.
1146 """parse a localized time string and return a (unixtime, offset) tuple.
1144 The date may be a "unixtime offset" string or in one of the specified
1147 The date may be a "unixtime offset" string or in one of the specified
1145 formats."""
1148 formats."""
1146 if not string:
1149 if not string:
1147 return 0, 0
1150 return 0, 0
1148 if not formats:
1151 if not formats:
1149 formats = defaultdateformats
1152 formats = defaultdateformats
1150 string = string.strip()
1153 string = string.strip()
1151 try:
1154 try:
1152 when, offset = map(int, string.split(' '))
1155 when, offset = map(int, string.split(' '))
1153 except ValueError:
1156 except ValueError:
1154 # fill out defaults
1157 # fill out defaults
1155 if not defaults:
1158 if not defaults:
1156 defaults = {}
1159 defaults = {}
1157 now = makedate()
1160 now = makedate()
1158 for part in "d mb yY HI M S".split():
1161 for part in "d mb yY HI M S".split():
1159 if part not in defaults:
1162 if part not in defaults:
1160 if part[0] in "HMS":
1163 if part[0] in "HMS":
1161 defaults[part] = "00"
1164 defaults[part] = "00"
1162 elif part[0] in "dm":
1165 elif part[0] in "dm":
1163 defaults[part] = "1"
1166 defaults[part] = "1"
1164 else:
1167 else:
1165 defaults[part] = datestr(now, "%" + part[0], False)
1168 defaults[part] = datestr(now, "%" + part[0], False)
1166
1169
1167 for format in formats:
1170 for format in formats:
1168 try:
1171 try:
1169 when, offset = strdate(string, format, defaults)
1172 when, offset = strdate(string, format, defaults)
1170 except ValueError:
1173 except ValueError:
1171 pass
1174 pass
1172 else:
1175 else:
1173 break
1176 break
1174 else:
1177 else:
1175 raise Abort(_('invalid date: %r ') % string)
1178 raise Abort(_('invalid date: %r ') % string)
1176 # validate explicit (probably user-specified) date and
1179 # validate explicit (probably user-specified) date and
1177 # time zone offset. values must fit in signed 32 bits for
1180 # time zone offset. values must fit in signed 32 bits for
1178 # current 32-bit linux runtimes. timezones go from UTC-12
1181 # current 32-bit linux runtimes. timezones go from UTC-12
1179 # to UTC+14
1182 # to UTC+14
1180 if abs(when) > 0x7fffffff:
1183 if abs(when) > 0x7fffffff:
1181 raise Abort(_('date exceeds 32 bits: %d') % when)
1184 raise Abort(_('date exceeds 32 bits: %d') % when)
1182 if offset < -50400 or offset > 43200:
1185 if offset < -50400 or offset > 43200:
1183 raise Abort(_('impossible time zone offset: %d') % offset)
1186 raise Abort(_('impossible time zone offset: %d') % offset)
1184 return when, offset
1187 return when, offset
1185
1188
1186 def matchdate(date):
1189 def matchdate(date):
1187 """Return a function that matches a given date match specifier
1190 """Return a function that matches a given date match specifier
1188
1191
1189 Formats include:
1192 Formats include:
1190
1193
1191 '{date}' match a given date to the accuracy provided
1194 '{date}' match a given date to the accuracy provided
1192
1195
1193 '<{date}' on or before a given date
1196 '<{date}' on or before a given date
1194
1197
1195 '>{date}' on or after a given date
1198 '>{date}' on or after a given date
1196
1199
1197 """
1200 """
1198
1201
1199 def lower(date):
1202 def lower(date):
1200 return parsedate(date, extendeddateformats)[0]
1203 return parsedate(date, extendeddateformats)[0]
1201
1204
1202 def upper(date):
1205 def upper(date):
1203 d = dict(mb="12", HI="23", M="59", S="59")
1206 d = dict(mb="12", HI="23", M="59", S="59")
1204 for days in "31 30 29".split():
1207 for days in "31 30 29".split():
1205 try:
1208 try:
1206 d["d"] = days
1209 d["d"] = days
1207 return parsedate(date, extendeddateformats, d)[0]
1210 return parsedate(date, extendeddateformats, d)[0]
1208 except:
1211 except:
1209 pass
1212 pass
1210 d["d"] = "28"
1213 d["d"] = "28"
1211 return parsedate(date, extendeddateformats, d)[0]
1214 return parsedate(date, extendeddateformats, d)[0]
1212
1215
1213 if date[0] == "<":
1216 if date[0] == "<":
1214 when = upper(date[1:])
1217 when = upper(date[1:])
1215 return lambda x: x <= when
1218 return lambda x: x <= when
1216 elif date[0] == ">":
1219 elif date[0] == ">":
1217 when = lower(date[1:])
1220 when = lower(date[1:])
1218 return lambda x: x >= when
1221 return lambda x: x >= when
1219 elif date[0] == "-":
1222 elif date[0] == "-":
1220 try:
1223 try:
1221 days = int(date[1:])
1224 days = int(date[1:])
1222 except ValueError:
1225 except ValueError:
1223 raise Abort(_("invalid day spec: %s") % date[1:])
1226 raise Abort(_("invalid day spec: %s") % date[1:])
1224 when = makedate()[0] - days * 3600 * 24
1227 when = makedate()[0] - days * 3600 * 24
1225 return lambda x: x >= when
1228 return lambda x: x >= when
1226 elif " to " in date:
1229 elif " to " in date:
1227 a, b = date.split(" to ")
1230 a, b = date.split(" to ")
1228 start, stop = lower(a), upper(b)
1231 start, stop = lower(a), upper(b)
1229 return lambda x: x >= start and x <= stop
1232 return lambda x: x >= start and x <= stop
1230 else:
1233 else:
1231 start, stop = lower(date), upper(date)
1234 start, stop = lower(date), upper(date)
1232 return lambda x: x >= start and x <= stop
1235 return lambda x: x >= start and x <= stop
1233
1236
1234 def shortuser(user):
1237 def shortuser(user):
1235 """Return a short representation of a user name or email address."""
1238 """Return a short representation of a user name or email address."""
1236 f = user.find('@')
1239 f = user.find('@')
1237 if f >= 0:
1240 if f >= 0:
1238 user = user[:f]
1241 user = user[:f]
1239 f = user.find('<')
1242 f = user.find('<')
1240 if f >= 0:
1243 if f >= 0:
1241 user = user[f+1:]
1244 user = user[f+1:]
1242 f = user.find(' ')
1245 f = user.find(' ')
1243 if f >= 0:
1246 if f >= 0:
1244 user = user[:f]
1247 user = user[:f]
1245 f = user.find('.')
1248 f = user.find('.')
1246 if f >= 0:
1249 if f >= 0:
1247 user = user[:f]
1250 user = user[:f]
1248 return user
1251 return user
1249
1252
1250 def ellipsis(text, maxlength=400):
1253 def ellipsis(text, maxlength=400):
1251 """Trim string to at most maxlength (default: 400) characters."""
1254 """Trim string to at most maxlength (default: 400) characters."""
1252 if len(text) <= maxlength:
1255 if len(text) <= maxlength:
1253 return text
1256 return text
1254 else:
1257 else:
1255 return "%s..." % (text[:maxlength-3])
1258 return "%s..." % (text[:maxlength-3])
1256
1259
1257 def walkrepos(path):
1260 def walkrepos(path):
1258 '''yield every hg repository under path, recursively.'''
1261 '''yield every hg repository under path, recursively.'''
1259 def errhandler(err):
1262 def errhandler(err):
1260 if err.filename == path:
1263 if err.filename == path:
1261 raise err
1264 raise err
1262
1265
1263 for root, dirs, files in os.walk(path, onerror=errhandler):
1266 for root, dirs, files in os.walk(path, onerror=errhandler):
1264 for d in dirs:
1267 for d in dirs:
1265 if d == '.hg':
1268 if d == '.hg':
1266 yield root
1269 yield root
1267 dirs[:] = []
1270 dirs[:] = []
1268 break
1271 break
1269
1272
1270 _rcpath = None
1273 _rcpath = None
1271
1274
1272 def rcpath():
1275 def rcpath():
1273 '''return hgrc search path. if env var HGRCPATH is set, use it.
1276 '''return hgrc search path. if env var HGRCPATH is set, use it.
1274 for each item in path, if directory, use files ending in .rc,
1277 for each item in path, if directory, use files ending in .rc,
1275 else use item.
1278 else use item.
1276 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1279 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1277 if no HGRCPATH, use default os-specific path.'''
1280 if no HGRCPATH, use default os-specific path.'''
1278 global _rcpath
1281 global _rcpath
1279 if _rcpath is None:
1282 if _rcpath is None:
1280 if 'HGRCPATH' in os.environ:
1283 if 'HGRCPATH' in os.environ:
1281 _rcpath = []
1284 _rcpath = []
1282 for p in os.environ['HGRCPATH'].split(os.pathsep):
1285 for p in os.environ['HGRCPATH'].split(os.pathsep):
1283 if not p: continue
1286 if not p: continue
1284 if os.path.isdir(p):
1287 if os.path.isdir(p):
1285 for f in os.listdir(p):
1288 for f in os.listdir(p):
1286 if f.endswith('.rc'):
1289 if f.endswith('.rc'):
1287 _rcpath.append(os.path.join(p, f))
1290 _rcpath.append(os.path.join(p, f))
1288 else:
1291 else:
1289 _rcpath.append(p)
1292 _rcpath.append(p)
1290 else:
1293 else:
1291 _rcpath = os_rcpath()
1294 _rcpath = os_rcpath()
1292 return _rcpath
1295 return _rcpath
1293
1296
1294 def bytecount(nbytes):
1297 def bytecount(nbytes):
1295 '''return byte count formatted as readable string, with units'''
1298 '''return byte count formatted as readable string, with units'''
1296
1299
1297 units = (
1300 units = (
1298 (100, 1<<30, _('%.0f GB')),
1301 (100, 1<<30, _('%.0f GB')),
1299 (10, 1<<30, _('%.1f GB')),
1302 (10, 1<<30, _('%.1f GB')),
1300 (1, 1<<30, _('%.2f GB')),
1303 (1, 1<<30, _('%.2f GB')),
1301 (100, 1<<20, _('%.0f MB')),
1304 (100, 1<<20, _('%.0f MB')),
1302 (10, 1<<20, _('%.1f MB')),
1305 (10, 1<<20, _('%.1f MB')),
1303 (1, 1<<20, _('%.2f MB')),
1306 (1, 1<<20, _('%.2f MB')),
1304 (100, 1<<10, _('%.0f KB')),
1307 (100, 1<<10, _('%.0f KB')),
1305 (10, 1<<10, _('%.1f KB')),
1308 (10, 1<<10, _('%.1f KB')),
1306 (1, 1<<10, _('%.2f KB')),
1309 (1, 1<<10, _('%.2f KB')),
1307 (1, 1, _('%.0f bytes')),
1310 (1, 1, _('%.0f bytes')),
1308 )
1311 )
1309
1312
1310 for multiplier, divisor, format in units:
1313 for multiplier, divisor, format in units:
1311 if nbytes >= divisor * multiplier:
1314 if nbytes >= divisor * multiplier:
1312 return format % (nbytes / float(divisor))
1315 return format % (nbytes / float(divisor))
1313 return units[-1][2] % nbytes
1316 return units[-1][2] % nbytes
1314
1317
1315 def drop_scheme(scheme, path):
1318 def drop_scheme(scheme, path):
1316 sc = scheme + ':'
1319 sc = scheme + ':'
1317 if path.startswith(sc):
1320 if path.startswith(sc):
1318 path = path[len(sc):]
1321 path = path[len(sc):]
1319 if path.startswith('//'):
1322 if path.startswith('//'):
1320 path = path[2:]
1323 path = path[2:]
1321 return path
1324 return path
General Comments 0
You need to be logged in to leave comments. Login now