##// END OF EJS Templates
add changelog style to command line template....
Vadim Gelfer -
r1987:04c17fc3 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,802 +1,803
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 else:
158 else:
159 res += re.escape(c)
159 res += re.escape(c)
160 return head + res + tail
160 return head + res + tail
161
161
162 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
162 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
163
163
164 def pathto(n1, n2):
164 def pathto(n1, n2):
165 '''return the relative path from one place to another.
165 '''return the relative path from one place to another.
166 this returns a path in the form used by the local filesystem, not hg.'''
166 this returns a path in the form used by the local filesystem, not hg.'''
167 if not n1: return localpath(n2)
167 if not n1: return localpath(n2)
168 a, b = n1.split('/'), n2.split('/')
168 a, b = n1.split('/'), n2.split('/')
169 a.reverse()
169 a.reverse()
170 b.reverse()
170 b.reverse()
171 while a and b and a[-1] == b[-1]:
171 while a and b and a[-1] == b[-1]:
172 a.pop()
172 a.pop()
173 b.pop()
173 b.pop()
174 b.reverse()
174 b.reverse()
175 return os.sep.join((['..'] * len(a)) + b)
175 return os.sep.join((['..'] * len(a)) + b)
176
176
177 def canonpath(root, cwd, myname):
177 def canonpath(root, cwd, myname):
178 """return the canonical path of myname, given cwd and root"""
178 """return the canonical path of myname, given cwd and root"""
179 if root == os.sep:
179 if root == os.sep:
180 rootsep = os.sep
180 rootsep = os.sep
181 else:
181 else:
182 rootsep = root + os.sep
182 rootsep = root + os.sep
183 name = myname
183 name = myname
184 if not name.startswith(os.sep):
184 if not name.startswith(os.sep):
185 name = os.path.join(root, cwd, name)
185 name = os.path.join(root, cwd, name)
186 name = os.path.normpath(name)
186 name = os.path.normpath(name)
187 if name.startswith(rootsep):
187 if name.startswith(rootsep):
188 name = name[len(rootsep):]
188 name = name[len(rootsep):]
189 audit_path(name)
189 audit_path(name)
190 return pconvert(name)
190 return pconvert(name)
191 elif name == root:
191 elif name == root:
192 return ''
192 return ''
193 else:
193 else:
194 raise Abort('%s not under root' % myname)
194 raise Abort('%s not under root' % myname)
195
195
196 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
196 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
197 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
197 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
198
198
199 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
199 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
200 if os.name == 'nt':
200 if os.name == 'nt':
201 dflt_pat = 'glob'
201 dflt_pat = 'glob'
202 else:
202 else:
203 dflt_pat = 'relpath'
203 dflt_pat = 'relpath'
204 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
204 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
205
205
206 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
206 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
207 """build a function to match a set of file patterns
207 """build a function to match a set of file patterns
208
208
209 arguments:
209 arguments:
210 canonroot - the canonical root of the tree you're matching against
210 canonroot - the canonical root of the tree you're matching against
211 cwd - the current working directory, if relevant
211 cwd - the current working directory, if relevant
212 names - patterns to find
212 names - patterns to find
213 inc - patterns to include
213 inc - patterns to include
214 exc - patterns to exclude
214 exc - patterns to exclude
215 head - a regex to prepend to patterns to control whether a match is rooted
215 head - a regex to prepend to patterns to control whether a match is rooted
216
216
217 a pattern is one of:
217 a pattern is one of:
218 'glob:<rooted glob>'
218 'glob:<rooted glob>'
219 're:<rooted regexp>'
219 're:<rooted regexp>'
220 'path:<rooted path>'
220 'path:<rooted path>'
221 'relglob:<relative glob>'
221 'relglob:<relative glob>'
222 'relpath:<relative path>'
222 'relpath:<relative path>'
223 'relre:<relative regexp>'
223 'relre:<relative regexp>'
224 '<rooted path or regexp>'
224 '<rooted path or regexp>'
225
225
226 returns:
226 returns:
227 a 3-tuple containing
227 a 3-tuple containing
228 - list of explicit non-pattern names passed in
228 - list of explicit non-pattern names passed in
229 - a bool match(filename) function
229 - a bool match(filename) function
230 - a bool indicating if any patterns were passed in
230 - a bool indicating if any patterns were passed in
231
231
232 todo:
232 todo:
233 make head regex a rooted bool
233 make head regex a rooted bool
234 """
234 """
235
235
236 def contains_glob(name):
236 def contains_glob(name):
237 for c in name:
237 for c in name:
238 if c in _globchars: return True
238 if c in _globchars: return True
239 return False
239 return False
240
240
241 def regex(kind, name, tail):
241 def regex(kind, name, tail):
242 '''convert a pattern into a regular expression'''
242 '''convert a pattern into a regular expression'''
243 if kind == 're':
243 if kind == 're':
244 return name
244 return name
245 elif kind == 'path':
245 elif kind == 'path':
246 return '^' + re.escape(name) + '(?:/|$)'
246 return '^' + re.escape(name) + '(?:/|$)'
247 elif kind == 'relglob':
247 elif kind == 'relglob':
248 return head + globre(name, '(?:|.*/)', tail)
248 return head + globre(name, '(?:|.*/)', tail)
249 elif kind == 'relpath':
249 elif kind == 'relpath':
250 return head + re.escape(name) + tail
250 return head + re.escape(name) + tail
251 elif kind == 'relre':
251 elif kind == 'relre':
252 if name.startswith('^'):
252 if name.startswith('^'):
253 return name
253 return name
254 return '.*' + name
254 return '.*' + name
255 return head + globre(name, '', tail)
255 return head + globre(name, '', tail)
256
256
257 def matchfn(pats, tail):
257 def matchfn(pats, tail):
258 """build a matching function from a set of patterns"""
258 """build a matching function from a set of patterns"""
259 if not pats:
259 if not pats:
260 return
260 return
261 matches = []
261 matches = []
262 for k, p in pats:
262 for k, p in pats:
263 try:
263 try:
264 pat = '(?:%s)' % regex(k, p, tail)
264 pat = '(?:%s)' % regex(k, p, tail)
265 matches.append(re.compile(pat).match)
265 matches.append(re.compile(pat).match)
266 except re.error:
266 except re.error:
267 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
267 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
268 else: raise Abort("invalid pattern (%s): %s" % (k, p))
268 else: raise Abort("invalid pattern (%s): %s" % (k, p))
269
269
270 def buildfn(text):
270 def buildfn(text):
271 for m in matches:
271 for m in matches:
272 r = m(text)
272 r = m(text)
273 if r:
273 if r:
274 return r
274 return r
275
275
276 return buildfn
276 return buildfn
277
277
278 def globprefix(pat):
278 def globprefix(pat):
279 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
279 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
280 root = []
280 root = []
281 for p in pat.split(os.sep):
281 for p in pat.split(os.sep):
282 if contains_glob(p): break
282 if contains_glob(p): break
283 root.append(p)
283 root.append(p)
284 return '/'.join(root)
284 return '/'.join(root)
285
285
286 pats = []
286 pats = []
287 files = []
287 files = []
288 roots = []
288 roots = []
289 for kind, name in [patkind(p, dflt_pat) for p in names]:
289 for kind, name in [patkind(p, dflt_pat) for p in names]:
290 if kind in ('glob', 'relpath'):
290 if kind in ('glob', 'relpath'):
291 name = canonpath(canonroot, cwd, name)
291 name = canonpath(canonroot, cwd, name)
292 if name == '':
292 if name == '':
293 kind, name = 'glob', '**'
293 kind, name = 'glob', '**'
294 if kind in ('glob', 'path', 're'):
294 if kind in ('glob', 'path', 're'):
295 pats.append((kind, name))
295 pats.append((kind, name))
296 if kind == 'glob':
296 if kind == 'glob':
297 root = globprefix(name)
297 root = globprefix(name)
298 if root: roots.append(root)
298 if root: roots.append(root)
299 elif kind == 'relpath':
299 elif kind == 'relpath':
300 files.append((kind, name))
300 files.append((kind, name))
301 roots.append(name)
301 roots.append(name)
302
302
303 patmatch = matchfn(pats, '$') or always
303 patmatch = matchfn(pats, '$') or always
304 filematch = matchfn(files, '(?:/|$)') or always
304 filematch = matchfn(files, '(?:/|$)') or always
305 incmatch = always
305 incmatch = always
306 if inc:
306 if inc:
307 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
307 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
308 excmatch = lambda fn: False
308 excmatch = lambda fn: False
309 if exc:
309 if exc:
310 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
310 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
311
311
312 return (roots,
312 return (roots,
313 lambda fn: (incmatch(fn) and not excmatch(fn) and
313 lambda fn: (incmatch(fn) and not excmatch(fn) and
314 (fn.endswith('/') or
314 (fn.endswith('/') or
315 (not pats and not files) or
315 (not pats and not files) or
316 (pats and patmatch(fn)) or
316 (pats and patmatch(fn)) or
317 (files and filematch(fn)))),
317 (files and filematch(fn)))),
318 (inc or exc or (pats and pats != [('glob', '**')])) and True)
318 (inc or exc or (pats and pats != [('glob', '**')])) and True)
319
319
320 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
320 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
321 '''enhanced shell command execution.
321 '''enhanced shell command execution.
322 run with environment maybe modified, maybe in different dir.
322 run with environment maybe modified, maybe in different dir.
323
323
324 if command fails and onerr is None, return status. if ui object,
324 if command fails and onerr is None, return status. if ui object,
325 print error message and return status, else raise onerr object as
325 print error message and return status, else raise onerr object as
326 exception.'''
326 exception.'''
327 oldenv = {}
327 oldenv = {}
328 for k in environ:
328 for k in environ:
329 oldenv[k] = os.environ.get(k)
329 oldenv[k] = os.environ.get(k)
330 if cwd is not None:
330 if cwd is not None:
331 oldcwd = os.getcwd()
331 oldcwd = os.getcwd()
332 try:
332 try:
333 for k, v in environ.iteritems():
333 for k, v in environ.iteritems():
334 os.environ[k] = str(v)
334 os.environ[k] = str(v)
335 if cwd is not None and oldcwd != cwd:
335 if cwd is not None and oldcwd != cwd:
336 os.chdir(cwd)
336 os.chdir(cwd)
337 rc = os.system(cmd)
337 rc = os.system(cmd)
338 if rc and onerr:
338 if rc and onerr:
339 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
339 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
340 explain_exit(rc)[0])
340 explain_exit(rc)[0])
341 if errprefix:
341 if errprefix:
342 errmsg = '%s: %s' % (errprefix, errmsg)
342 errmsg = '%s: %s' % (errprefix, errmsg)
343 try:
343 try:
344 onerr.warn(errmsg + '\n')
344 onerr.warn(errmsg + '\n')
345 except AttributeError:
345 except AttributeError:
346 raise onerr(errmsg)
346 raise onerr(errmsg)
347 return rc
347 return rc
348 finally:
348 finally:
349 for k, v in oldenv.iteritems():
349 for k, v in oldenv.iteritems():
350 if v is None:
350 if v is None:
351 del os.environ[k]
351 del os.environ[k]
352 else:
352 else:
353 os.environ[k] = v
353 os.environ[k] = v
354 if cwd is not None and oldcwd != cwd:
354 if cwd is not None and oldcwd != cwd:
355 os.chdir(oldcwd)
355 os.chdir(oldcwd)
356
356
357 def rename(src, dst):
357 def rename(src, dst):
358 """forcibly rename a file"""
358 """forcibly rename a file"""
359 try:
359 try:
360 os.rename(src, dst)
360 os.rename(src, dst)
361 except:
361 except:
362 os.unlink(dst)
362 os.unlink(dst)
363 os.rename(src, dst)
363 os.rename(src, dst)
364
364
365 def unlink(f):
365 def unlink(f):
366 """unlink and remove the directory if it is empty"""
366 """unlink and remove the directory if it is empty"""
367 os.unlink(f)
367 os.unlink(f)
368 # try removing directories that might now be empty
368 # try removing directories that might now be empty
369 try: os.removedirs(os.path.dirname(f))
369 try: os.removedirs(os.path.dirname(f))
370 except: pass
370 except: pass
371
371
372 def copyfiles(src, dst, hardlink=None):
372 def copyfiles(src, dst, hardlink=None):
373 """Copy a directory tree using hardlinks if possible"""
373 """Copy a directory tree using hardlinks if possible"""
374
374
375 if hardlink is None:
375 if hardlink is None:
376 hardlink = (os.stat(src).st_dev ==
376 hardlink = (os.stat(src).st_dev ==
377 os.stat(os.path.dirname(dst)).st_dev)
377 os.stat(os.path.dirname(dst)).st_dev)
378
378
379 if os.path.isdir(src):
379 if os.path.isdir(src):
380 os.mkdir(dst)
380 os.mkdir(dst)
381 for name in os.listdir(src):
381 for name in os.listdir(src):
382 srcname = os.path.join(src, name)
382 srcname = os.path.join(src, name)
383 dstname = os.path.join(dst, name)
383 dstname = os.path.join(dst, name)
384 copyfiles(srcname, dstname, hardlink)
384 copyfiles(srcname, dstname, hardlink)
385 else:
385 else:
386 if hardlink:
386 if hardlink:
387 try:
387 try:
388 os_link(src, dst)
388 os_link(src, dst)
389 except:
389 except:
390 hardlink = False
390 hardlink = False
391 shutil.copy(src, dst)
391 shutil.copy(src, dst)
392 else:
392 else:
393 shutil.copy(src, dst)
393 shutil.copy(src, dst)
394
394
395 def audit_path(path):
395 def audit_path(path):
396 """Abort if path contains dangerous components"""
396 """Abort if path contains dangerous components"""
397 parts = os.path.normcase(path).split(os.sep)
397 parts = os.path.normcase(path).split(os.sep)
398 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
398 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
399 or os.pardir in parts):
399 or os.pardir in parts):
400 raise Abort(_("path contains illegal component: %s\n") % path)
400 raise Abort(_("path contains illegal component: %s\n") % path)
401
401
402 def opener(base, audit=True):
402 def opener(base, audit=True):
403 """
403 """
404 return a function that opens files relative to base
404 return a function that opens files relative to base
405
405
406 this function is used to hide the details of COW semantics and
406 this function is used to hide the details of COW semantics and
407 remote file access from higher level code.
407 remote file access from higher level code.
408 """
408 """
409 p = base
409 p = base
410 audit_p = audit
410 audit_p = audit
411
411
412 def mktempcopy(name):
412 def mktempcopy(name):
413 d, fn = os.path.split(name)
413 d, fn = os.path.split(name)
414 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
414 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
415 fp = os.fdopen(fd, "wb")
415 fp = os.fdopen(fd, "wb")
416 try:
416 try:
417 fp.write(file(name, "rb").read())
417 fp.write(file(name, "rb").read())
418 except:
418 except:
419 try: os.unlink(temp)
419 try: os.unlink(temp)
420 except: pass
420 except: pass
421 raise
421 raise
422 fp.close()
422 fp.close()
423 st = os.lstat(name)
423 st = os.lstat(name)
424 os.chmod(temp, st.st_mode)
424 os.chmod(temp, st.st_mode)
425 return temp
425 return temp
426
426
427 class atomicfile(file):
427 class atomicfile(file):
428 """the file will only be copied on close"""
428 """the file will only be copied on close"""
429 def __init__(self, name, mode, atomic=False):
429 def __init__(self, name, mode, atomic=False):
430 self.__name = name
430 self.__name = name
431 self.temp = mktempcopy(name)
431 self.temp = mktempcopy(name)
432 file.__init__(self, self.temp, mode)
432 file.__init__(self, self.temp, mode)
433 def close(self):
433 def close(self):
434 if not self.closed:
434 if not self.closed:
435 file.close(self)
435 file.close(self)
436 rename(self.temp, self.__name)
436 rename(self.temp, self.__name)
437 def __del__(self):
437 def __del__(self):
438 self.close()
438 self.close()
439
439
440 def o(path, mode="r", text=False, atomic=False):
440 def o(path, mode="r", text=False, atomic=False):
441 if audit_p:
441 if audit_p:
442 audit_path(path)
442 audit_path(path)
443 f = os.path.join(p, path)
443 f = os.path.join(p, path)
444
444
445 if not text:
445 if not text:
446 mode += "b" # for that other OS
446 mode += "b" # for that other OS
447
447
448 if mode[0] != "r":
448 if mode[0] != "r":
449 try:
449 try:
450 nlink = nlinks(f)
450 nlink = nlinks(f)
451 except OSError:
451 except OSError:
452 d = os.path.dirname(f)
452 d = os.path.dirname(f)
453 if not os.path.isdir(d):
453 if not os.path.isdir(d):
454 os.makedirs(d)
454 os.makedirs(d)
455 else:
455 else:
456 if atomic:
456 if atomic:
457 return atomicfile(f, mode)
457 return atomicfile(f, mode)
458 if nlink > 1:
458 if nlink > 1:
459 rename(mktempcopy(f), f)
459 rename(mktempcopy(f), f)
460 return file(f, mode)
460 return file(f, mode)
461
461
462 return o
462 return o
463
463
464 def _makelock_file(info, pathname):
464 def _makelock_file(info, pathname):
465 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
465 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
466 os.write(ld, info)
466 os.write(ld, info)
467 os.close(ld)
467 os.close(ld)
468
468
469 def _readlock_file(pathname):
469 def _readlock_file(pathname):
470 return file(pathname).read()
470 return file(pathname).read()
471
471
472 def nlinks(pathname):
472 def nlinks(pathname):
473 """Return number of hardlinks for the given file."""
473 """Return number of hardlinks for the given file."""
474 return os.stat(pathname).st_nlink
474 return os.stat(pathname).st_nlink
475
475
476 if hasattr(os, 'link'):
476 if hasattr(os, 'link'):
477 os_link = os.link
477 os_link = os.link
478 else:
478 else:
479 def os_link(src, dst):
479 def os_link(src, dst):
480 raise OSError(0, _("Hardlinks not supported"))
480 raise OSError(0, _("Hardlinks not supported"))
481
481
482 # Platform specific variants
482 # Platform specific variants
483 if os.name == 'nt':
483 if os.name == 'nt':
484 demandload(globals(), "msvcrt")
484 demandload(globals(), "msvcrt")
485 nulldev = 'NUL:'
485 nulldev = 'NUL:'
486
486
487 class winstdout:
487 class winstdout:
488 '''stdout on windows misbehaves if sent through a pipe'''
488 '''stdout on windows misbehaves if sent through a pipe'''
489
489
490 def __init__(self, fp):
490 def __init__(self, fp):
491 self.fp = fp
491 self.fp = fp
492
492
493 def __getattr__(self, key):
493 def __getattr__(self, key):
494 return getattr(self.fp, key)
494 return getattr(self.fp, key)
495
495
496 def close(self):
496 def close(self):
497 try:
497 try:
498 self.fp.close()
498 self.fp.close()
499 except: pass
499 except: pass
500
500
501 def write(self, s):
501 def write(self, s):
502 try:
502 try:
503 return self.fp.write(s)
503 return self.fp.write(s)
504 except IOError, inst:
504 except IOError, inst:
505 if inst.errno != 0: raise
505 if inst.errno != 0: raise
506 self.close()
506 self.close()
507 raise IOError(errno.EPIPE, 'Broken pipe')
507 raise IOError(errno.EPIPE, 'Broken pipe')
508
508
509 sys.stdout = winstdout(sys.stdout)
509 sys.stdout = winstdout(sys.stdout)
510
510
511 def os_rcpath():
511 def os_rcpath():
512 '''return default os-specific hgrc search path'''
512 '''return default os-specific hgrc search path'''
513 try:
513 try:
514 import win32api, win32process
514 import win32api, win32process
515 proc = win32api.GetCurrentProcess()
515 proc = win32api.GetCurrentProcess()
516 filename = win32process.GetModuleFileNameEx(proc, 0)
516 filename = win32process.GetModuleFileNameEx(proc, 0)
517 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
517 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
518 except ImportError:
518 except ImportError:
519 systemrc = r'c:\mercurial\mercurial.ini'
519 systemrc = r'c:\mercurial\mercurial.ini'
520
520
521 return [systemrc,
521 return [systemrc,
522 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
522 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
523
523
524 def parse_patch_output(output_line):
524 def parse_patch_output(output_line):
525 """parses the output produced by patch and returns the file name"""
525 """parses the output produced by patch and returns the file name"""
526 pf = output_line[14:]
526 pf = output_line[14:]
527 if pf[0] == '`':
527 if pf[0] == '`':
528 pf = pf[1:-1] # Remove the quotes
528 pf = pf[1:-1] # Remove the quotes
529 return pf
529 return pf
530
530
531 try: # ActivePython can create hard links using win32file module
531 try: # ActivePython can create hard links using win32file module
532 import win32api, win32con, win32file
532 import win32api, win32con, win32file
533
533
534 def os_link(src, dst): # NB will only succeed on NTFS
534 def os_link(src, dst): # NB will only succeed on NTFS
535 win32file.CreateHardLink(dst, src)
535 win32file.CreateHardLink(dst, src)
536
536
537 def nlinks(pathname):
537 def nlinks(pathname):
538 """Return number of hardlinks for the given file."""
538 """Return number of hardlinks for the given file."""
539 try:
539 try:
540 fh = win32file.CreateFile(pathname,
540 fh = win32file.CreateFile(pathname,
541 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
541 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
542 None, win32file.OPEN_EXISTING, 0, None)
542 None, win32file.OPEN_EXISTING, 0, None)
543 res = win32file.GetFileInformationByHandle(fh)
543 res = win32file.GetFileInformationByHandle(fh)
544 fh.Close()
544 fh.Close()
545 return res[7]
545 return res[7]
546 except:
546 except:
547 return os.stat(pathname).st_nlink
547 return os.stat(pathname).st_nlink
548
548
549 def testpid(pid):
549 def testpid(pid):
550 '''return False if pid is dead, True if running or not known'''
550 '''return False if pid is dead, True if running or not known'''
551 try:
551 try:
552 win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
552 win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
553 False, pid)
553 False, pid)
554 except:
554 except:
555 return True
555 return True
556
556
557 except ImportError:
557 except ImportError:
558 def testpid(pid):
558 def testpid(pid):
559 '''return False if pid dead, True if running or not known'''
559 '''return False if pid dead, True if running or not known'''
560 return True
560 return True
561
561
562 def is_exec(f, last):
562 def is_exec(f, last):
563 return last
563 return last
564
564
565 def set_exec(f, mode):
565 def set_exec(f, mode):
566 pass
566 pass
567
567
568 def set_binary(fd):
568 def set_binary(fd):
569 msvcrt.setmode(fd.fileno(), os.O_BINARY)
569 msvcrt.setmode(fd.fileno(), os.O_BINARY)
570
570
571 def pconvert(path):
571 def pconvert(path):
572 return path.replace("\\", "/")
572 return path.replace("\\", "/")
573
573
574 def localpath(path):
574 def localpath(path):
575 return path.replace('/', '\\')
575 return path.replace('/', '\\')
576
576
577 def normpath(path):
577 def normpath(path):
578 return pconvert(os.path.normpath(path))
578 return pconvert(os.path.normpath(path))
579
579
580 makelock = _makelock_file
580 makelock = _makelock_file
581 readlock = _readlock_file
581 readlock = _readlock_file
582
582
583 def explain_exit(code):
583 def explain_exit(code):
584 return _("exited with status %d") % code, code
584 return _("exited with status %d") % code, code
585
585
586 else:
586 else:
587 nulldev = '/dev/null'
587 nulldev = '/dev/null'
588
588
589 def rcfiles(path):
589 def rcfiles(path):
590 rcs = [os.path.join(path, 'hgrc')]
590 rcs = [os.path.join(path, 'hgrc')]
591 rcdir = os.path.join(path, 'hgrc.d')
591 rcdir = os.path.join(path, 'hgrc.d')
592 try:
592 try:
593 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
593 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
594 if f.endswith(".rc")])
594 if f.endswith(".rc")])
595 except OSError, inst: pass
595 except OSError, inst: pass
596 return rcs
596 return rcs
597
597
598 def os_rcpath():
598 def os_rcpath():
599 '''return default os-specific hgrc search path'''
599 '''return default os-specific hgrc search path'''
600 path = []
600 path = []
601 if len(sys.argv) > 0:
601 if len(sys.argv) > 0:
602 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
602 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
603 '/../etc/mercurial'))
603 '/../etc/mercurial'))
604 path.extend(rcfiles('/etc/mercurial'))
604 path.extend(rcfiles('/etc/mercurial'))
605 path.append(os.path.expanduser('~/.hgrc'))
605 path.append(os.path.expanduser('~/.hgrc'))
606 path = [os.path.normpath(f) for f in path]
606 path = [os.path.normpath(f) for f in path]
607 return path
607 return path
608
608
609 def parse_patch_output(output_line):
609 def parse_patch_output(output_line):
610 """parses the output produced by patch and returns the file name"""
610 """parses the output produced by patch and returns the file name"""
611 pf = output_line[14:]
611 pf = output_line[14:]
612 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
612 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
613 pf = pf[1:-1] # Remove the quotes
613 pf = pf[1:-1] # Remove the quotes
614 return pf
614 return pf
615
615
616 def is_exec(f, last):
616 def is_exec(f, last):
617 """check whether a file is executable"""
617 """check whether a file is executable"""
618 return (os.stat(f).st_mode & 0100 != 0)
618 return (os.stat(f).st_mode & 0100 != 0)
619
619
620 def set_exec(f, mode):
620 def set_exec(f, mode):
621 s = os.stat(f).st_mode
621 s = os.stat(f).st_mode
622 if (s & 0100 != 0) == mode:
622 if (s & 0100 != 0) == mode:
623 return
623 return
624 if mode:
624 if mode:
625 # Turn on +x for every +r bit when making a file executable
625 # Turn on +x for every +r bit when making a file executable
626 # and obey umask.
626 # and obey umask.
627 umask = os.umask(0)
627 umask = os.umask(0)
628 os.umask(umask)
628 os.umask(umask)
629 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
629 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
630 else:
630 else:
631 os.chmod(f, s & 0666)
631 os.chmod(f, s & 0666)
632
632
633 def set_binary(fd):
633 def set_binary(fd):
634 pass
634 pass
635
635
636 def pconvert(path):
636 def pconvert(path):
637 return path
637 return path
638
638
639 def localpath(path):
639 def localpath(path):
640 return path
640 return path
641
641
642 normpath = os.path.normpath
642 normpath = os.path.normpath
643
643
644 def makelock(info, pathname):
644 def makelock(info, pathname):
645 try:
645 try:
646 os.symlink(info, pathname)
646 os.symlink(info, pathname)
647 except OSError, why:
647 except OSError, why:
648 if why.errno == errno.EEXIST:
648 if why.errno == errno.EEXIST:
649 raise
649 raise
650 else:
650 else:
651 _makelock_file(info, pathname)
651 _makelock_file(info, pathname)
652
652
653 def readlock(pathname):
653 def readlock(pathname):
654 try:
654 try:
655 return os.readlink(pathname)
655 return os.readlink(pathname)
656 except OSError, why:
656 except OSError, why:
657 if why.errno == errno.EINVAL:
657 if why.errno == errno.EINVAL:
658 return _readlock_file(pathname)
658 return _readlock_file(pathname)
659 else:
659 else:
660 raise
660 raise
661
661
662 def testpid(pid):
662 def testpid(pid):
663 '''return False if pid dead, True if running or not sure'''
663 '''return False if pid dead, True if running or not sure'''
664 try:
664 try:
665 os.kill(pid, 0)
665 os.kill(pid, 0)
666 return True
666 return True
667 except OSError, inst:
667 except OSError, inst:
668 return inst.errno != errno.ESRCH
668 return inst.errno != errno.ESRCH
669
669
670 def explain_exit(code):
670 def explain_exit(code):
671 """return a 2-tuple (desc, code) describing a process's status"""
671 """return a 2-tuple (desc, code) describing a process's status"""
672 if os.WIFEXITED(code):
672 if os.WIFEXITED(code):
673 val = os.WEXITSTATUS(code)
673 val = os.WEXITSTATUS(code)
674 return _("exited with status %d") % val, val
674 return _("exited with status %d") % val, val
675 elif os.WIFSIGNALED(code):
675 elif os.WIFSIGNALED(code):
676 val = os.WTERMSIG(code)
676 val = os.WTERMSIG(code)
677 return _("killed by signal %d") % val, val
677 return _("killed by signal %d") % val, val
678 elif os.WIFSTOPPED(code):
678 elif os.WIFSTOPPED(code):
679 val = os.WSTOPSIG(code)
679 val = os.WSTOPSIG(code)
680 return _("stopped by signal %d") % val, val
680 return _("stopped by signal %d") % val, val
681 raise ValueError(_("invalid exit code"))
681 raise ValueError(_("invalid exit code"))
682
682
683 class chunkbuffer(object):
683 class chunkbuffer(object):
684 """Allow arbitrary sized chunks of data to be efficiently read from an
684 """Allow arbitrary sized chunks of data to be efficiently read from an
685 iterator over chunks of arbitrary size."""
685 iterator over chunks of arbitrary size."""
686
686
687 def __init__(self, in_iter, targetsize = 2**16):
687 def __init__(self, in_iter, targetsize = 2**16):
688 """in_iter is the iterator that's iterating over the input chunks.
688 """in_iter is the iterator that's iterating over the input chunks.
689 targetsize is how big a buffer to try to maintain."""
689 targetsize is how big a buffer to try to maintain."""
690 self.in_iter = iter(in_iter)
690 self.in_iter = iter(in_iter)
691 self.buf = ''
691 self.buf = ''
692 self.targetsize = int(targetsize)
692 self.targetsize = int(targetsize)
693 if self.targetsize <= 0:
693 if self.targetsize <= 0:
694 raise ValueError(_("targetsize must be greater than 0, was %d") %
694 raise ValueError(_("targetsize must be greater than 0, was %d") %
695 targetsize)
695 targetsize)
696 self.iterempty = False
696 self.iterempty = False
697
697
698 def fillbuf(self):
698 def fillbuf(self):
699 """Ignore target size; read every chunk from iterator until empty."""
699 """Ignore target size; read every chunk from iterator until empty."""
700 if not self.iterempty:
700 if not self.iterempty:
701 collector = cStringIO.StringIO()
701 collector = cStringIO.StringIO()
702 collector.write(self.buf)
702 collector.write(self.buf)
703 for ch in self.in_iter:
703 for ch in self.in_iter:
704 collector.write(ch)
704 collector.write(ch)
705 self.buf = collector.getvalue()
705 self.buf = collector.getvalue()
706 self.iterempty = True
706 self.iterempty = True
707
707
708 def read(self, l):
708 def read(self, l):
709 """Read L bytes of data from the iterator of chunks of data.
709 """Read L bytes of data from the iterator of chunks of data.
710 Returns less than L bytes if the iterator runs dry."""
710 Returns less than L bytes if the iterator runs dry."""
711 if l > len(self.buf) and not self.iterempty:
711 if l > len(self.buf) and not self.iterempty:
712 # Clamp to a multiple of self.targetsize
712 # Clamp to a multiple of self.targetsize
713 targetsize = self.targetsize * ((l // self.targetsize) + 1)
713 targetsize = self.targetsize * ((l // self.targetsize) + 1)
714 collector = cStringIO.StringIO()
714 collector = cStringIO.StringIO()
715 collector.write(self.buf)
715 collector.write(self.buf)
716 collected = len(self.buf)
716 collected = len(self.buf)
717 for chunk in self.in_iter:
717 for chunk in self.in_iter:
718 collector.write(chunk)
718 collector.write(chunk)
719 collected += len(chunk)
719 collected += len(chunk)
720 if collected >= targetsize:
720 if collected >= targetsize:
721 break
721 break
722 if collected < targetsize:
722 if collected < targetsize:
723 self.iterempty = True
723 self.iterempty = True
724 self.buf = collector.getvalue()
724 self.buf = collector.getvalue()
725 s, self.buf = self.buf[:l], buffer(self.buf, l)
725 s, self.buf = self.buf[:l], buffer(self.buf, l)
726 return s
726 return s
727
727
728 def filechunkiter(f, size = 65536):
728 def filechunkiter(f, size = 65536):
729 """Create a generator that produces all the data in the file size
729 """Create a generator that produces all the data in the file size
730 (default 65536) bytes at a time. Chunks may be less than size
730 (default 65536) bytes at a time. Chunks may be less than size
731 bytes if the chunk is the last chunk in the file, or the file is a
731 bytes if the chunk is the last chunk in the file, or the file is a
732 socket or some other type of file that sometimes reads less data
732 socket or some other type of file that sometimes reads less data
733 than is requested."""
733 than is requested."""
734 s = f.read(size)
734 s = f.read(size)
735 while len(s) > 0:
735 while len(s) > 0:
736 yield s
736 yield s
737 s = f.read(size)
737 s = f.read(size)
738
738
739 def makedate():
739 def makedate():
740 lt = time.localtime()
740 lt = time.localtime()
741 if lt[8] == 1 and time.daylight:
741 if lt[8] == 1 and time.daylight:
742 tz = time.altzone
742 tz = time.altzone
743 else:
743 else:
744 tz = time.timezone
744 tz = time.timezone
745 return time.mktime(lt), tz
745 return time.mktime(lt), tz
746
746
747 def datestr(date=None, format='%a %b %d %H:%M:%S %Y'):
747 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
748 """represent a (unixtime, offset) tuple as a localized time.
748 """represent a (unixtime, offset) tuple as a localized time.
749 unixtime is seconds since the epoch, and offset is the time zone's
749 unixtime is seconds since the epoch, and offset is the time zone's
750 number of seconds away from UTC."""
750 number of seconds away from UTC. if timezone is false, do not
751 append time zone to string."""
751 t, tz = date or makedate()
752 t, tz = date or makedate()
752 return ("%s %+03d%02d" %
753 s = time.strftime(format, time.gmtime(float(t) - tz))
753 (time.strftime(format, time.gmtime(float(t) - tz)),
754 if timezone:
754 -tz / 3600,
755 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
755 ((-tz % 3600) / 60)))
756 return s
756
757
757 def shortuser(user):
758 def shortuser(user):
758 """Return a short representation of a user name or email address."""
759 """Return a short representation of a user name or email address."""
759 f = user.find('@')
760 f = user.find('@')
760 if f >= 0:
761 if f >= 0:
761 user = user[:f]
762 user = user[:f]
762 f = user.find('<')
763 f = user.find('<')
763 if f >= 0:
764 if f >= 0:
764 user = user[f+1:]
765 user = user[f+1:]
765 return user
766 return user
766
767
767 def walkrepos(path):
768 def walkrepos(path):
768 '''yield every hg repository under path, recursively.'''
769 '''yield every hg repository under path, recursively.'''
769 def errhandler(err):
770 def errhandler(err):
770 if err.filename == path:
771 if err.filename == path:
771 raise err
772 raise err
772
773
773 for root, dirs, files in os.walk(path, onerror=errhandler):
774 for root, dirs, files in os.walk(path, onerror=errhandler):
774 for d in dirs:
775 for d in dirs:
775 if d == '.hg':
776 if d == '.hg':
776 yield root
777 yield root
777 dirs[:] = []
778 dirs[:] = []
778 break
779 break
779
780
780 _rcpath = None
781 _rcpath = None
781
782
782 def rcpath():
783 def rcpath():
783 '''return hgrc search path. if env var HGRCPATH is set, use it.
784 '''return hgrc search path. if env var HGRCPATH is set, use it.
784 for each item in path, if directory, use files ending in .rc,
785 for each item in path, if directory, use files ending in .rc,
785 else use item.
786 else use item.
786 make HGRCPATH empty to only look in .hg/hgrc of current repo.
787 make HGRCPATH empty to only look in .hg/hgrc of current repo.
787 if no HGRCPATH, use default os-specific path.'''
788 if no HGRCPATH, use default os-specific path.'''
788 global _rcpath
789 global _rcpath
789 if _rcpath is None:
790 if _rcpath is None:
790 if 'HGRCPATH' in os.environ:
791 if 'HGRCPATH' in os.environ:
791 _rcpath = []
792 _rcpath = []
792 for p in os.environ['HGRCPATH'].split(os.pathsep):
793 for p in os.environ['HGRCPATH'].split(os.pathsep):
793 if not p: continue
794 if not p: continue
794 if os.path.isdir(p):
795 if os.path.isdir(p):
795 for f in os.listdir(p):
796 for f in os.listdir(p):
796 if f.endswith('.rc'):
797 if f.endswith('.rc'):
797 _rcpath.append(os.path.join(p, f))
798 _rcpath.append(os.path.join(p, f))
798 else:
799 else:
799 _rcpath.append(p)
800 _rcpath.append(p)
800 else:
801 else:
801 _rcpath = os_rcpath()
802 _rcpath = os_rcpath()
802 return _rcpath
803 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now