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