##// END OF EJS Templates
merge with self
Benoit Boissinot -
r1991:a8a618c5 merge default
parent child Browse files
Show More
@@ -0,0 +1,15
1 changeset = '{date|shortdate} {author|person} <{author|email}> ({node|short}{tags})\n\n\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\n'
2 changeset_quiet = '{date|shortdate} {author|person} <{author|email}>\n\n\t* {desc|firstline|fill68|tabindent|strip}\n\n'
3 changeset_verbose = '{date|isodate} {author|person} <{author|email}> ({node|short}{tags})\n\n\t* {file_adds|stringify|fill68|tabindent}{file_dels|stringify|fill68|tabindent}{files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\n'
4 start_tags = ' ['
5 tag = '{tag}, '
6 last_tag = '{tag}]'
7 start_files = '('
8 file = '{file}, '
9 last_file = '{file}):\n\t'
10 start_file_adds = '('
11 file_add = '{file_add}, '
12 last_file_add = '{file_add}): new file.\n* '
13 start_file_dels = '('
14 file_del = '{file_del}, '
15 last_file_del = '{file_del}): deleted file.\n* '
@@ -1,240 +1,304
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 import re
8 import re
9 from demandload import demandload
9 from demandload import demandload
10 from i18n import gettext as _
10 from i18n import gettext as _
11 demandload(globals(), "cStringIO cgi sys os time urllib util")
11 demandload(globals(), "cStringIO cgi re sys os time urllib util textwrap")
12
12
13 esctable = {
13 esctable = {
14 '\\': '\\',
14 '\\': '\\',
15 'r': '\r',
15 'r': '\r',
16 't': '\t',
16 't': '\t',
17 'n': '\n',
17 'n': '\n',
18 'v': '\v',
18 'v': '\v',
19 }
19 }
20
20
21 def parsestring(s, quoted=True):
21 def parsestring(s, quoted=True):
22 '''parse a string using simple c-like syntax.
22 '''parse a string using simple c-like syntax.
23 string must be in quotes if quoted is True.'''
23 string must be in quotes if quoted is True.'''
24 fp = cStringIO.StringIO()
24 fp = cStringIO.StringIO()
25 if quoted:
25 if quoted:
26 first = s[0]
26 first = s[0]
27 if len(s) < 2: raise SyntaxError(_('string too short'))
27 if len(s) < 2: raise SyntaxError(_('string too short'))
28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
28 if first not in "'\"": raise SyntaxError(_('invalid quote'))
29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
29 if s[-1] != first: raise SyntaxError(_('unmatched quotes'))
30 s = s[1:-1]
30 s = s[1:-1]
31 escape = False
31 escape = False
32 for c in s:
32 for c in s:
33 if escape:
33 if escape:
34 fp.write(esctable.get(c, c))
34 fp.write(esctable.get(c, c))
35 escape = False
35 escape = False
36 elif c == '\\': escape = True
36 elif c == '\\': escape = True
37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
37 elif quoted and c == first: raise SyntaxError(_('string ends early'))
38 else: fp.write(c)
38 else: fp.write(c)
39 if escape: raise SyntaxError(_('unterminated escape'))
39 if escape: raise SyntaxError(_('unterminated escape'))
40 return fp.getvalue()
40 return fp.getvalue()
41
41
42 class templater(object):
42 class templater(object):
43 '''template expansion engine.
43 '''template expansion engine.
44
44
45 template expansion works like this. a map file contains key=value
45 template expansion works like this. a map file contains key=value
46 pairs. if value is quoted, it is treated as string. otherwise, it
46 pairs. if value is quoted, it is treated as string. otherwise, it
47 is treated as name of template file.
47 is treated as name of template file.
48
48
49 templater is asked to expand a key in map. it looks up key, and
49 templater is asked to expand a key in map. it looks up key, and
50 looks for atrings like this: {foo}. it expands {foo} by looking up
50 looks for atrings like this: {foo}. it expands {foo} by looking up
51 foo in map, and substituting it. expansion is recursive: it stops
51 foo in map, and substituting it. expansion is recursive: it stops
52 when there is no more {foo} to replace.
52 when there is no more {foo} to replace.
53
53
54 expansion also allows formatting and filtering.
54 expansion also allows formatting and filtering.
55
55
56 format uses key to expand each item in list. syntax is
56 format uses key to expand each item in list. syntax is
57 {key%format}.
57 {key%format}.
58
58
59 filter uses function to transform value. syntax is
59 filter uses function to transform value. syntax is
60 {key|filter1|filter2|...}.'''
60 {key|filter1|filter2|...}.'''
61
61
62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
62 def __init__(self, mapfile, filters={}, defaults={}, cache={}):
63 '''set up template engine.
63 '''set up template engine.
64 mapfile is name of file to read map definitions from.
64 mapfile is name of file to read map definitions from.
65 filters is dict of functions. each transforms a value into another.
65 filters is dict of functions. each transforms a value into another.
66 defaults is dict of default map definitions.'''
66 defaults is dict of default map definitions.'''
67 self.mapfile = mapfile or 'template'
67 self.mapfile = mapfile or 'template'
68 self.cache = cache.copy()
68 self.cache = cache.copy()
69 self.map = {}
69 self.map = {}
70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
70 self.base = (mapfile and os.path.dirname(mapfile)) or ''
71 self.filters = filters
71 self.filters = filters
72 self.defaults = defaults
72 self.defaults = defaults
73
73
74 if not mapfile:
74 if not mapfile:
75 return
75 return
76 i = 0
76 i = 0
77 for l in file(mapfile):
77 for l in file(mapfile):
78 l = l.strip()
78 l = l.strip()
79 i += 1
79 i += 1
80 if not l or l[0] in '#;': continue
80 if not l or l[0] in '#;': continue
81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
81 m = re.match(r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', l)
82 if m:
82 if m:
83 key, val = m.groups()
83 key, val = m.groups()
84 if val[0] in "'\"":
84 if val[0] in "'\"":
85 try:
85 try:
86 self.cache[key] = parsestring(val)
86 self.cache[key] = parsestring(val)
87 except SyntaxError, inst:
87 except SyntaxError, inst:
88 raise SyntaxError('%s:%s: %s' %
88 raise SyntaxError('%s:%s: %s' %
89 (mapfile, i, inst.args[0]))
89 (mapfile, i, inst.args[0]))
90 else:
90 else:
91 self.map[key] = os.path.join(self.base, val)
91 self.map[key] = os.path.join(self.base, val)
92 else:
92 else:
93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
93 raise SyntaxError(_("%s:%s: parse error") % (mapfile, i))
94
94
95 def __contains__(self, key):
95 def __contains__(self, key):
96 return key in self.cache
96 return key in self.cache
97
97
98 def __call__(self, t, **map):
98 def __call__(self, t, **map):
99 '''perform expansion.
99 '''perform expansion.
100 t is name of map element to expand.
100 t is name of map element to expand.
101 map is added elements to use during expansion.'''
101 map is added elements to use during expansion.'''
102 m = self.defaults.copy()
102 m = self.defaults.copy()
103 m.update(map)
103 m.update(map)
104 try:
104 try:
105 tmpl = self.cache[t]
105 tmpl = self.cache[t]
106 except KeyError:
106 except KeyError:
107 try:
107 try:
108 tmpl = self.cache[t] = file(self.map[t]).read()
108 tmpl = self.cache[t] = file(self.map[t]).read()
109 except IOError, inst:
109 except IOError, inst:
110 raise IOError(inst.args[0], _('template file %s: %s') %
110 raise IOError(inst.args[0], _('template file %s: %s') %
111 (self.map[t], inst.args[1]))
111 (self.map[t], inst.args[1]))
112 return self.template(tmpl, self.filters, **m)
112 return self.template(tmpl, self.filters, **m)
113
113
114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
114 template_re = re.compile(r"[#{]([a-zA-Z_][a-zA-Z0-9_]*)"
115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
115 r"((%[a-zA-Z_][a-zA-Z0-9_]*)*)"
116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
116 r"((\|[a-zA-Z_][a-zA-Z0-9_]*)*)[#}]")
117
117
118 def template(self, tmpl, filters={}, **map):
118 def template(self, tmpl, filters={}, **map):
119 lm = map.copy()
119 lm = map.copy()
120 while tmpl:
120 while tmpl:
121 m = self.template_re.search(tmpl)
121 m = self.template_re.search(tmpl)
122 if m:
122 if m:
123 start, end = m.span(0)
123 start, end = m.span(0)
124 s, e = tmpl[start], tmpl[end - 1]
124 s, e = tmpl[start], tmpl[end - 1]
125 key = m.group(1)
125 key = m.group(1)
126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
126 if ((s == '#' and e != '#') or (s == '{' and e != '}')):
127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
127 raise SyntaxError(_("'%s'/'%s' mismatch expanding '%s'") %
128 (s, e, key))
128 (s, e, key))
129 if start:
129 if start:
130 yield tmpl[:start]
130 yield tmpl[:start]
131 v = map.get(key, "")
131 v = map.get(key, "")
132 v = callable(v) and v(**map) or v
132 v = callable(v) and v(**map) or v
133
133
134 format = m.group(2)
134 format = m.group(2)
135 fl = m.group(4)
135 fl = m.group(4)
136
136
137 if format:
137 if format:
138 q = v.__iter__
138 q = v.__iter__
139 for i in q():
139 for i in q():
140 lm.update(i)
140 lm.update(i)
141 yield self(format[1:], **lm)
141 yield self(format[1:], **lm)
142
142
143 v = ""
143 v = ""
144
144
145 elif fl:
145 elif fl:
146 for f in fl.split("|")[1:]:
146 for f in fl.split("|")[1:]:
147 v = filters[f](v)
147 v = filters[f](v)
148
148
149 yield v
149 yield v
150 tmpl = tmpl[end:]
150 tmpl = tmpl[end:]
151 else:
151 else:
152 yield tmpl
152 yield tmpl
153 break
153 break
154
154
155 agescales = [("second", 1),
155 agescales = [("second", 1),
156 ("minute", 60),
156 ("minute", 60),
157 ("hour", 3600),
157 ("hour", 3600),
158 ("day", 3600 * 24),
158 ("day", 3600 * 24),
159 ("week", 3600 * 24 * 7),
159 ("week", 3600 * 24 * 7),
160 ("month", 3600 * 24 * 30),
160 ("month", 3600 * 24 * 30),
161 ("year", 3600 * 24 * 365)]
161 ("year", 3600 * 24 * 365)]
162
162
163 agescales.reverse()
163 agescales.reverse()
164
164
165 def age(date):
165 def age(date):
166 '''turn a (timestamp, tzoff) tuple into an age string.'''
166 '''turn a (timestamp, tzoff) tuple into an age string.'''
167
167
168 def plural(t, c):
168 def plural(t, c):
169 if c == 1:
169 if c == 1:
170 return t
170 return t
171 return t + "s"
171 return t + "s"
172 def fmt(t, c):
172 def fmt(t, c):
173 return "%d %s" % (c, plural(t, c))
173 return "%d %s" % (c, plural(t, c))
174
174
175 now = time.time()
175 now = time.time()
176 then = date[0]
176 then = date[0]
177 delta = max(1, int(now - then))
177 delta = max(1, int(now - then))
178
178
179 for t, s in agescales:
179 for t, s in agescales:
180 n = delta / s
180 n = delta / s
181 if n >= 2 or s == 1:
181 if n >= 2 or s == 1:
182 return fmt(t, n)
182 return fmt(t, n)
183
183
184 def stringify(thing):
185 '''turn nested template iterator into string.'''
186 cs = cStringIO.StringIO()
187 def walk(things):
188 for t in things:
189 if hasattr(t, '__iter__'):
190 walk(t)
191 else:
192 cs.write(t)
193 walk(thing)
194 return cs.getvalue()
195
196 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
197 space_re = re.compile(r' +')
198
199 def fill(text, width):
200 '''fill many paragraphs.'''
201 def findparas():
202 start = 0
203 while True:
204 m = para_re.search(text, start)
205 if not m:
206 w = len(text)
207 while w > start and text[w-1].isspace(): w -= 1
208 yield text[start:w], text[w:]
209 break
210 yield text[start:m.start(0)], m.group(1)
211 start = m.end(1)
212
213 fp = cStringIO.StringIO()
214 for para, rest in findparas():
215 fp.write(space_re.sub(' ', textwrap.fill(para, width)))
216 fp.write(rest)
217 return fp.getvalue()
218
184 def isodate(date):
219 def isodate(date):
185 '''turn a (timestamp, tzoff) tuple into an iso 8631 date.'''
220 '''turn a (timestamp, tzoff) tuple into an iso 8631 date and time.'''
186 return util.datestr(date, format='%Y-%m-%d %H:%M')
221 return util.datestr(date, format='%Y-%m-%d %H:%M')
187
222
188 def nl2br(text):
223 def nl2br(text):
189 '''replace raw newlines with xhtml line breaks.'''
224 '''replace raw newlines with xhtml line breaks.'''
190 return text.replace('\n', '<br/>\n')
225 return text.replace('\n', '<br/>\n')
191
226
192 def obfuscate(text):
227 def obfuscate(text):
193 return ''.join(['&#%d;' % ord(c) for c in text])
228 return ''.join(['&#%d;' % ord(c) for c in text])
194
229
195 def domain(author):
230 def domain(author):
196 '''get domain of author, or empty string if none.'''
231 '''get domain of author, or empty string if none.'''
197 f = author.find('@')
232 f = author.find('@')
198 if f == -1: return ''
233 if f == -1: return ''
199 author = author[f+1:]
234 author = author[f+1:]
200 f = author.find('>')
235 f = author.find('>')
201 if f >= 0: author = author[:f]
236 if f >= 0: author = author[:f]
202 return author
237 return author
203
238
239 def email(author):
240 '''get email of author.'''
241 r = author.find('>')
242 if r == -1: r = None
243 return author[author.find('<')+1:r]
244
204 def person(author):
245 def person(author):
205 '''get name of author, or else username.'''
246 '''get name of author, or else username.'''
206 f = author.find('<')
247 f = author.find('<')
207 if f == -1: return util.shortuser(author)
248 if f == -1: return util.shortuser(author)
208 return author[:f].rstrip()
249 return author[:f].rstrip()
209
250
251 def shortdate(date):
252 '''turn (timestamp, tzoff) tuple into iso 8631 date.'''
253 return util.datestr(date, format='%Y-%m-%d', timezone=False)
254
255 def indent(text, prefix):
256 '''indent each non-empty line of text after first with prefix.'''
257 fp = cStringIO.StringIO()
258 lines = text.splitlines()
259 num_lines = len(lines)
260 for i in xrange(num_lines):
261 l = lines[i]
262 if i and l.strip(): fp.write(prefix)
263 fp.write(l)
264 if i < num_lines - 1 or text.endswith('\n'):
265 fp.write('\n')
266 return fp.getvalue()
267
210 common_filters = {
268 common_filters = {
211 "addbreaks": nl2br,
269 "addbreaks": nl2br,
212 "age": age,
270 "age": age,
213 "date": lambda x: util.datestr(x),
271 "date": lambda x: util.datestr(x),
214 "domain": domain,
272 "domain": domain,
273 "email": email,
215 "escape": lambda x: cgi.escape(x, True),
274 "escape": lambda x: cgi.escape(x, True),
275 "fill68": lambda x: fill(x, width=68),
276 "fill76": lambda x: fill(x, width=76),
216 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
277 "firstline": lambda x: x.splitlines(1)[0].rstrip('\r\n'),
278 "tabindent": lambda x: indent(x, '\t'),
217 "isodate": isodate,
279 "isodate": isodate,
218 "obfuscate": obfuscate,
280 "obfuscate": obfuscate,
219 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
281 "permissions": lambda x: x and "-rwxr-xr-x" or "-rw-r--r--",
220 "person": person,
282 "person": person,
221 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
283 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"),
222 "short": lambda x: x[:12],
284 "short": lambda x: x[:12],
285 "shortdate": shortdate,
286 "stringify": stringify,
223 "strip": lambda x: x.strip(),
287 "strip": lambda x: x.strip(),
224 "urlescape": lambda x: urllib.quote(x),
288 "urlescape": lambda x: urllib.quote(x),
225 "user": lambda x: util.shortuser(x),
289 "user": lambda x: util.shortuser(x),
226 }
290 }
227
291
228 def templatepath(name=None):
292 def templatepath(name=None):
229 '''return location of template file or directory (if no name).
293 '''return location of template file or directory (if no name).
230 returns None if not found.'''
294 returns None if not found.'''
231 for f in 'templates', '../templates':
295 for f in 'templates', '../templates':
232 fl = f.split('/')
296 fl = f.split('/')
233 if name: fl.append(name)
297 if name: fl.append(name)
234 p = os.path.join(os.path.dirname(__file__), *fl)
298 p = os.path.join(os.path.dirname(__file__), *fl)
235 if (name and os.path.exists(p)) or os.path.isdir(p):
299 if (name and os.path.exists(p)) or os.path.isdir(p):
236 return os.path.normpath(p)
300 return os.path.normpath(p)
237 else:
301 else:
238 # executable version (py2exe) doesn't support __file__
302 # executable version (py2exe) doesn't support __file__
239 if hasattr(sys, 'frozen'):
303 if hasattr(sys, 'frozen'):
240 return os.path.join(sys.prefix, "templates")
304 return os.path.join(sys.prefix, "templates")
@@ -1,233 +1,237
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005 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 import ConfigParser
8 import ConfigParser
9 from i18n import gettext as _
9 from i18n import gettext as _
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "os re socket sys util tempfile")
11 demandload(globals(), "errno os re socket sys tempfile util")
12
12
13 class ui(object):
13 class ui(object):
14 def __init__(self, verbose=False, debug=False, quiet=False,
14 def __init__(self, verbose=False, debug=False, quiet=False,
15 interactive=True, parentui=None):
15 interactive=True, parentui=None):
16 self.overlay = {}
16 self.overlay = {}
17 if parentui is None:
17 if parentui is None:
18 # this is the parent of all ui children
18 # this is the parent of all ui children
19 self.parentui = None
19 self.parentui = None
20 self.cdata = ConfigParser.SafeConfigParser()
20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath())
21 self.readconfig(util.rcpath())
22
22
23 self.quiet = self.configbool("ui", "quiet")
23 self.quiet = self.configbool("ui", "quiet")
24 self.verbose = self.configbool("ui", "verbose")
24 self.verbose = self.configbool("ui", "verbose")
25 self.debugflag = self.configbool("ui", "debug")
25 self.debugflag = self.configbool("ui", "debug")
26 self.interactive = self.configbool("ui", "interactive", True)
26 self.interactive = self.configbool("ui", "interactive", True)
27
27
28 self.updateopts(verbose, debug, quiet, interactive)
28 self.updateopts(verbose, debug, quiet, interactive)
29 self.diffcache = None
29 self.diffcache = None
30 else:
30 else:
31 # parentui may point to an ui object which is already a child
31 # parentui may point to an ui object which is already a child
32 self.parentui = parentui.parentui or parentui
32 self.parentui = parentui.parentui or parentui
33 parent_cdata = self.parentui.cdata
33 parent_cdata = self.parentui.cdata
34 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
34 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
35 # make interpolation work
35 # make interpolation work
36 for section in parent_cdata.sections():
36 for section in parent_cdata.sections():
37 self.cdata.add_section(section)
37 self.cdata.add_section(section)
38 for name, value in parent_cdata.items(section, raw=True):
38 for name, value in parent_cdata.items(section, raw=True):
39 self.cdata.set(section, name, value)
39 self.cdata.set(section, name, value)
40
40
41 def __getattr__(self, key):
41 def __getattr__(self, key):
42 return getattr(self.parentui, key)
42 return getattr(self.parentui, key)
43
43
44 def updateopts(self, verbose=False, debug=False, quiet=False,
44 def updateopts(self, verbose=False, debug=False, quiet=False,
45 interactive=True):
45 interactive=True):
46 self.quiet = (self.quiet or quiet) and not verbose and not debug
46 self.quiet = (self.quiet or quiet) and not verbose and not debug
47 self.verbose = (self.verbose or verbose) or debug
47 self.verbose = (self.verbose or verbose) or debug
48 self.debugflag = (self.debugflag or debug)
48 self.debugflag = (self.debugflag or debug)
49 self.interactive = (self.interactive and interactive)
49 self.interactive = (self.interactive and interactive)
50
50
51 def readconfig(self, fn, root=None):
51 def readconfig(self, fn, root=None):
52 if isinstance(fn, basestring):
52 if isinstance(fn, basestring):
53 fn = [fn]
53 fn = [fn]
54 for f in fn:
54 for f in fn:
55 try:
55 try:
56 self.cdata.read(f)
56 self.cdata.read(f)
57 except ConfigParser.ParsingError, inst:
57 except ConfigParser.ParsingError, inst:
58 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
58 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
59 # translate paths relative to root (or home) into absolute paths
59 # translate paths relative to root (or home) into absolute paths
60 if root is None:
60 if root is None:
61 root = os.path.expanduser('~')
61 root = os.path.expanduser('~')
62 for name, path in self.configitems("paths"):
62 for name, path in self.configitems("paths"):
63 if path and path.find("://") == -1 and not os.path.isabs(path):
63 if path and path.find("://") == -1 and not os.path.isabs(path):
64 self.cdata.set("paths", name, os.path.join(root, path))
64 self.cdata.set("paths", name, os.path.join(root, path))
65
65
66 def setconfig(self, section, name, val):
66 def setconfig(self, section, name, val):
67 self.overlay[(section, name)] = val
67 self.overlay[(section, name)] = val
68
68
69 def config(self, section, name, default=None):
69 def config(self, section, name, default=None):
70 if self.overlay.has_key((section, name)):
70 if self.overlay.has_key((section, name)):
71 return self.overlay[(section, name)]
71 return self.overlay[(section, name)]
72 if self.cdata.has_option(section, name):
72 if self.cdata.has_option(section, name):
73 try:
73 try:
74 return self.cdata.get(section, name)
74 return self.cdata.get(section, name)
75 except ConfigParser.InterpolationError, inst:
75 except ConfigParser.InterpolationError, inst:
76 raise util.Abort(_("Error in configuration:\n%s") % inst)
76 raise util.Abort(_("Error in configuration:\n%s") % inst)
77 if self.parentui is None:
77 if self.parentui is None:
78 return default
78 return default
79 else:
79 else:
80 return self.parentui.config(section, name, default)
80 return self.parentui.config(section, name, default)
81
81
82 def configbool(self, section, name, default=False):
82 def configbool(self, section, name, default=False):
83 if self.overlay.has_key((section, name)):
83 if self.overlay.has_key((section, name)):
84 return self.overlay[(section, name)]
84 return self.overlay[(section, name)]
85 if self.cdata.has_option(section, name):
85 if self.cdata.has_option(section, name):
86 try:
86 try:
87 return self.cdata.getboolean(section, name)
87 return self.cdata.getboolean(section, name)
88 except ConfigParser.InterpolationError, inst:
88 except ConfigParser.InterpolationError, inst:
89 raise util.Abort(_("Error in configuration:\n%s") % inst)
89 raise util.Abort(_("Error in configuration:\n%s") % inst)
90 if self.parentui is None:
90 if self.parentui is None:
91 return default
91 return default
92 else:
92 else:
93 return self.parentui.configbool(section, name, default)
93 return self.parentui.configbool(section, name, default)
94
94
95 def configitems(self, section):
95 def configitems(self, section):
96 items = {}
96 items = {}
97 if self.parentui is not None:
97 if self.parentui is not None:
98 items = dict(self.parentui.configitems(section))
98 items = dict(self.parentui.configitems(section))
99 if self.cdata.has_section(section):
99 if self.cdata.has_section(section):
100 try:
100 try:
101 items.update(dict(self.cdata.items(section)))
101 items.update(dict(self.cdata.items(section)))
102 except ConfigParser.InterpolationError, inst:
102 except ConfigParser.InterpolationError, inst:
103 raise util.Abort(_("Error in configuration:\n%s") % inst)
103 raise util.Abort(_("Error in configuration:\n%s") % inst)
104 x = items.items()
104 x = items.items()
105 x.sort()
105 x.sort()
106 return x
106 return x
107
107
108 def walkconfig(self, seen=None):
108 def walkconfig(self, seen=None):
109 if seen is None:
109 if seen is None:
110 seen = {}
110 seen = {}
111 for (section, name), value in self.overlay.iteritems():
111 for (section, name), value in self.overlay.iteritems():
112 yield section, name, value
112 yield section, name, value
113 seen[section, name] = 1
113 seen[section, name] = 1
114 for section in self.cdata.sections():
114 for section in self.cdata.sections():
115 for name, value in self.cdata.items(section):
115 for name, value in self.cdata.items(section):
116 if (section, name) in seen: continue
116 if (section, name) in seen: continue
117 yield section, name, value.replace('\n', '\\n')
117 yield section, name, value.replace('\n', '\\n')
118 seen[section, name] = 1
118 seen[section, name] = 1
119 if self.parentui is not None:
119 if self.parentui is not None:
120 for parent in self.parentui.walkconfig(seen):
120 for parent in self.parentui.walkconfig(seen):
121 yield parent
121 yield parent
122
122
123 def extensions(self):
123 def extensions(self):
124 return self.configitems("extensions")
124 return self.configitems("extensions")
125
125
126 def diffopts(self):
126 def diffopts(self):
127 if self.diffcache:
127 if self.diffcache:
128 return self.diffcache
128 return self.diffcache
129 ret = { 'showfunc' : True, 'ignorews' : False}
129 ret = { 'showfunc' : True, 'ignorews' : False}
130 for x in self.configitems("diff"):
130 for x in self.configitems("diff"):
131 k = x[0].lower()
131 k = x[0].lower()
132 v = x[1]
132 v = x[1]
133 if v:
133 if v:
134 v = v.lower()
134 v = v.lower()
135 if v == 'true':
135 if v == 'true':
136 value = True
136 value = True
137 else:
137 else:
138 value = False
138 value = False
139 ret[k] = value
139 ret[k] = value
140 self.diffcache = ret
140 self.diffcache = ret
141 return ret
141 return ret
142
142
143 def username(self):
143 def username(self):
144 """Return default username to be used in commits.
144 """Return default username to be used in commits.
145
145
146 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
146 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
147 and stop searching if one of these is set.
147 and stop searching if one of these is set.
148 Abort if found username is an empty string to force specifying
148 Abort if found username is an empty string to force specifying
149 the commit user elsewhere, e.g. with line option or repo hgrc.
149 the commit user elsewhere, e.g. with line option or repo hgrc.
150 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
150 If not found, use $LOGNAME or $USERNAME +"@full.hostname".
151 """
151 """
152 user = os.environ.get("HGUSER")
152 user = os.environ.get("HGUSER")
153 if user is None:
153 if user is None:
154 user = self.config("ui", "username")
154 user = self.config("ui", "username")
155 if user is None:
155 if user is None:
156 user = os.environ.get("EMAIL")
156 user = os.environ.get("EMAIL")
157 if user is None:
157 if user is None:
158 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
158 user = os.environ.get("LOGNAME") or os.environ.get("USERNAME")
159 if user:
159 if user:
160 user = "%s@%s" % (user, socket.getfqdn())
160 user = "%s@%s" % (user, socket.getfqdn())
161 if not user:
161 if not user:
162 raise util.Abort(_("Please specify a username."))
162 raise util.Abort(_("Please specify a username."))
163 return user
163 return user
164
164
165 def shortuser(self, user):
165 def shortuser(self, user):
166 """Return a short representation of a user name or email address."""
166 """Return a short representation of a user name or email address."""
167 if not self.verbose: user = util.shortuser(user)
167 if not self.verbose: user = util.shortuser(user)
168 return user
168 return user
169
169
170 def expandpath(self, loc):
170 def expandpath(self, loc):
171 """Return repository location relative to cwd or from [paths]"""
171 """Return repository location relative to cwd or from [paths]"""
172 if loc.find("://") != -1 or os.path.exists(loc):
172 if loc.find("://") != -1 or os.path.exists(loc):
173 return loc
173 return loc
174
174
175 return self.config("paths", loc, loc)
175 return self.config("paths", loc, loc)
176
176
177 def write(self, *args):
177 def write(self, *args):
178 for a in args:
178 for a in args:
179 sys.stdout.write(str(a))
179 sys.stdout.write(str(a))
180
180
181 def write_err(self, *args):
181 def write_err(self, *args):
182 if not sys.stdout.closed: sys.stdout.flush()
182 try:
183 for a in args:
183 if not sys.stdout.closed: sys.stdout.flush()
184 sys.stderr.write(str(a))
184 for a in args:
185 sys.stderr.write(str(a))
186 except IOError, inst:
187 if inst.errno != errno.EPIPE:
188 raise
185
189
186 def flush(self):
190 def flush(self):
187 try:
191 try:
188 sys.stdout.flush()
192 sys.stdout.flush()
189 finally:
193 finally:
190 sys.stderr.flush()
194 sys.stderr.flush()
191
195
192 def readline(self):
196 def readline(self):
193 return sys.stdin.readline()[:-1]
197 return sys.stdin.readline()[:-1]
194 def prompt(self, msg, pat, default="y"):
198 def prompt(self, msg, pat, default="y"):
195 if not self.interactive: return default
199 if not self.interactive: return default
196 while 1:
200 while 1:
197 self.write(msg, " ")
201 self.write(msg, " ")
198 r = self.readline()
202 r = self.readline()
199 if re.match(pat, r):
203 if re.match(pat, r):
200 return r
204 return r
201 else:
205 else:
202 self.write(_("unrecognized response\n"))
206 self.write(_("unrecognized response\n"))
203 def status(self, *msg):
207 def status(self, *msg):
204 if not self.quiet: self.write(*msg)
208 if not self.quiet: self.write(*msg)
205 def warn(self, *msg):
209 def warn(self, *msg):
206 self.write_err(*msg)
210 self.write_err(*msg)
207 def note(self, *msg):
211 def note(self, *msg):
208 if self.verbose: self.write(*msg)
212 if self.verbose: self.write(*msg)
209 def debug(self, *msg):
213 def debug(self, *msg):
210 if self.debugflag: self.write(*msg)
214 if self.debugflag: self.write(*msg)
211 def edit(self, text, user):
215 def edit(self, text, user):
212 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
216 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt")
213 try:
217 try:
214 f = os.fdopen(fd, "w")
218 f = os.fdopen(fd, "w")
215 f.write(text)
219 f.write(text)
216 f.close()
220 f.close()
217
221
218 editor = (os.environ.get("HGEDITOR") or
222 editor = (os.environ.get("HGEDITOR") or
219 self.config("ui", "editor") or
223 self.config("ui", "editor") or
220 os.environ.get("EDITOR", "vi"))
224 os.environ.get("EDITOR", "vi"))
221
225
222 util.system("%s \"%s\"" % (editor, name),
226 util.system("%s \"%s\"" % (editor, name),
223 environ={'HGUSER': user},
227 environ={'HGUSER': user},
224 onerr=util.Abort, errprefix=_("edit failed"))
228 onerr=util.Abort, errprefix=_("edit failed"))
225
229
226 f = open(name)
230 f = open(name)
227 t = f.read()
231 t = f.read()
228 f.close()
232 f.close()
229 t = re.sub("(?m)^HG:.*\n", "", t)
233 t = re.sub("(?m)^HG:.*\n", "", t)
230 finally:
234 finally:
231 os.unlink(name)
235 os.unlink(name)
232
236
233 return t
237 return t
@@ -1,809 +1,810
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
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8
8
9 This contains helper routines that are independent of the SCM core and hide
9 This contains helper routines that are independent of the SCM core and hide
10 platform-specific details from the core.
10 platform-specific details from the core.
11 """
11 """
12
12
13 import os, errno
13 import os, errno
14 from i18n import gettext as _
14 from i18n import gettext as _
15 from demandload import *
15 from demandload import *
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 demandload(globals(), "threading time")
17 demandload(globals(), "threading time")
18
18
19 def pipefilter(s, cmd):
19 def pipefilter(s, cmd):
20 '''filter string S through command CMD, returning its output'''
20 '''filter string S through command CMD, returning its output'''
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
22 def writer():
22 def writer():
23 pin.write(s)
23 pin.write(s)
24 pin.close()
24 pin.close()
25
25
26 # we should use select instead on UNIX, but this will work on most
26 # we should use select instead on UNIX, but this will work on most
27 # systems, including Windows
27 # systems, including Windows
28 w = threading.Thread(target=writer)
28 w = threading.Thread(target=writer)
29 w.start()
29 w.start()
30 f = pout.read()
30 f = pout.read()
31 pout.close()
31 pout.close()
32 w.join()
32 w.join()
33 return f
33 return f
34
34
35 def tempfilter(s, cmd):
35 def tempfilter(s, cmd):
36 '''filter string S through a pair of temporary files with CMD.
36 '''filter string S through a pair of temporary files with CMD.
37 CMD is used as a template to create the real command to be run,
37 CMD is used as a template to create the real command to be run,
38 with the strings INFILE and OUTFILE replaced by the real names of
38 with the strings INFILE and OUTFILE replaced by the real names of
39 the temporary files generated.'''
39 the temporary files generated.'''
40 inname, outname = None, None
40 inname, outname = None, None
41 try:
41 try:
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
42 infd, inname = tempfile.mkstemp(prefix='hgfin')
43 fp = os.fdopen(infd, 'wb')
43 fp = os.fdopen(infd, 'wb')
44 fp.write(s)
44 fp.write(s)
45 fp.close()
45 fp.close()
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
47 os.close(outfd)
47 os.close(outfd)
48 cmd = cmd.replace('INFILE', inname)
48 cmd = cmd.replace('INFILE', inname)
49 cmd = cmd.replace('OUTFILE', outname)
49 cmd = cmd.replace('OUTFILE', outname)
50 code = os.system(cmd)
50 code = os.system(cmd)
51 if code: raise Abort(_("command '%s' failed: %s") %
51 if code: raise Abort(_("command '%s' failed: %s") %
52 (cmd, explain_exit(code)))
52 (cmd, explain_exit(code)))
53 return open(outname, 'rb').read()
53 return open(outname, 'rb').read()
54 finally:
54 finally:
55 try:
55 try:
56 if inname: os.unlink(inname)
56 if inname: os.unlink(inname)
57 except: pass
57 except: pass
58 try:
58 try:
59 if outname: os.unlink(outname)
59 if outname: os.unlink(outname)
60 except: pass
60 except: pass
61
61
62 filtertable = {
62 filtertable = {
63 'tempfile:': tempfilter,
63 'tempfile:': tempfilter,
64 'pipe:': pipefilter,
64 'pipe:': pipefilter,
65 }
65 }
66
66
67 def filter(s, cmd):
67 def filter(s, cmd):
68 "filter a string through a command that transforms its input to its output"
68 "filter a string through a command that transforms its input to its output"
69 for name, fn in filtertable.iteritems():
69 for name, fn in filtertable.iteritems():
70 if cmd.startswith(name):
70 if cmd.startswith(name):
71 return fn(s, cmd[len(name):].lstrip())
71 return fn(s, cmd[len(name):].lstrip())
72 return pipefilter(s, cmd)
72 return pipefilter(s, cmd)
73
73
74 def patch(strip, patchname, ui):
74 def patch(strip, patchname, ui):
75 """apply the patch <patchname> to the working directory.
75 """apply the patch <patchname> to the working directory.
76 a list of patched files is returned"""
76 a list of patched files is returned"""
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
78 files = {}
78 files = {}
79 for line in fp:
79 for line in fp:
80 line = line.rstrip()
80 line = line.rstrip()
81 ui.status("%s\n" % line)
81 ui.status("%s\n" % line)
82 if line.startswith('patching file '):
82 if line.startswith('patching file '):
83 pf = parse_patch_output(line)
83 pf = parse_patch_output(line)
84 files.setdefault(pf, 1)
84 files.setdefault(pf, 1)
85 code = fp.close()
85 code = fp.close()
86 if code:
86 if code:
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
88 return files.keys()
88 return files.keys()
89
89
90 def binary(s):
90 def binary(s):
91 """return true if a string is binary data using diff's heuristic"""
91 """return true if a string is binary data using diff's heuristic"""
92 if s and '\0' in s[:4096]:
92 if s and '\0' in s[:4096]:
93 return True
93 return True
94 return False
94 return False
95
95
96 def unique(g):
96 def unique(g):
97 """return the uniq elements of iterable g"""
97 """return the uniq elements of iterable g"""
98 seen = {}
98 seen = {}
99 for f in g:
99 for f in g:
100 if f not in seen:
100 if f not in seen:
101 seen[f] = 1
101 seen[f] = 1
102 yield f
102 yield f
103
103
104 class Abort(Exception):
104 class Abort(Exception):
105 """Raised if a command needs to print an error and exit."""
105 """Raised if a command needs to print an error and exit."""
106
106
107 def always(fn): return True
107 def always(fn): return True
108 def never(fn): return False
108 def never(fn): return False
109
109
110 def patkind(name, dflt_pat='glob'):
110 def patkind(name, dflt_pat='glob'):
111 """Split a string into an optional pattern kind prefix and the
111 """Split a string into an optional pattern kind prefix and the
112 actual pattern."""
112 actual pattern."""
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
114 if name.startswith(prefix + ':'): return name.split(':', 1)
114 if name.startswith(prefix + ':'): return name.split(':', 1)
115 return dflt_pat, name
115 return dflt_pat, name
116
116
117 def globre(pat, head='^', tail='$'):
117 def globre(pat, head='^', tail='$'):
118 "convert a glob pattern into a regexp"
118 "convert a glob pattern into a regexp"
119 i, n = 0, len(pat)
119 i, n = 0, len(pat)
120 res = ''
120 res = ''
121 group = False
121 group = False
122 def peek(): return i < n and pat[i]
122 def peek(): return i < n and pat[i]
123 while i < n:
123 while i < n:
124 c = pat[i]
124 c = pat[i]
125 i = i+1
125 i = i+1
126 if c == '*':
126 if c == '*':
127 if peek() == '*':
127 if peek() == '*':
128 i += 1
128 i += 1
129 res += '.*'
129 res += '.*'
130 else:
130 else:
131 res += '[^/]*'
131 res += '[^/]*'
132 elif c == '?':
132 elif c == '?':
133 res += '.'
133 res += '.'
134 elif c == '[':
134 elif c == '[':
135 j = i
135 j = i
136 if j < n and pat[j] in '!]':
136 if j < n and pat[j] in '!]':
137 j += 1
137 j += 1
138 while j < n and pat[j] != ']':
138 while j < n and pat[j] != ']':
139 j += 1
139 j += 1
140 if j >= n:
140 if j >= n:
141 res += '\\['
141 res += '\\['
142 else:
142 else:
143 stuff = pat[i:j].replace('\\','\\\\')
143 stuff = pat[i:j].replace('\\','\\\\')
144 i = j + 1
144 i = j + 1
145 if stuff[0] == '!':
145 if stuff[0] == '!':
146 stuff = '^' + stuff[1:]
146 stuff = '^' + stuff[1:]
147 elif stuff[0] == '^':
147 elif stuff[0] == '^':
148 stuff = '\\' + stuff
148 stuff = '\\' + stuff
149 res = '%s[%s]' % (res, stuff)
149 res = '%s[%s]' % (res, stuff)
150 elif c == '{':
150 elif c == '{':
151 group = True
151 group = True
152 res += '(?:'
152 res += '(?:'
153 elif c == '}' and group:
153 elif c == '}' and group:
154 res += ')'
154 res += ')'
155 group = False
155 group = False
156 elif c == ',' and group:
156 elif c == ',' and group:
157 res += '|'
157 res += '|'
158 elif c == '\\':
158 elif c == '\\':
159 p = peek()
159 p = peek()
160 if p:
160 if p:
161 i += 1
161 i += 1
162 res += re.escape(p)
162 res += re.escape(p)
163 else:
163 else:
164 res += re.escape(c)
164 res += re.escape(c)
165 else:
165 else:
166 res += re.escape(c)
166 res += re.escape(c)
167 return head + res + tail
167 return head + res + tail
168
168
169 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
169 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
170
170
171 def pathto(n1, n2):
171 def pathto(n1, n2):
172 '''return the relative path from one place to another.
172 '''return the relative path from one place to another.
173 this returns a path in the form used by the local filesystem, not hg.'''
173 this returns a path in the form used by the local filesystem, not hg.'''
174 if not n1: return localpath(n2)
174 if not n1: return localpath(n2)
175 a, b = n1.split('/'), n2.split('/')
175 a, b = n1.split('/'), n2.split('/')
176 a.reverse()
176 a.reverse()
177 b.reverse()
177 b.reverse()
178 while a and b and a[-1] == b[-1]:
178 while a and b and a[-1] == b[-1]:
179 a.pop()
179 a.pop()
180 b.pop()
180 b.pop()
181 b.reverse()
181 b.reverse()
182 return os.sep.join((['..'] * len(a)) + b)
182 return os.sep.join((['..'] * len(a)) + b)
183
183
184 def canonpath(root, cwd, myname):
184 def canonpath(root, cwd, myname):
185 """return the canonical path of myname, given cwd and root"""
185 """return the canonical path of myname, given cwd and root"""
186 if root == os.sep:
186 if root == os.sep:
187 rootsep = os.sep
187 rootsep = os.sep
188 else:
188 else:
189 rootsep = root + os.sep
189 rootsep = root + os.sep
190 name = myname
190 name = myname
191 if not name.startswith(os.sep):
191 if not name.startswith(os.sep):
192 name = os.path.join(root, cwd, name)
192 name = os.path.join(root, cwd, name)
193 name = os.path.normpath(name)
193 name = os.path.normpath(name)
194 if name.startswith(rootsep):
194 if name.startswith(rootsep):
195 name = name[len(rootsep):]
195 name = name[len(rootsep):]
196 audit_path(name)
196 audit_path(name)
197 return pconvert(name)
197 return pconvert(name)
198 elif name == root:
198 elif name == root:
199 return ''
199 return ''
200 else:
200 else:
201 raise Abort('%s not under root' % myname)
201 raise Abort('%s not under root' % myname)
202
202
203 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
203 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
204 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
204 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
205
205
206 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
206 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
207 if os.name == 'nt':
207 if os.name == 'nt':
208 dflt_pat = 'glob'
208 dflt_pat = 'glob'
209 else:
209 else:
210 dflt_pat = 'relpath'
210 dflt_pat = 'relpath'
211 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
211 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
212
212
213 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
213 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
214 """build a function to match a set of file patterns
214 """build a function to match a set of file patterns
215
215
216 arguments:
216 arguments:
217 canonroot - the canonical root of the tree you're matching against
217 canonroot - the canonical root of the tree you're matching against
218 cwd - the current working directory, if relevant
218 cwd - the current working directory, if relevant
219 names - patterns to find
219 names - patterns to find
220 inc - patterns to include
220 inc - patterns to include
221 exc - patterns to exclude
221 exc - patterns to exclude
222 head - a regex to prepend to patterns to control whether a match is rooted
222 head - a regex to prepend to patterns to control whether a match is rooted
223
223
224 a pattern is one of:
224 a pattern is one of:
225 'glob:<rooted glob>'
225 'glob:<rooted glob>'
226 're:<rooted regexp>'
226 're:<rooted regexp>'
227 'path:<rooted path>'
227 'path:<rooted path>'
228 'relglob:<relative glob>'
228 'relglob:<relative glob>'
229 'relpath:<relative path>'
229 'relpath:<relative path>'
230 'relre:<relative regexp>'
230 'relre:<relative regexp>'
231 '<rooted path or regexp>'
231 '<rooted path or regexp>'
232
232
233 returns:
233 returns:
234 a 3-tuple containing
234 a 3-tuple containing
235 - list of explicit non-pattern names passed in
235 - list of explicit non-pattern names passed in
236 - a bool match(filename) function
236 - a bool match(filename) function
237 - a bool indicating if any patterns were passed in
237 - a bool indicating if any patterns were passed in
238
238
239 todo:
239 todo:
240 make head regex a rooted bool
240 make head regex a rooted bool
241 """
241 """
242
242
243 def contains_glob(name):
243 def contains_glob(name):
244 for c in name:
244 for c in name:
245 if c in _globchars: return True
245 if c in _globchars: return True
246 return False
246 return False
247
247
248 def regex(kind, name, tail):
248 def regex(kind, name, tail):
249 '''convert a pattern into a regular expression'''
249 '''convert a pattern into a regular expression'''
250 if kind == 're':
250 if kind == 're':
251 return name
251 return name
252 elif kind == 'path':
252 elif kind == 'path':
253 return '^' + re.escape(name) + '(?:/|$)'
253 return '^' + re.escape(name) + '(?:/|$)'
254 elif kind == 'relglob':
254 elif kind == 'relglob':
255 return head + globre(name, '(?:|.*/)', tail)
255 return head + globre(name, '(?:|.*/)', tail)
256 elif kind == 'relpath':
256 elif kind == 'relpath':
257 return head + re.escape(name) + tail
257 return head + re.escape(name) + tail
258 elif kind == 'relre':
258 elif kind == 'relre':
259 if name.startswith('^'):
259 if name.startswith('^'):
260 return name
260 return name
261 return '.*' + name
261 return '.*' + name
262 return head + globre(name, '', tail)
262 return head + globre(name, '', tail)
263
263
264 def matchfn(pats, tail):
264 def matchfn(pats, tail):
265 """build a matching function from a set of patterns"""
265 """build a matching function from a set of patterns"""
266 if not pats:
266 if not pats:
267 return
267 return
268 matches = []
268 matches = []
269 for k, p in pats:
269 for k, p in pats:
270 try:
270 try:
271 pat = '(?:%s)' % regex(k, p, tail)
271 pat = '(?:%s)' % regex(k, p, tail)
272 matches.append(re.compile(pat).match)
272 matches.append(re.compile(pat).match)
273 except re.error:
273 except re.error:
274 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
274 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
275 else: raise Abort("invalid pattern (%s): %s" % (k, p))
275 else: raise Abort("invalid pattern (%s): %s" % (k, p))
276
276
277 def buildfn(text):
277 def buildfn(text):
278 for m in matches:
278 for m in matches:
279 r = m(text)
279 r = m(text)
280 if r:
280 if r:
281 return r
281 return r
282
282
283 return buildfn
283 return buildfn
284
284
285 def globprefix(pat):
285 def globprefix(pat):
286 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
286 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
287 root = []
287 root = []
288 for p in pat.split(os.sep):
288 for p in pat.split(os.sep):
289 if contains_glob(p): break
289 if contains_glob(p): break
290 root.append(p)
290 root.append(p)
291 return '/'.join(root)
291 return '/'.join(root)
292
292
293 pats = []
293 pats = []
294 files = []
294 files = []
295 roots = []
295 roots = []
296 for kind, name in [patkind(p, dflt_pat) for p in names]:
296 for kind, name in [patkind(p, dflt_pat) for p in names]:
297 if kind in ('glob', 'relpath'):
297 if kind in ('glob', 'relpath'):
298 name = canonpath(canonroot, cwd, name)
298 name = canonpath(canonroot, cwd, name)
299 if name == '':
299 if name == '':
300 kind, name = 'glob', '**'
300 kind, name = 'glob', '**'
301 if kind in ('glob', 'path', 're'):
301 if kind in ('glob', 'path', 're'):
302 pats.append((kind, name))
302 pats.append((kind, name))
303 if kind == 'glob':
303 if kind == 'glob':
304 root = globprefix(name)
304 root = globprefix(name)
305 if root: roots.append(root)
305 if root: roots.append(root)
306 elif kind == 'relpath':
306 elif kind == 'relpath':
307 files.append((kind, name))
307 files.append((kind, name))
308 roots.append(name)
308 roots.append(name)
309
309
310 patmatch = matchfn(pats, '$') or always
310 patmatch = matchfn(pats, '$') or always
311 filematch = matchfn(files, '(?:/|$)') or always
311 filematch = matchfn(files, '(?:/|$)') or always
312 incmatch = always
312 incmatch = always
313 if inc:
313 if inc:
314 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
314 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
315 excmatch = lambda fn: False
315 excmatch = lambda fn: False
316 if exc:
316 if exc:
317 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
317 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
318
318
319 return (roots,
319 return (roots,
320 lambda fn: (incmatch(fn) and not excmatch(fn) and
320 lambda fn: (incmatch(fn) and not excmatch(fn) and
321 (fn.endswith('/') or
321 (fn.endswith('/') or
322 (not pats and not files) or
322 (not pats and not files) or
323 (pats and patmatch(fn)) or
323 (pats and patmatch(fn)) or
324 (files and filematch(fn)))),
324 (files and filematch(fn)))),
325 (inc or exc or (pats and pats != [('glob', '**')])) and True)
325 (inc or exc or (pats and pats != [('glob', '**')])) and True)
326
326
327 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
327 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
328 '''enhanced shell command execution.
328 '''enhanced shell command execution.
329 run with environment maybe modified, maybe in different dir.
329 run with environment maybe modified, maybe in different dir.
330
330
331 if command fails and onerr is None, return status. if ui object,
331 if command fails and onerr is None, return status. if ui object,
332 print error message and return status, else raise onerr object as
332 print error message and return status, else raise onerr object as
333 exception.'''
333 exception.'''
334 oldenv = {}
334 oldenv = {}
335 for k in environ:
335 for k in environ:
336 oldenv[k] = os.environ.get(k)
336 oldenv[k] = os.environ.get(k)
337 if cwd is not None:
337 if cwd is not None:
338 oldcwd = os.getcwd()
338 oldcwd = os.getcwd()
339 try:
339 try:
340 for k, v in environ.iteritems():
340 for k, v in environ.iteritems():
341 os.environ[k] = str(v)
341 os.environ[k] = str(v)
342 if cwd is not None and oldcwd != cwd:
342 if cwd is not None and oldcwd != cwd:
343 os.chdir(cwd)
343 os.chdir(cwd)
344 rc = os.system(cmd)
344 rc = os.system(cmd)
345 if rc and onerr:
345 if rc and onerr:
346 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
346 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
347 explain_exit(rc)[0])
347 explain_exit(rc)[0])
348 if errprefix:
348 if errprefix:
349 errmsg = '%s: %s' % (errprefix, errmsg)
349 errmsg = '%s: %s' % (errprefix, errmsg)
350 try:
350 try:
351 onerr.warn(errmsg + '\n')
351 onerr.warn(errmsg + '\n')
352 except AttributeError:
352 except AttributeError:
353 raise onerr(errmsg)
353 raise onerr(errmsg)
354 return rc
354 return rc
355 finally:
355 finally:
356 for k, v in oldenv.iteritems():
356 for k, v in oldenv.iteritems():
357 if v is None:
357 if v is None:
358 del os.environ[k]
358 del os.environ[k]
359 else:
359 else:
360 os.environ[k] = v
360 os.environ[k] = v
361 if cwd is not None and oldcwd != cwd:
361 if cwd is not None and oldcwd != cwd:
362 os.chdir(oldcwd)
362 os.chdir(oldcwd)
363
363
364 def rename(src, dst):
364 def rename(src, dst):
365 """forcibly rename a file"""
365 """forcibly rename a file"""
366 try:
366 try:
367 os.rename(src, dst)
367 os.rename(src, dst)
368 except:
368 except:
369 os.unlink(dst)
369 os.unlink(dst)
370 os.rename(src, dst)
370 os.rename(src, dst)
371
371
372 def unlink(f):
372 def unlink(f):
373 """unlink and remove the directory if it is empty"""
373 """unlink and remove the directory if it is empty"""
374 os.unlink(f)
374 os.unlink(f)
375 # try removing directories that might now be empty
375 # try removing directories that might now be empty
376 try: os.removedirs(os.path.dirname(f))
376 try: os.removedirs(os.path.dirname(f))
377 except: pass
377 except: pass
378
378
379 def copyfiles(src, dst, hardlink=None):
379 def copyfiles(src, dst, hardlink=None):
380 """Copy a directory tree using hardlinks if possible"""
380 """Copy a directory tree using hardlinks if possible"""
381
381
382 if hardlink is None:
382 if hardlink is None:
383 hardlink = (os.stat(src).st_dev ==
383 hardlink = (os.stat(src).st_dev ==
384 os.stat(os.path.dirname(dst)).st_dev)
384 os.stat(os.path.dirname(dst)).st_dev)
385
385
386 if os.path.isdir(src):
386 if os.path.isdir(src):
387 os.mkdir(dst)
387 os.mkdir(dst)
388 for name in os.listdir(src):
388 for name in os.listdir(src):
389 srcname = os.path.join(src, name)
389 srcname = os.path.join(src, name)
390 dstname = os.path.join(dst, name)
390 dstname = os.path.join(dst, name)
391 copyfiles(srcname, dstname, hardlink)
391 copyfiles(srcname, dstname, hardlink)
392 else:
392 else:
393 if hardlink:
393 if hardlink:
394 try:
394 try:
395 os_link(src, dst)
395 os_link(src, dst)
396 except:
396 except:
397 hardlink = False
397 hardlink = False
398 shutil.copy(src, dst)
398 shutil.copy(src, dst)
399 else:
399 else:
400 shutil.copy(src, dst)
400 shutil.copy(src, dst)
401
401
402 def audit_path(path):
402 def audit_path(path):
403 """Abort if path contains dangerous components"""
403 """Abort if path contains dangerous components"""
404 parts = os.path.normcase(path).split(os.sep)
404 parts = os.path.normcase(path).split(os.sep)
405 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
405 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
406 or os.pardir in parts):
406 or os.pardir in parts):
407 raise Abort(_("path contains illegal component: %s\n") % path)
407 raise Abort(_("path contains illegal component: %s\n") % path)
408
408
409 def opener(base, audit=True):
409 def opener(base, audit=True):
410 """
410 """
411 return a function that opens files relative to base
411 return a function that opens files relative to base
412
412
413 this function is used to hide the details of COW semantics and
413 this function is used to hide the details of COW semantics and
414 remote file access from higher level code.
414 remote file access from higher level code.
415 """
415 """
416 p = base
416 p = base
417 audit_p = audit
417 audit_p = audit
418
418
419 def mktempcopy(name):
419 def mktempcopy(name):
420 d, fn = os.path.split(name)
420 d, fn = os.path.split(name)
421 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
421 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
422 fp = os.fdopen(fd, "wb")
422 fp = os.fdopen(fd, "wb")
423 try:
423 try:
424 fp.write(file(name, "rb").read())
424 fp.write(file(name, "rb").read())
425 except:
425 except:
426 try: os.unlink(temp)
426 try: os.unlink(temp)
427 except: pass
427 except: pass
428 raise
428 raise
429 fp.close()
429 fp.close()
430 st = os.lstat(name)
430 st = os.lstat(name)
431 os.chmod(temp, st.st_mode)
431 os.chmod(temp, st.st_mode)
432 return temp
432 return temp
433
433
434 class atomicfile(file):
434 class atomicfile(file):
435 """the file will only be copied on close"""
435 """the file will only be copied on close"""
436 def __init__(self, name, mode, atomic=False):
436 def __init__(self, name, mode, atomic=False):
437 self.__name = name
437 self.__name = name
438 self.temp = mktempcopy(name)
438 self.temp = mktempcopy(name)
439 file.__init__(self, self.temp, mode)
439 file.__init__(self, self.temp, mode)
440 def close(self):
440 def close(self):
441 if not self.closed:
441 if not self.closed:
442 file.close(self)
442 file.close(self)
443 rename(self.temp, self.__name)
443 rename(self.temp, self.__name)
444 def __del__(self):
444 def __del__(self):
445 self.close()
445 self.close()
446
446
447 def o(path, mode="r", text=False, atomic=False):
447 def o(path, mode="r", text=False, atomic=False):
448 if audit_p:
448 if audit_p:
449 audit_path(path)
449 audit_path(path)
450 f = os.path.join(p, path)
450 f = os.path.join(p, path)
451
451
452 if not text:
452 if not text:
453 mode += "b" # for that other OS
453 mode += "b" # for that other OS
454
454
455 if mode[0] != "r":
455 if mode[0] != "r":
456 try:
456 try:
457 nlink = nlinks(f)
457 nlink = nlinks(f)
458 except OSError:
458 except OSError:
459 d = os.path.dirname(f)
459 d = os.path.dirname(f)
460 if not os.path.isdir(d):
460 if not os.path.isdir(d):
461 os.makedirs(d)
461 os.makedirs(d)
462 else:
462 else:
463 if atomic:
463 if atomic:
464 return atomicfile(f, mode)
464 return atomicfile(f, mode)
465 if nlink > 1:
465 if nlink > 1:
466 rename(mktempcopy(f), f)
466 rename(mktempcopy(f), f)
467 return file(f, mode)
467 return file(f, mode)
468
468
469 return o
469 return o
470
470
471 def _makelock_file(info, pathname):
471 def _makelock_file(info, pathname):
472 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
472 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
473 os.write(ld, info)
473 os.write(ld, info)
474 os.close(ld)
474 os.close(ld)
475
475
476 def _readlock_file(pathname):
476 def _readlock_file(pathname):
477 return file(pathname).read()
477 return file(pathname).read()
478
478
479 def nlinks(pathname):
479 def nlinks(pathname):
480 """Return number of hardlinks for the given file."""
480 """Return number of hardlinks for the given file."""
481 return os.stat(pathname).st_nlink
481 return os.stat(pathname).st_nlink
482
482
483 if hasattr(os, 'link'):
483 if hasattr(os, 'link'):
484 os_link = os.link
484 os_link = os.link
485 else:
485 else:
486 def os_link(src, dst):
486 def os_link(src, dst):
487 raise OSError(0, _("Hardlinks not supported"))
487 raise OSError(0, _("Hardlinks not supported"))
488
488
489 # Platform specific variants
489 # Platform specific variants
490 if os.name == 'nt':
490 if os.name == 'nt':
491 demandload(globals(), "msvcrt")
491 demandload(globals(), "msvcrt")
492 nulldev = 'NUL:'
492 nulldev = 'NUL:'
493
493
494 class winstdout:
494 class winstdout:
495 '''stdout on windows misbehaves if sent through a pipe'''
495 '''stdout on windows misbehaves if sent through a pipe'''
496
496
497 def __init__(self, fp):
497 def __init__(self, fp):
498 self.fp = fp
498 self.fp = fp
499
499
500 def __getattr__(self, key):
500 def __getattr__(self, key):
501 return getattr(self.fp, key)
501 return getattr(self.fp, key)
502
502
503 def close(self):
503 def close(self):
504 try:
504 try:
505 self.fp.close()
505 self.fp.close()
506 except: pass
506 except: pass
507
507
508 def write(self, s):
508 def write(self, s):
509 try:
509 try:
510 return self.fp.write(s)
510 return self.fp.write(s)
511 except IOError, inst:
511 except IOError, inst:
512 if inst.errno != 0: raise
512 if inst.errno != 0: raise
513 self.close()
513 self.close()
514 raise IOError(errno.EPIPE, 'Broken pipe')
514 raise IOError(errno.EPIPE, 'Broken pipe')
515
515
516 sys.stdout = winstdout(sys.stdout)
516 sys.stdout = winstdout(sys.stdout)
517
517
518 def os_rcpath():
518 def os_rcpath():
519 '''return default os-specific hgrc search path'''
519 '''return default os-specific hgrc search path'''
520 try:
520 try:
521 import win32api, win32process
521 import win32api, win32process
522 proc = win32api.GetCurrentProcess()
522 proc = win32api.GetCurrentProcess()
523 filename = win32process.GetModuleFileNameEx(proc, 0)
523 filename = win32process.GetModuleFileNameEx(proc, 0)
524 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
524 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
525 except ImportError:
525 except ImportError:
526 systemrc = r'c:\mercurial\mercurial.ini'
526 systemrc = r'c:\mercurial\mercurial.ini'
527
527
528 return [systemrc,
528 return [systemrc,
529 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
529 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
530
530
531 def parse_patch_output(output_line):
531 def parse_patch_output(output_line):
532 """parses the output produced by patch and returns the file name"""
532 """parses the output produced by patch and returns the file name"""
533 pf = output_line[14:]
533 pf = output_line[14:]
534 if pf[0] == '`':
534 if pf[0] == '`':
535 pf = pf[1:-1] # Remove the quotes
535 pf = pf[1:-1] # Remove the quotes
536 return pf
536 return pf
537
537
538 try: # ActivePython can create hard links using win32file module
538 try: # ActivePython can create hard links using win32file module
539 import win32api, win32con, win32file
539 import win32api, win32con, win32file
540
540
541 def os_link(src, dst): # NB will only succeed on NTFS
541 def os_link(src, dst): # NB will only succeed on NTFS
542 win32file.CreateHardLink(dst, src)
542 win32file.CreateHardLink(dst, src)
543
543
544 def nlinks(pathname):
544 def nlinks(pathname):
545 """Return number of hardlinks for the given file."""
545 """Return number of hardlinks for the given file."""
546 try:
546 try:
547 fh = win32file.CreateFile(pathname,
547 fh = win32file.CreateFile(pathname,
548 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
548 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
549 None, win32file.OPEN_EXISTING, 0, None)
549 None, win32file.OPEN_EXISTING, 0, None)
550 res = win32file.GetFileInformationByHandle(fh)
550 res = win32file.GetFileInformationByHandle(fh)
551 fh.Close()
551 fh.Close()
552 return res[7]
552 return res[7]
553 except:
553 except:
554 return os.stat(pathname).st_nlink
554 return os.stat(pathname).st_nlink
555
555
556 def testpid(pid):
556 def testpid(pid):
557 '''return False if pid is dead, True if running or not known'''
557 '''return False if pid is dead, True if running or not known'''
558 try:
558 try:
559 win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
559 win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
560 False, pid)
560 False, pid)
561 except:
561 except:
562 return True
562 return True
563
563
564 except ImportError:
564 except ImportError:
565 def testpid(pid):
565 def testpid(pid):
566 '''return False if pid dead, True if running or not known'''
566 '''return False if pid dead, True if running or not known'''
567 return True
567 return True
568
568
569 def is_exec(f, last):
569 def is_exec(f, last):
570 return last
570 return last
571
571
572 def set_exec(f, mode):
572 def set_exec(f, mode):
573 pass
573 pass
574
574
575 def set_binary(fd):
575 def set_binary(fd):
576 msvcrt.setmode(fd.fileno(), os.O_BINARY)
576 msvcrt.setmode(fd.fileno(), os.O_BINARY)
577
577
578 def pconvert(path):
578 def pconvert(path):
579 return path.replace("\\", "/")
579 return path.replace("\\", "/")
580
580
581 def localpath(path):
581 def localpath(path):
582 return path.replace('/', '\\')
582 return path.replace('/', '\\')
583
583
584 def normpath(path):
584 def normpath(path):
585 return pconvert(os.path.normpath(path))
585 return pconvert(os.path.normpath(path))
586
586
587 makelock = _makelock_file
587 makelock = _makelock_file
588 readlock = _readlock_file
588 readlock = _readlock_file
589
589
590 def explain_exit(code):
590 def explain_exit(code):
591 return _("exited with status %d") % code, code
591 return _("exited with status %d") % code, code
592
592
593 else:
593 else:
594 nulldev = '/dev/null'
594 nulldev = '/dev/null'
595
595
596 def rcfiles(path):
596 def rcfiles(path):
597 rcs = [os.path.join(path, 'hgrc')]
597 rcs = [os.path.join(path, 'hgrc')]
598 rcdir = os.path.join(path, 'hgrc.d')
598 rcdir = os.path.join(path, 'hgrc.d')
599 try:
599 try:
600 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
600 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
601 if f.endswith(".rc")])
601 if f.endswith(".rc")])
602 except OSError, inst: pass
602 except OSError, inst: pass
603 return rcs
603 return rcs
604
604
605 def os_rcpath():
605 def os_rcpath():
606 '''return default os-specific hgrc search path'''
606 '''return default os-specific hgrc search path'''
607 path = []
607 path = []
608 if len(sys.argv) > 0:
608 if len(sys.argv) > 0:
609 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
609 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
610 '/../etc/mercurial'))
610 '/../etc/mercurial'))
611 path.extend(rcfiles('/etc/mercurial'))
611 path.extend(rcfiles('/etc/mercurial'))
612 path.append(os.path.expanduser('~/.hgrc'))
612 path.append(os.path.expanduser('~/.hgrc'))
613 path = [os.path.normpath(f) for f in path]
613 path = [os.path.normpath(f) for f in path]
614 return path
614 return path
615
615
616 def parse_patch_output(output_line):
616 def parse_patch_output(output_line):
617 """parses the output produced by patch and returns the file name"""
617 """parses the output produced by patch and returns the file name"""
618 pf = output_line[14:]
618 pf = output_line[14:]
619 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
619 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
620 pf = pf[1:-1] # Remove the quotes
620 pf = pf[1:-1] # Remove the quotes
621 return pf
621 return pf
622
622
623 def is_exec(f, last):
623 def is_exec(f, last):
624 """check whether a file is executable"""
624 """check whether a file is executable"""
625 return (os.stat(f).st_mode & 0100 != 0)
625 return (os.stat(f).st_mode & 0100 != 0)
626
626
627 def set_exec(f, mode):
627 def set_exec(f, mode):
628 s = os.stat(f).st_mode
628 s = os.stat(f).st_mode
629 if (s & 0100 != 0) == mode:
629 if (s & 0100 != 0) == mode:
630 return
630 return
631 if mode:
631 if mode:
632 # Turn on +x for every +r bit when making a file executable
632 # Turn on +x for every +r bit when making a file executable
633 # and obey umask.
633 # and obey umask.
634 umask = os.umask(0)
634 umask = os.umask(0)
635 os.umask(umask)
635 os.umask(umask)
636 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
636 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
637 else:
637 else:
638 os.chmod(f, s & 0666)
638 os.chmod(f, s & 0666)
639
639
640 def set_binary(fd):
640 def set_binary(fd):
641 pass
641 pass
642
642
643 def pconvert(path):
643 def pconvert(path):
644 return path
644 return path
645
645
646 def localpath(path):
646 def localpath(path):
647 return path
647 return path
648
648
649 normpath = os.path.normpath
649 normpath = os.path.normpath
650
650
651 def makelock(info, pathname):
651 def makelock(info, pathname):
652 try:
652 try:
653 os.symlink(info, pathname)
653 os.symlink(info, pathname)
654 except OSError, why:
654 except OSError, why:
655 if why.errno == errno.EEXIST:
655 if why.errno == errno.EEXIST:
656 raise
656 raise
657 else:
657 else:
658 _makelock_file(info, pathname)
658 _makelock_file(info, pathname)
659
659
660 def readlock(pathname):
660 def readlock(pathname):
661 try:
661 try:
662 return os.readlink(pathname)
662 return os.readlink(pathname)
663 except OSError, why:
663 except OSError, why:
664 if why.errno == errno.EINVAL:
664 if why.errno == errno.EINVAL:
665 return _readlock_file(pathname)
665 return _readlock_file(pathname)
666 else:
666 else:
667 raise
667 raise
668
668
669 def testpid(pid):
669 def testpid(pid):
670 '''return False if pid dead, True if running or not sure'''
670 '''return False if pid dead, True if running or not sure'''
671 try:
671 try:
672 os.kill(pid, 0)
672 os.kill(pid, 0)
673 return True
673 return True
674 except OSError, inst:
674 except OSError, inst:
675 return inst.errno != errno.ESRCH
675 return inst.errno != errno.ESRCH
676
676
677 def explain_exit(code):
677 def explain_exit(code):
678 """return a 2-tuple (desc, code) describing a process's status"""
678 """return a 2-tuple (desc, code) describing a process's status"""
679 if os.WIFEXITED(code):
679 if os.WIFEXITED(code):
680 val = os.WEXITSTATUS(code)
680 val = os.WEXITSTATUS(code)
681 return _("exited with status %d") % val, val
681 return _("exited with status %d") % val, val
682 elif os.WIFSIGNALED(code):
682 elif os.WIFSIGNALED(code):
683 val = os.WTERMSIG(code)
683 val = os.WTERMSIG(code)
684 return _("killed by signal %d") % val, val
684 return _("killed by signal %d") % val, val
685 elif os.WIFSTOPPED(code):
685 elif os.WIFSTOPPED(code):
686 val = os.WSTOPSIG(code)
686 val = os.WSTOPSIG(code)
687 return _("stopped by signal %d") % val, val
687 return _("stopped by signal %d") % val, val
688 raise ValueError(_("invalid exit code"))
688 raise ValueError(_("invalid exit code"))
689
689
690 class chunkbuffer(object):
690 class chunkbuffer(object):
691 """Allow arbitrary sized chunks of data to be efficiently read from an
691 """Allow arbitrary sized chunks of data to be efficiently read from an
692 iterator over chunks of arbitrary size."""
692 iterator over chunks of arbitrary size."""
693
693
694 def __init__(self, in_iter, targetsize = 2**16):
694 def __init__(self, in_iter, targetsize = 2**16):
695 """in_iter is the iterator that's iterating over the input chunks.
695 """in_iter is the iterator that's iterating over the input chunks.
696 targetsize is how big a buffer to try to maintain."""
696 targetsize is how big a buffer to try to maintain."""
697 self.in_iter = iter(in_iter)
697 self.in_iter = iter(in_iter)
698 self.buf = ''
698 self.buf = ''
699 self.targetsize = int(targetsize)
699 self.targetsize = int(targetsize)
700 if self.targetsize <= 0:
700 if self.targetsize <= 0:
701 raise ValueError(_("targetsize must be greater than 0, was %d") %
701 raise ValueError(_("targetsize must be greater than 0, was %d") %
702 targetsize)
702 targetsize)
703 self.iterempty = False
703 self.iterempty = False
704
704
705 def fillbuf(self):
705 def fillbuf(self):
706 """Ignore target size; read every chunk from iterator until empty."""
706 """Ignore target size; read every chunk from iterator until empty."""
707 if not self.iterempty:
707 if not self.iterempty:
708 collector = cStringIO.StringIO()
708 collector = cStringIO.StringIO()
709 collector.write(self.buf)
709 collector.write(self.buf)
710 for ch in self.in_iter:
710 for ch in self.in_iter:
711 collector.write(ch)
711 collector.write(ch)
712 self.buf = collector.getvalue()
712 self.buf = collector.getvalue()
713 self.iterempty = True
713 self.iterempty = True
714
714
715 def read(self, l):
715 def read(self, l):
716 """Read L bytes of data from the iterator of chunks of data.
716 """Read L bytes of data from the iterator of chunks of data.
717 Returns less than L bytes if the iterator runs dry."""
717 Returns less than L bytes if the iterator runs dry."""
718 if l > len(self.buf) and not self.iterempty:
718 if l > len(self.buf) and not self.iterempty:
719 # Clamp to a multiple of self.targetsize
719 # Clamp to a multiple of self.targetsize
720 targetsize = self.targetsize * ((l // self.targetsize) + 1)
720 targetsize = self.targetsize * ((l // self.targetsize) + 1)
721 collector = cStringIO.StringIO()
721 collector = cStringIO.StringIO()
722 collector.write(self.buf)
722 collector.write(self.buf)
723 collected = len(self.buf)
723 collected = len(self.buf)
724 for chunk in self.in_iter:
724 for chunk in self.in_iter:
725 collector.write(chunk)
725 collector.write(chunk)
726 collected += len(chunk)
726 collected += len(chunk)
727 if collected >= targetsize:
727 if collected >= targetsize:
728 break
728 break
729 if collected < targetsize:
729 if collected < targetsize:
730 self.iterempty = True
730 self.iterempty = True
731 self.buf = collector.getvalue()
731 self.buf = collector.getvalue()
732 s, self.buf = self.buf[:l], buffer(self.buf, l)
732 s, self.buf = self.buf[:l], buffer(self.buf, l)
733 return s
733 return s
734
734
735 def filechunkiter(f, size = 65536):
735 def filechunkiter(f, size = 65536):
736 """Create a generator that produces all the data in the file size
736 """Create a generator that produces all the data in the file size
737 (default 65536) bytes at a time. Chunks may be less than size
737 (default 65536) bytes at a time. Chunks may be less than size
738 bytes if the chunk is the last chunk in the file, or the file is a
738 bytes if the chunk is the last chunk in the file, or the file is a
739 socket or some other type of file that sometimes reads less data
739 socket or some other type of file that sometimes reads less data
740 than is requested."""
740 than is requested."""
741 s = f.read(size)
741 s = f.read(size)
742 while len(s) > 0:
742 while len(s) > 0:
743 yield s
743 yield s
744 s = f.read(size)
744 s = f.read(size)
745
745
746 def makedate():
746 def makedate():
747 lt = time.localtime()
747 lt = time.localtime()
748 if lt[8] == 1 and time.daylight:
748 if lt[8] == 1 and time.daylight:
749 tz = time.altzone
749 tz = time.altzone
750 else:
750 else:
751 tz = time.timezone
751 tz = time.timezone
752 return time.mktime(lt), tz
752 return time.mktime(lt), tz
753
753
754 def datestr(date=None, format='%a %b %d %H:%M:%S %Y'):
754 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
755 """represent a (unixtime, offset) tuple as a localized time.
755 """represent a (unixtime, offset) tuple as a localized time.
756 unixtime is seconds since the epoch, and offset is the time zone's
756 unixtime is seconds since the epoch, and offset is the time zone's
757 number of seconds away from UTC."""
757 number of seconds away from UTC. if timezone is false, do not
758 append time zone to string."""
758 t, tz = date or makedate()
759 t, tz = date or makedate()
759 return ("%s %+03d%02d" %
760 s = time.strftime(format, time.gmtime(float(t) - tz))
760 (time.strftime(format, time.gmtime(float(t) - tz)),
761 if timezone:
761 -tz / 3600,
762 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
762 ((-tz % 3600) / 60)))
763 return s
763
764
764 def shortuser(user):
765 def shortuser(user):
765 """Return a short representation of a user name or email address."""
766 """Return a short representation of a user name or email address."""
766 f = user.find('@')
767 f = user.find('@')
767 if f >= 0:
768 if f >= 0:
768 user = user[:f]
769 user = user[:f]
769 f = user.find('<')
770 f = user.find('<')
770 if f >= 0:
771 if f >= 0:
771 user = user[f+1:]
772 user = user[f+1:]
772 return user
773 return user
773
774
774 def walkrepos(path):
775 def walkrepos(path):
775 '''yield every hg repository under path, recursively.'''
776 '''yield every hg repository under path, recursively.'''
776 def errhandler(err):
777 def errhandler(err):
777 if err.filename == path:
778 if err.filename == path:
778 raise err
779 raise err
779
780
780 for root, dirs, files in os.walk(path, onerror=errhandler):
781 for root, dirs, files in os.walk(path, onerror=errhandler):
781 for d in dirs:
782 for d in dirs:
782 if d == '.hg':
783 if d == '.hg':
783 yield root
784 yield root
784 dirs[:] = []
785 dirs[:] = []
785 break
786 break
786
787
787 _rcpath = None
788 _rcpath = None
788
789
789 def rcpath():
790 def rcpath():
790 '''return hgrc search path. if env var HGRCPATH is set, use it.
791 '''return hgrc search path. if env var HGRCPATH is set, use it.
791 for each item in path, if directory, use files ending in .rc,
792 for each item in path, if directory, use files ending in .rc,
792 else use item.
793 else use item.
793 make HGRCPATH empty to only look in .hg/hgrc of current repo.
794 make HGRCPATH empty to only look in .hg/hgrc of current repo.
794 if no HGRCPATH, use default os-specific path.'''
795 if no HGRCPATH, use default os-specific path.'''
795 global _rcpath
796 global _rcpath
796 if _rcpath is None:
797 if _rcpath is None:
797 if 'HGRCPATH' in os.environ:
798 if 'HGRCPATH' in os.environ:
798 _rcpath = []
799 _rcpath = []
799 for p in os.environ['HGRCPATH'].split(os.pathsep):
800 for p in os.environ['HGRCPATH'].split(os.pathsep):
800 if not p: continue
801 if not p: continue
801 if os.path.isdir(p):
802 if os.path.isdir(p):
802 for f in os.listdir(p):
803 for f in os.listdir(p):
803 if f.endswith('.rc'):
804 if f.endswith('.rc'):
804 _rcpath.append(os.path.join(p, f))
805 _rcpath.append(os.path.join(p, f))
805 else:
806 else:
806 _rcpath.append(p)
807 _rcpath.append(p)
807 else:
808 else:
808 _rcpath = os_rcpath()
809 _rcpath = os_rcpath()
809 return _rcpath
810 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now