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