##// END OF EJS Templates
Avoid looking up usernames if the current user owns the .hgrc file...
Alexis S. L. Carvalho -
r3677:1a0fa391 default
parent child Browse files
Show More
@@ -1,439 +1,441
1 1 # ui.py - user interface bits for mercurial
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 from i18n import gettext as _
9 9 from demandload import *
10 10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 11 demandload(globals(), "ConfigParser traceback util")
12 12
13 13 def dupconfig(orig):
14 14 new = util.configparser(orig.defaults())
15 15 updateconfig(orig, new)
16 16 return new
17 17
18 18 def updateconfig(source, dest, sections=None):
19 19 if not sections:
20 20 sections = source.sections()
21 21 for section in sections:
22 22 if not dest.has_section(section):
23 23 dest.add_section(section)
24 24 for name, value in source.items(section, raw=True):
25 25 dest.set(section, name, value)
26 26
27 27 class ui(object):
28 28 def __init__(self, verbose=False, debug=False, quiet=False,
29 29 interactive=True, traceback=False, report_untrusted=True,
30 30 parentui=None):
31 31 self.overlay = None
32 32 if parentui is None:
33 33 # this is the parent of all ui children
34 34 self.parentui = None
35 35 self.readhooks = []
36 36 self.quiet = quiet
37 37 self.verbose = verbose
38 38 self.debugflag = debug
39 39 self.interactive = interactive
40 40 self.traceback = traceback
41 41 self.report_untrusted = report_untrusted
42 42 self.trusted_users = {}
43 43 self.trusted_groups = {}
44 44 # if ucdata is not None, its keys must be a superset of cdata's
45 45 self.cdata = util.configparser()
46 46 self.ucdata = None
47 47 # we always trust global config files
48 48 self.check_trusted = False
49 49 self.readconfig(util.rcpath())
50 50 self.check_trusted = True
51 51 self.updateopts(verbose, debug, quiet, interactive)
52 52 else:
53 53 # parentui may point to an ui object which is already a child
54 54 self.parentui = parentui.parentui or parentui
55 55 self.readhooks = self.parentui.readhooks[:]
56 56 self.trusted_users = parentui.trusted_users.copy()
57 57 self.trusted_groups = parentui.trusted_groups.copy()
58 58 self.cdata = dupconfig(self.parentui.cdata)
59 59 if self.parentui.ucdata:
60 60 self.ucdata = dupconfig(self.parentui.ucdata)
61 61 if self.parentui.overlay:
62 62 self.overlay = dupconfig(self.parentui.overlay)
63 63
64 64 def __getattr__(self, key):
65 65 return getattr(self.parentui, key)
66 66
67 67 def updateopts(self, verbose=False, debug=False, quiet=False,
68 68 interactive=True, traceback=False, config=[]):
69 69 for section, name, value in config:
70 70 self.setconfig(section, name, value)
71 71
72 72 if quiet or verbose or debug:
73 73 self.setconfig('ui', 'quiet', str(bool(quiet)))
74 74 self.setconfig('ui', 'verbose', str(bool(verbose)))
75 75 self.setconfig('ui', 'debug', str(bool(debug)))
76 76
77 77 self.verbosity_constraints()
78 78
79 79 if not interactive:
80 80 self.setconfig('ui', 'interactive', 'False')
81 81 self.interactive = False
82 82
83 83 self.traceback = self.traceback or traceback
84 84
85 85 def verbosity_constraints(self):
86 86 self.quiet = self.configbool('ui', 'quiet')
87 87 self.verbose = self.configbool('ui', 'verbose')
88 88 self.debugflag = self.configbool('ui', 'debug')
89 89
90 90 if self.debugflag:
91 91 self.verbose = True
92 92 self.quiet = False
93 93 elif self.verbose and self.quiet:
94 94 self.quiet = self.verbose = False
95 95
96 96 def _is_trusted(self, fp, f, warn=True):
97 97 if not self.check_trusted:
98 98 return True
99 st = util.fstat(fp)
100 if util.isowner(fp, st):
101 return True
99 102 tusers = self.trusted_users
100 103 tgroups = self.trusted_groups
101 104 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
102 st = util.fstat(fp)
103 105 user = util.username(st.st_uid)
104 106 group = util.groupname(st.st_gid)
105 107 if user not in tusers and group not in tgroups:
106 108 if warn and self.report_untrusted:
107 109 self.warn(_('Not trusting file %s from untrusted '
108 110 'user %s, group %s\n') % (f, user, group))
109 111 return False
110 112 return True
111 113
112 114 def readconfig(self, fn, root=None):
113 115 if isinstance(fn, basestring):
114 116 fn = [fn]
115 117 for f in fn:
116 118 try:
117 119 fp = open(f)
118 120 except IOError:
119 121 continue
120 122 cdata = self.cdata
121 123 trusted = self._is_trusted(fp, f)
122 124 if not trusted:
123 125 if self.ucdata is None:
124 126 self.ucdata = dupconfig(self.cdata)
125 127 cdata = self.ucdata
126 128 elif self.ucdata is not None:
127 129 # use a separate configparser, so that we don't accidentally
128 130 # override ucdata settings later on.
129 131 cdata = util.configparser()
130 132
131 133 try:
132 134 cdata.readfp(fp, f)
133 135 except ConfigParser.ParsingError, inst:
134 136 msg = _("Failed to parse %s\n%s") % (f, inst)
135 137 if trusted:
136 138 raise util.Abort(msg)
137 139 self.warn(_("Ignored: %s\n") % msg)
138 140
139 141 if trusted:
140 142 if cdata != self.cdata:
141 143 updateconfig(cdata, self.cdata)
142 144 if self.ucdata is not None:
143 145 updateconfig(cdata, self.ucdata)
144 146 # override data from config files with data set with ui.setconfig
145 147 if self.overlay:
146 148 updateconfig(self.overlay, self.cdata)
147 149 if root is None:
148 150 root = os.path.expanduser('~')
149 151 self.fixconfig(root=root)
150 152 for hook in self.readhooks:
151 153 hook(self)
152 154
153 155 def addreadhook(self, hook):
154 156 self.readhooks.append(hook)
155 157
156 158 def readsections(self, filename, *sections):
157 159 """Read filename and add only the specified sections to the config data
158 160
159 161 The settings are added to the trusted config data.
160 162 """
161 163 if not sections:
162 164 return
163 165
164 166 cdata = util.configparser()
165 167 try:
166 168 cdata.read(filename)
167 169 except ConfigParser.ParsingError, inst:
168 170 raise util.Abort(_("failed to parse %s\n%s") % (filename,
169 171 inst))
170 172
171 173 for section in sections:
172 174 if not cdata.has_section(section):
173 175 cdata.add_section(section)
174 176
175 177 updateconfig(cdata, self.cdata, sections)
176 178 if self.ucdata:
177 179 updateconfig(cdata, self.ucdata, sections)
178 180
179 181 def fixconfig(self, section=None, name=None, value=None, root=None):
180 182 # translate paths relative to root (or home) into absolute paths
181 183 if section is None or section == 'paths':
182 184 if root is None:
183 185 root = os.getcwd()
184 186 items = section and [(name, value)] or []
185 187 for cdata in self.cdata, self.ucdata, self.overlay:
186 188 if not cdata: continue
187 189 if not items and cdata.has_section('paths'):
188 190 pathsitems = cdata.items('paths')
189 191 else:
190 192 pathsitems = items
191 193 for n, path in pathsitems:
192 194 if path and "://" not in path and not os.path.isabs(path):
193 195 cdata.set("paths", n, os.path.join(root, path))
194 196
195 197 # update quiet/verbose/debug and interactive status
196 198 if section is None or section == 'ui':
197 199 if name is None or name in ('quiet', 'verbose', 'debug'):
198 200 self.verbosity_constraints()
199 201
200 202 if name is None or name == 'interactive':
201 203 self.interactive = self.configbool("ui", "interactive", True)
202 204
203 205 # update trust information
204 206 if section is None or section == 'trusted':
205 207 user = util.username()
206 208 if user is not None:
207 209 self.trusted_users[user] = 1
208 210 for user in self.configlist('trusted', 'users'):
209 211 self.trusted_users[user] = 1
210 212 for group in self.configlist('trusted', 'groups'):
211 213 self.trusted_groups[group] = 1
212 214
213 215 def setconfig(self, section, name, value):
214 216 if not self.overlay:
215 217 self.overlay = util.configparser()
216 218 for cdata in (self.overlay, self.cdata, self.ucdata):
217 219 if not cdata: continue
218 220 if not cdata.has_section(section):
219 221 cdata.add_section(section)
220 222 cdata.set(section, name, value)
221 223 self.fixconfig(section, name, value)
222 224
223 225 def _get_cdata(self, untrusted):
224 226 if untrusted and self.ucdata:
225 227 return self.ucdata
226 228 return self.cdata
227 229
228 230 def _config(self, section, name, default, funcname, untrusted, abort):
229 231 cdata = self._get_cdata(untrusted)
230 232 if cdata.has_option(section, name):
231 233 try:
232 234 func = getattr(cdata, funcname)
233 235 return func(section, name)
234 236 except ConfigParser.InterpolationError, inst:
235 237 msg = _("Error in configuration section [%s] "
236 238 "parameter '%s':\n%s") % (section, name, inst)
237 239 if abort:
238 240 raise util.Abort(msg)
239 241 self.warn(_("Ignored: %s\n") % msg)
240 242 return default
241 243
242 244 def _configcommon(self, section, name, default, funcname, untrusted):
243 245 value = self._config(section, name, default, funcname,
244 246 untrusted, abort=True)
245 247 if self.debugflag and not untrusted and self.ucdata:
246 248 uvalue = self._config(section, name, None, funcname,
247 249 untrusted=True, abort=False)
248 250 if uvalue is not None and uvalue != value:
249 251 self.warn(_("Ignoring untrusted configuration option "
250 252 "%s.%s = %s\n") % (section, name, uvalue))
251 253 return value
252 254
253 255 def config(self, section, name, default=None, untrusted=False):
254 256 return self._configcommon(section, name, default, 'get', untrusted)
255 257
256 258 def configbool(self, section, name, default=False, untrusted=False):
257 259 return self._configcommon(section, name, default, 'getboolean',
258 260 untrusted)
259 261
260 262 def configlist(self, section, name, default=None, untrusted=False):
261 263 """Return a list of comma/space separated strings"""
262 264 result = self.config(section, name, untrusted=untrusted)
263 265 if result is None:
264 266 result = default or []
265 267 if isinstance(result, basestring):
266 268 result = result.replace(",", " ").split()
267 269 return result
268 270
269 271 def has_config(self, section, untrusted=False):
270 272 '''tell whether section exists in config.'''
271 273 cdata = self._get_cdata(untrusted)
272 274 return cdata.has_section(section)
273 275
274 276 def _configitems(self, section, untrusted, abort):
275 277 items = {}
276 278 cdata = self._get_cdata(untrusted)
277 279 if cdata.has_section(section):
278 280 try:
279 281 items.update(dict(cdata.items(section)))
280 282 except ConfigParser.InterpolationError, inst:
281 283 msg = _("Error in configuration section [%s]:\n"
282 284 "%s") % (section, inst)
283 285 if abort:
284 286 raise util.Abort(msg)
285 287 self.warn(_("Ignored: %s\n") % msg)
286 288 return items
287 289
288 290 def configitems(self, section, untrusted=False):
289 291 items = self._configitems(section, untrusted=untrusted, abort=True)
290 292 if self.debugflag and not untrusted and self.ucdata:
291 293 uitems = self._configitems(section, untrusted=True, abort=False)
292 294 keys = uitems.keys()
293 295 keys.sort()
294 296 for k in keys:
295 297 if uitems[k] != items.get(k):
296 298 self.warn(_("Ignoring untrusted configuration option "
297 299 "%s.%s = %s\n") % (section, k, uitems[k]))
298 300 x = items.items()
299 301 x.sort()
300 302 return x
301 303
302 304 def walkconfig(self, untrusted=False):
303 305 cdata = self._get_cdata(untrusted)
304 306 sections = cdata.sections()
305 307 sections.sort()
306 308 for section in sections:
307 309 for name, value in self.configitems(section, untrusted):
308 310 yield section, name, value.replace('\n', '\\n')
309 311
310 312 def extensions(self):
311 313 result = self.configitems("extensions")
312 314 for i, (key, value) in enumerate(result):
313 315 if value:
314 316 result[i] = (key, os.path.expanduser(value))
315 317 return result
316 318
317 319 def hgignorefiles(self):
318 320 result = []
319 321 for key, value in self.configitems("ui"):
320 322 if key == 'ignore' or key.startswith('ignore.'):
321 323 result.append(os.path.expanduser(value))
322 324 return result
323 325
324 326 def configrevlog(self):
325 327 result = {}
326 328 for key, value in self.configitems("revlog"):
327 329 result[key.lower()] = value
328 330 return result
329 331
330 332 def username(self):
331 333 """Return default username to be used in commits.
332 334
333 335 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
334 336 and stop searching if one of these is set.
335 337 Abort if no username is found, to force specifying the commit user
336 338 with line option or repo hgrc.
337 339 """
338 340 user = os.environ.get("HGUSER")
339 341 if user is None:
340 342 user = self.config("ui", "username")
341 343 if user is None:
342 344 user = os.environ.get("EMAIL")
343 345 if not user:
344 346 self.status(_("Please choose a commit username to be recorded "
345 347 "in the changelog via\ncommand line option "
346 348 '(-u "First Last <email@example.com>"), in the\n'
347 349 "configuration files (hgrc), or by setting the "
348 350 "EMAIL environment variable.\n\n"))
349 351 raise util.Abort(_("No commit username specified!"))
350 352 return user
351 353
352 354 def shortuser(self, user):
353 355 """Return a short representation of a user name or email address."""
354 356 if not self.verbose: user = util.shortuser(user)
355 357 return user
356 358
357 359 def expandpath(self, loc, default=None):
358 360 """Return repository location relative to cwd or from [paths]"""
359 361 if "://" in loc or os.path.isdir(loc):
360 362 return loc
361 363
362 364 path = self.config("paths", loc)
363 365 if not path and default is not None:
364 366 path = self.config("paths", default)
365 367 return path or loc
366 368
367 369 def write(self, *args):
368 370 for a in args:
369 371 sys.stdout.write(str(a))
370 372
371 373 def write_err(self, *args):
372 374 try:
373 375 if not sys.stdout.closed: sys.stdout.flush()
374 376 for a in args:
375 377 sys.stderr.write(str(a))
376 378 except IOError, inst:
377 379 if inst.errno != errno.EPIPE:
378 380 raise
379 381
380 382 def flush(self):
381 383 try: sys.stdout.flush()
382 384 except: pass
383 385 try: sys.stderr.flush()
384 386 except: pass
385 387
386 388 def readline(self):
387 389 return sys.stdin.readline()[:-1]
388 390 def prompt(self, msg, pat=None, default="y"):
389 391 if not self.interactive: return default
390 392 while 1:
391 393 self.write(msg, " ")
392 394 r = self.readline()
393 395 if not pat or re.match(pat, r):
394 396 return r
395 397 else:
396 398 self.write(_("unrecognized response\n"))
397 399 def getpass(self, prompt=None, default=None):
398 400 if not self.interactive: return default
399 401 return getpass.getpass(prompt or _('password: '))
400 402 def status(self, *msg):
401 403 if not self.quiet: self.write(*msg)
402 404 def warn(self, *msg):
403 405 self.write_err(*msg)
404 406 def note(self, *msg):
405 407 if self.verbose: self.write(*msg)
406 408 def debug(self, *msg):
407 409 if self.debugflag: self.write(*msg)
408 410 def edit(self, text, user):
409 411 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
410 412 text=True)
411 413 try:
412 414 f = os.fdopen(fd, "w")
413 415 f.write(text)
414 416 f.close()
415 417
416 418 editor = (os.environ.get("HGEDITOR") or
417 419 self.config("ui", "editor") or
418 420 os.environ.get("EDITOR", "vi"))
419 421
420 422 util.system("%s \"%s\"" % (editor, name),
421 423 environ={'HGUSER': user},
422 424 onerr=util.Abort, errprefix=_("edit failed"))
423 425
424 426 f = open(name)
425 427 t = f.read()
426 428 f.close()
427 429 t = re.sub("(?m)^HG:.*\n", "", t)
428 430 finally:
429 431 os.unlink(name)
430 432
431 433 return t
432 434
433 435 def print_exc(self):
434 436 '''print exception traceback if traceback printing enabled.
435 437 only to call in exception handler. returns true if traceback
436 438 printed.'''
437 439 if self.traceback:
438 440 traceback.print_exc()
439 441 return self.traceback
@@ -1,1070 +1,1085
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 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser")
19 19
20 20 # used by parsedate
21 21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 22 '%a %b %d %H:%M:%S %Y')
23 23
24 24 class SignalInterrupt(Exception):
25 25 """Exception raised on SIGTERM and SIGHUP."""
26 26
27 27 # like SafeConfigParser but with case-sensitive keys
28 28 class configparser(ConfigParser.SafeConfigParser):
29 29 def optionxform(self, optionstr):
30 30 return optionstr
31 31
32 32 def cachefunc(func):
33 33 '''cache the result of function calls'''
34 34 # XXX doesn't handle keywords args
35 35 cache = {}
36 36 if func.func_code.co_argcount == 1:
37 37 # we gain a small amount of time because
38 38 # we don't need to pack/unpack the list
39 39 def f(arg):
40 40 if arg not in cache:
41 41 cache[arg] = func(arg)
42 42 return cache[arg]
43 43 else:
44 44 def f(*args):
45 45 if args not in cache:
46 46 cache[args] = func(*args)
47 47 return cache[args]
48 48
49 49 return f
50 50
51 51 def pipefilter(s, cmd):
52 52 '''filter string S through command CMD, returning its output'''
53 53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
54 54 def writer():
55 55 try:
56 56 pin.write(s)
57 57 pin.close()
58 58 except IOError, inst:
59 59 if inst.errno != errno.EPIPE:
60 60 raise
61 61
62 62 # we should use select instead on UNIX, but this will work on most
63 63 # systems, including Windows
64 64 w = threading.Thread(target=writer)
65 65 w.start()
66 66 f = pout.read()
67 67 pout.close()
68 68 w.join()
69 69 return f
70 70
71 71 def tempfilter(s, cmd):
72 72 '''filter string S through a pair of temporary files with CMD.
73 73 CMD is used as a template to create the real command to be run,
74 74 with the strings INFILE and OUTFILE replaced by the real names of
75 75 the temporary files generated.'''
76 76 inname, outname = None, None
77 77 try:
78 78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
79 79 fp = os.fdopen(infd, 'wb')
80 80 fp.write(s)
81 81 fp.close()
82 82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
83 83 os.close(outfd)
84 84 cmd = cmd.replace('INFILE', inname)
85 85 cmd = cmd.replace('OUTFILE', outname)
86 86 code = os.system(cmd)
87 87 if code: raise Abort(_("command '%s' failed: %s") %
88 88 (cmd, explain_exit(code)))
89 89 return open(outname, 'rb').read()
90 90 finally:
91 91 try:
92 92 if inname: os.unlink(inname)
93 93 except: pass
94 94 try:
95 95 if outname: os.unlink(outname)
96 96 except: pass
97 97
98 98 filtertable = {
99 99 'tempfile:': tempfilter,
100 100 'pipe:': pipefilter,
101 101 }
102 102
103 103 def filter(s, cmd):
104 104 "filter a string through a command that transforms its input to its output"
105 105 for name, fn in filtertable.iteritems():
106 106 if cmd.startswith(name):
107 107 return fn(s, cmd[len(name):].lstrip())
108 108 return pipefilter(s, cmd)
109 109
110 110 def find_in_path(name, path, default=None):
111 111 '''find name in search path. path can be string (will be split
112 112 with os.pathsep), or iterable thing that returns strings. if name
113 113 found, return path to name. else return default.'''
114 114 if isinstance(path, str):
115 115 path = path.split(os.pathsep)
116 116 for p in path:
117 117 p_name = os.path.join(p, name)
118 118 if os.path.exists(p_name):
119 119 return p_name
120 120 return default
121 121
122 122 def binary(s):
123 123 """return true if a string is binary data using diff's heuristic"""
124 124 if s and '\0' in s[:4096]:
125 125 return True
126 126 return False
127 127
128 128 def unique(g):
129 129 """return the uniq elements of iterable g"""
130 130 seen = {}
131 131 l = []
132 132 for f in g:
133 133 if f not in seen:
134 134 seen[f] = 1
135 135 l.append(f)
136 136 return l
137 137
138 138 class Abort(Exception):
139 139 """Raised if a command needs to print an error and exit."""
140 140
141 141 class UnexpectedOutput(Abort):
142 142 """Raised to print an error with part of output and exit."""
143 143
144 144 def always(fn): return True
145 145 def never(fn): return False
146 146
147 147 def patkind(name, dflt_pat='glob'):
148 148 """Split a string into an optional pattern kind prefix and the
149 149 actual pattern."""
150 150 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
151 151 if name.startswith(prefix + ':'): return name.split(':', 1)
152 152 return dflt_pat, name
153 153
154 154 def globre(pat, head='^', tail='$'):
155 155 "convert a glob pattern into a regexp"
156 156 i, n = 0, len(pat)
157 157 res = ''
158 158 group = False
159 159 def peek(): return i < n and pat[i]
160 160 while i < n:
161 161 c = pat[i]
162 162 i = i+1
163 163 if c == '*':
164 164 if peek() == '*':
165 165 i += 1
166 166 res += '.*'
167 167 else:
168 168 res += '[^/]*'
169 169 elif c == '?':
170 170 res += '.'
171 171 elif c == '[':
172 172 j = i
173 173 if j < n and pat[j] in '!]':
174 174 j += 1
175 175 while j < n and pat[j] != ']':
176 176 j += 1
177 177 if j >= n:
178 178 res += '\\['
179 179 else:
180 180 stuff = pat[i:j].replace('\\','\\\\')
181 181 i = j + 1
182 182 if stuff[0] == '!':
183 183 stuff = '^' + stuff[1:]
184 184 elif stuff[0] == '^':
185 185 stuff = '\\' + stuff
186 186 res = '%s[%s]' % (res, stuff)
187 187 elif c == '{':
188 188 group = True
189 189 res += '(?:'
190 190 elif c == '}' and group:
191 191 res += ')'
192 192 group = False
193 193 elif c == ',' and group:
194 194 res += '|'
195 195 elif c == '\\':
196 196 p = peek()
197 197 if p:
198 198 i += 1
199 199 res += re.escape(p)
200 200 else:
201 201 res += re.escape(c)
202 202 else:
203 203 res += re.escape(c)
204 204 return head + res + tail
205 205
206 206 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
207 207
208 208 def pathto(n1, n2):
209 209 '''return the relative path from one place to another.
210 210 n1 should use os.sep to separate directories
211 211 n2 should use "/" to separate directories
212 212 returns an os.sep-separated path.
213 213 '''
214 214 if not n1: return localpath(n2)
215 215 a, b = n1.split(os.sep), n2.split('/')
216 216 a.reverse()
217 217 b.reverse()
218 218 while a and b and a[-1] == b[-1]:
219 219 a.pop()
220 220 b.pop()
221 221 b.reverse()
222 222 return os.sep.join((['..'] * len(a)) + b)
223 223
224 224 def canonpath(root, cwd, myname):
225 225 """return the canonical path of myname, given cwd and root"""
226 226 if root == os.sep:
227 227 rootsep = os.sep
228 228 elif root.endswith(os.sep):
229 229 rootsep = root
230 230 else:
231 231 rootsep = root + os.sep
232 232 name = myname
233 233 if not os.path.isabs(name):
234 234 name = os.path.join(root, cwd, name)
235 235 name = os.path.normpath(name)
236 236 if name != rootsep and name.startswith(rootsep):
237 237 name = name[len(rootsep):]
238 238 audit_path(name)
239 239 return pconvert(name)
240 240 elif name == root:
241 241 return ''
242 242 else:
243 243 # Determine whether `name' is in the hierarchy at or beneath `root',
244 244 # by iterating name=dirname(name) until that causes no change (can't
245 245 # check name == '/', because that doesn't work on windows). For each
246 246 # `name', compare dev/inode numbers. If they match, the list `rel'
247 247 # holds the reversed list of components making up the relative file
248 248 # name we want.
249 249 root_st = os.stat(root)
250 250 rel = []
251 251 while True:
252 252 try:
253 253 name_st = os.stat(name)
254 254 except OSError:
255 255 break
256 256 if samestat(name_st, root_st):
257 257 rel.reverse()
258 258 name = os.path.join(*rel)
259 259 audit_path(name)
260 260 return pconvert(name)
261 261 dirname, basename = os.path.split(name)
262 262 rel.append(basename)
263 263 if dirname == name:
264 264 break
265 265 name = dirname
266 266
267 267 raise Abort('%s not under root' % myname)
268 268
269 269 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
270 270 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
271 271
272 272 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
273 273 if os.name == 'nt':
274 274 dflt_pat = 'glob'
275 275 else:
276 276 dflt_pat = 'relpath'
277 277 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
278 278
279 279 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
280 280 """build a function to match a set of file patterns
281 281
282 282 arguments:
283 283 canonroot - the canonical root of the tree you're matching against
284 284 cwd - the current working directory, if relevant
285 285 names - patterns to find
286 286 inc - patterns to include
287 287 exc - patterns to exclude
288 288 head - a regex to prepend to patterns to control whether a match is rooted
289 289
290 290 a pattern is one of:
291 291 'glob:<rooted glob>'
292 292 're:<rooted regexp>'
293 293 'path:<rooted path>'
294 294 'relglob:<relative glob>'
295 295 'relpath:<relative path>'
296 296 'relre:<relative regexp>'
297 297 '<rooted path or regexp>'
298 298
299 299 returns:
300 300 a 3-tuple containing
301 301 - list of explicit non-pattern names passed in
302 302 - a bool match(filename) function
303 303 - a bool indicating if any patterns were passed in
304 304
305 305 todo:
306 306 make head regex a rooted bool
307 307 """
308 308
309 309 def contains_glob(name):
310 310 for c in name:
311 311 if c in _globchars: return True
312 312 return False
313 313
314 314 def regex(kind, name, tail):
315 315 '''convert a pattern into a regular expression'''
316 316 if kind == 're':
317 317 return name
318 318 elif kind == 'path':
319 319 return '^' + re.escape(name) + '(?:/|$)'
320 320 elif kind == 'relglob':
321 321 return head + globre(name, '(?:|.*/)', tail)
322 322 elif kind == 'relpath':
323 323 return head + re.escape(name) + tail
324 324 elif kind == 'relre':
325 325 if name.startswith('^'):
326 326 return name
327 327 return '.*' + name
328 328 return head + globre(name, '', tail)
329 329
330 330 def matchfn(pats, tail):
331 331 """build a matching function from a set of patterns"""
332 332 if not pats:
333 333 return
334 334 matches = []
335 335 for k, p in pats:
336 336 try:
337 337 pat = '(?:%s)' % regex(k, p, tail)
338 338 matches.append(re.compile(pat).match)
339 339 except re.error:
340 340 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
341 341 else: raise Abort("invalid pattern (%s): %s" % (k, p))
342 342
343 343 def buildfn(text):
344 344 for m in matches:
345 345 r = m(text)
346 346 if r:
347 347 return r
348 348
349 349 return buildfn
350 350
351 351 def globprefix(pat):
352 352 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
353 353 root = []
354 354 for p in pat.split(os.sep):
355 355 if contains_glob(p): break
356 356 root.append(p)
357 357 return '/'.join(root)
358 358
359 359 pats = []
360 360 files = []
361 361 roots = []
362 362 for kind, name in [patkind(p, dflt_pat) for p in names]:
363 363 if kind in ('glob', 'relpath'):
364 364 name = canonpath(canonroot, cwd, name)
365 365 if name == '':
366 366 kind, name = 'glob', '**'
367 367 if kind in ('glob', 'path', 're'):
368 368 pats.append((kind, name))
369 369 if kind == 'glob':
370 370 root = globprefix(name)
371 371 if root: roots.append(root)
372 372 elif kind == 'relpath':
373 373 files.append((kind, name))
374 374 roots.append(name)
375 375
376 376 patmatch = matchfn(pats, '$') or always
377 377 filematch = matchfn(files, '(?:/|$)') or always
378 378 incmatch = always
379 379 if inc:
380 380 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
381 381 incmatch = matchfn(inckinds, '(?:/|$)')
382 382 excmatch = lambda fn: False
383 383 if exc:
384 384 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
385 385 excmatch = matchfn(exckinds, '(?:/|$)')
386 386
387 387 return (roots,
388 388 lambda fn: (incmatch(fn) and not excmatch(fn) and
389 389 (fn.endswith('/') or
390 390 (not pats and not files) or
391 391 (pats and patmatch(fn)) or
392 392 (files and filematch(fn)))),
393 393 (inc or exc or (pats and pats != [('glob', '**')])) and True)
394 394
395 395 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
396 396 '''enhanced shell command execution.
397 397 run with environment maybe modified, maybe in different dir.
398 398
399 399 if command fails and onerr is None, return status. if ui object,
400 400 print error message and return status, else raise onerr object as
401 401 exception.'''
402 402 def py2shell(val):
403 403 'convert python object into string that is useful to shell'
404 404 if val in (None, False):
405 405 return '0'
406 406 if val == True:
407 407 return '1'
408 408 return str(val)
409 409 oldenv = {}
410 410 for k in environ:
411 411 oldenv[k] = os.environ.get(k)
412 412 if cwd is not None:
413 413 oldcwd = os.getcwd()
414 414 try:
415 415 for k, v in environ.iteritems():
416 416 os.environ[k] = py2shell(v)
417 417 if cwd is not None and oldcwd != cwd:
418 418 os.chdir(cwd)
419 419 rc = os.system(cmd)
420 420 if rc and onerr:
421 421 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
422 422 explain_exit(rc)[0])
423 423 if errprefix:
424 424 errmsg = '%s: %s' % (errprefix, errmsg)
425 425 try:
426 426 onerr.warn(errmsg + '\n')
427 427 except AttributeError:
428 428 raise onerr(errmsg)
429 429 return rc
430 430 finally:
431 431 for k, v in oldenv.iteritems():
432 432 if v is None:
433 433 del os.environ[k]
434 434 else:
435 435 os.environ[k] = v
436 436 if cwd is not None and oldcwd != cwd:
437 437 os.chdir(oldcwd)
438 438
439 439 def rename(src, dst):
440 440 """forcibly rename a file"""
441 441 try:
442 442 os.rename(src, dst)
443 443 except OSError, err:
444 444 # on windows, rename to existing file is not allowed, so we
445 445 # must delete destination first. but if file is open, unlink
446 446 # schedules it for delete but does not delete it. rename
447 447 # happens immediately even for open files, so we create
448 448 # temporary file, delete it, rename destination to that name,
449 449 # then delete that. then rename is safe to do.
450 450 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
451 451 os.close(fd)
452 452 os.unlink(temp)
453 453 os.rename(dst, temp)
454 454 os.unlink(temp)
455 455 os.rename(src, dst)
456 456
457 457 def unlink(f):
458 458 """unlink and remove the directory if it is empty"""
459 459 os.unlink(f)
460 460 # try removing directories that might now be empty
461 461 try:
462 462 os.removedirs(os.path.dirname(f))
463 463 except OSError:
464 464 pass
465 465
466 466 def copyfile(src, dest):
467 467 "copy a file, preserving mode"
468 468 try:
469 469 shutil.copyfile(src, dest)
470 470 shutil.copymode(src, dest)
471 471 except shutil.Error, inst:
472 472 raise util.Abort(str(inst))
473 473
474 474 def copyfiles(src, dst, hardlink=None):
475 475 """Copy a directory tree using hardlinks if possible"""
476 476
477 477 if hardlink is None:
478 478 hardlink = (os.stat(src).st_dev ==
479 479 os.stat(os.path.dirname(dst)).st_dev)
480 480
481 481 if os.path.isdir(src):
482 482 os.mkdir(dst)
483 483 for name in os.listdir(src):
484 484 srcname = os.path.join(src, name)
485 485 dstname = os.path.join(dst, name)
486 486 copyfiles(srcname, dstname, hardlink)
487 487 else:
488 488 if hardlink:
489 489 try:
490 490 os_link(src, dst)
491 491 except (IOError, OSError):
492 492 hardlink = False
493 493 shutil.copy(src, dst)
494 494 else:
495 495 shutil.copy(src, dst)
496 496
497 497 def audit_path(path):
498 498 """Abort if path contains dangerous components"""
499 499 parts = os.path.normcase(path).split(os.sep)
500 500 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
501 501 or os.pardir in parts):
502 502 raise Abort(_("path contains illegal component: %s\n") % path)
503 503
504 504 def _makelock_file(info, pathname):
505 505 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
506 506 os.write(ld, info)
507 507 os.close(ld)
508 508
509 509 def _readlock_file(pathname):
510 510 return posixfile(pathname).read()
511 511
512 512 def nlinks(pathname):
513 513 """Return number of hardlinks for the given file."""
514 514 return os.lstat(pathname).st_nlink
515 515
516 516 if hasattr(os, 'link'):
517 517 os_link = os.link
518 518 else:
519 519 def os_link(src, dst):
520 520 raise OSError(0, _("Hardlinks not supported"))
521 521
522 522 def fstat(fp):
523 523 '''stat file object that may not have fileno method.'''
524 524 try:
525 525 return os.fstat(fp.fileno())
526 526 except AttributeError:
527 527 return os.stat(fp.name)
528 528
529 529 posixfile = file
530 530
531 531 def is_win_9x():
532 532 '''return true if run on windows 95, 98 or me.'''
533 533 try:
534 534 return sys.getwindowsversion()[3] == 1
535 535 except AttributeError:
536 536 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
537 537
538 538 def username(uid=None):
539 539 """Return the name of the user with the given uid.
540 540
541 541 If uid is None, return the name of the current user."""
542 542 try:
543 543 import pwd
544 544 if uid is None:
545 545 uid = os.getuid()
546 546 try:
547 547 return pwd.getpwuid(uid)[0]
548 548 except KeyError:
549 549 return str(uid)
550 550 except ImportError:
551 551 return None
552 552
553 553 def groupname(gid=None):
554 554 """Return the name of the group with the given gid.
555 555
556 556 If gid is None, return the name of the current group."""
557 557 try:
558 558 import grp
559 559 if gid is None:
560 560 gid = os.getgid()
561 561 try:
562 562 return grp.getgrgid(gid)[0]
563 563 except KeyError:
564 564 return str(gid)
565 565 except ImportError:
566 566 return None
567 567
568 568 # Platform specific variants
569 569 if os.name == 'nt':
570 570 demandload(globals(), "msvcrt")
571 571 nulldev = 'NUL:'
572 572
573 573 class winstdout:
574 574 '''stdout on windows misbehaves if sent through a pipe'''
575 575
576 576 def __init__(self, fp):
577 577 self.fp = fp
578 578
579 579 def __getattr__(self, key):
580 580 return getattr(self.fp, key)
581 581
582 582 def close(self):
583 583 try:
584 584 self.fp.close()
585 585 except: pass
586 586
587 587 def write(self, s):
588 588 try:
589 589 return self.fp.write(s)
590 590 except IOError, inst:
591 591 if inst.errno != 0: raise
592 592 self.close()
593 593 raise IOError(errno.EPIPE, 'Broken pipe')
594 594
595 595 sys.stdout = winstdout(sys.stdout)
596 596
597 597 def system_rcpath():
598 598 try:
599 599 return system_rcpath_win32()
600 600 except:
601 601 return [r'c:\mercurial\mercurial.ini']
602 602
603 603 def os_rcpath():
604 604 '''return default os-specific hgrc search path'''
605 605 path = system_rcpath()
606 606 path.append(user_rcpath())
607 607 userprofile = os.environ.get('USERPROFILE')
608 608 if userprofile:
609 609 path.append(os.path.join(userprofile, 'mercurial.ini'))
610 610 return path
611 611
612 612 def user_rcpath():
613 613 '''return os-specific hgrc search path to the user dir'''
614 614 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
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[0] == '`':
620 620 pf = pf[1:-1] # Remove the quotes
621 621 return pf
622 622
623 623 def testpid(pid):
624 624 '''return False if pid dead, True if running or not known'''
625 625 return True
626 626
627 627 def is_exec(f, last):
628 628 return last
629 629
630 630 def set_exec(f, mode):
631 631 pass
632 632
633 633 def set_binary(fd):
634 634 msvcrt.setmode(fd.fileno(), os.O_BINARY)
635 635
636 636 def pconvert(path):
637 637 return path.replace("\\", "/")
638 638
639 639 def localpath(path):
640 640 return path.replace('/', '\\')
641 641
642 642 def normpath(path):
643 643 return pconvert(os.path.normpath(path))
644 644
645 645 makelock = _makelock_file
646 646 readlock = _readlock_file
647 647
648 648 def samestat(s1, s2):
649 649 return False
650 650
651 651 def shellquote(s):
652 652 return '"%s"' % s.replace('"', '\\"')
653 653
654 654 def explain_exit(code):
655 655 return _("exited with status %d") % code, code
656 656
657 # if you change this stub into a real check, please try to implement the
658 # username and groupname functions above, too.
659 def isowner(fp, st=None):
660 return True
661
657 662 try:
658 663 # override functions with win32 versions if possible
659 664 from util_win32 import *
660 665 if not is_win_9x():
661 666 posixfile = posixfile_nt
662 667 except ImportError:
663 668 pass
664 669
665 670 else:
666 671 nulldev = '/dev/null'
667 672
668 673 def rcfiles(path):
669 674 rcs = [os.path.join(path, 'hgrc')]
670 675 rcdir = os.path.join(path, 'hgrc.d')
671 676 try:
672 677 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
673 678 if f.endswith(".rc")])
674 679 except OSError:
675 680 pass
676 681 return rcs
677 682
678 683 def os_rcpath():
679 684 '''return default os-specific hgrc search path'''
680 685 path = []
681 686 # old mod_python does not set sys.argv
682 687 if len(getattr(sys, 'argv', [])) > 0:
683 688 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
684 689 '/../etc/mercurial'))
685 690 path.extend(rcfiles('/etc/mercurial'))
686 691 path.append(os.path.expanduser('~/.hgrc'))
687 692 path = [os.path.normpath(f) for f in path]
688 693 return path
689 694
690 695 def parse_patch_output(output_line):
691 696 """parses the output produced by patch and returns the file name"""
692 697 pf = output_line[14:]
693 698 if pf.startswith("'") and pf.endswith("'") and " " in pf:
694 699 pf = pf[1:-1] # Remove the quotes
695 700 return pf
696 701
697 702 def is_exec(f, last):
698 703 """check whether a file is executable"""
699 704 return (os.lstat(f).st_mode & 0100 != 0)
700 705
701 706 def set_exec(f, mode):
702 707 s = os.lstat(f).st_mode
703 708 if (s & 0100 != 0) == mode:
704 709 return
705 710 if mode:
706 711 # Turn on +x for every +r bit when making a file executable
707 712 # and obey umask.
708 713 umask = os.umask(0)
709 714 os.umask(umask)
710 715 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
711 716 else:
712 717 os.chmod(f, s & 0666)
713 718
714 719 def set_binary(fd):
715 720 pass
716 721
717 722 def pconvert(path):
718 723 return path
719 724
720 725 def localpath(path):
721 726 return path
722 727
723 728 normpath = os.path.normpath
724 729 samestat = os.path.samestat
725 730
726 731 def makelock(info, pathname):
727 732 try:
728 733 os.symlink(info, pathname)
729 734 except OSError, why:
730 735 if why.errno == errno.EEXIST:
731 736 raise
732 737 else:
733 738 _makelock_file(info, pathname)
734 739
735 740 def readlock(pathname):
736 741 try:
737 742 return os.readlink(pathname)
738 743 except OSError, why:
739 744 if why.errno == errno.EINVAL:
740 745 return _readlock_file(pathname)
741 746 else:
742 747 raise
743 748
744 749 def shellquote(s):
745 750 return "'%s'" % s.replace("'", "'\\''")
746 751
747 752 def testpid(pid):
748 753 '''return False if pid dead, True if running or not sure'''
749 754 try:
750 755 os.kill(pid, 0)
751 756 return True
752 757 except OSError, inst:
753 758 return inst.errno != errno.ESRCH
754 759
755 760 def explain_exit(code):
756 761 """return a 2-tuple (desc, code) describing a process's status"""
757 762 if os.WIFEXITED(code):
758 763 val = os.WEXITSTATUS(code)
759 764 return _("exited with status %d") % val, val
760 765 elif os.WIFSIGNALED(code):
761 766 val = os.WTERMSIG(code)
762 767 return _("killed by signal %d") % val, val
763 768 elif os.WIFSTOPPED(code):
764 769 val = os.WSTOPSIG(code)
765 770 return _("stopped by signal %d") % val, val
766 771 raise ValueError(_("invalid exit code"))
767 772
773 def isowner(fp, st=None):
774 """Return True if the file object f belongs to the current user.
775
776 The return value of a util.fstat(f) may be passed as the st argument.
777 """
778 if st is None:
779 st = fstat(f)
780 return st.st_uid == os.getuid()
781
782
768 783 def opener(base, audit=True):
769 784 """
770 785 return a function that opens files relative to base
771 786
772 787 this function is used to hide the details of COW semantics and
773 788 remote file access from higher level code.
774 789 """
775 790 p = base
776 791 audit_p = audit
777 792
778 793 def mktempcopy(name):
779 794 d, fn = os.path.split(name)
780 795 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
781 796 os.close(fd)
782 797 ofp = posixfile(temp, "wb")
783 798 try:
784 799 try:
785 800 ifp = posixfile(name, "rb")
786 801 except IOError, inst:
787 802 if not getattr(inst, 'filename', None):
788 803 inst.filename = name
789 804 raise
790 805 for chunk in filechunkiter(ifp):
791 806 ofp.write(chunk)
792 807 ifp.close()
793 808 ofp.close()
794 809 except:
795 810 try: os.unlink(temp)
796 811 except: pass
797 812 raise
798 813 st = os.lstat(name)
799 814 os.chmod(temp, st.st_mode)
800 815 return temp
801 816
802 817 class atomictempfile(posixfile):
803 818 """the file will only be copied when rename is called"""
804 819 def __init__(self, name, mode):
805 820 self.__name = name
806 821 self.temp = mktempcopy(name)
807 822 posixfile.__init__(self, self.temp, mode)
808 823 def rename(self):
809 824 if not self.closed:
810 825 posixfile.close(self)
811 826 rename(self.temp, localpath(self.__name))
812 827 def __del__(self):
813 828 if not self.closed:
814 829 try:
815 830 os.unlink(self.temp)
816 831 except: pass
817 832 posixfile.close(self)
818 833
819 834 class atomicfile(atomictempfile):
820 835 """the file will only be copied on close"""
821 836 def __init__(self, name, mode):
822 837 atomictempfile.__init__(self, name, mode)
823 838 def close(self):
824 839 self.rename()
825 840 def __del__(self):
826 841 self.rename()
827 842
828 843 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
829 844 if audit_p:
830 845 audit_path(path)
831 846 f = os.path.join(p, path)
832 847
833 848 if not text:
834 849 mode += "b" # for that other OS
835 850
836 851 if mode[0] != "r":
837 852 try:
838 853 nlink = nlinks(f)
839 854 except OSError:
840 855 d = os.path.dirname(f)
841 856 if not os.path.isdir(d):
842 857 os.makedirs(d)
843 858 else:
844 859 if atomic:
845 860 return atomicfile(f, mode)
846 861 elif atomictemp:
847 862 return atomictempfile(f, mode)
848 863 if nlink > 1:
849 864 rename(mktempcopy(f), f)
850 865 return posixfile(f, mode)
851 866
852 867 return o
853 868
854 869 class chunkbuffer(object):
855 870 """Allow arbitrary sized chunks of data to be efficiently read from an
856 871 iterator over chunks of arbitrary size."""
857 872
858 873 def __init__(self, in_iter, targetsize = 2**16):
859 874 """in_iter is the iterator that's iterating over the input chunks.
860 875 targetsize is how big a buffer to try to maintain."""
861 876 self.in_iter = iter(in_iter)
862 877 self.buf = ''
863 878 self.targetsize = int(targetsize)
864 879 if self.targetsize <= 0:
865 880 raise ValueError(_("targetsize must be greater than 0, was %d") %
866 881 targetsize)
867 882 self.iterempty = False
868 883
869 884 def fillbuf(self):
870 885 """Ignore target size; read every chunk from iterator until empty."""
871 886 if not self.iterempty:
872 887 collector = cStringIO.StringIO()
873 888 collector.write(self.buf)
874 889 for ch in self.in_iter:
875 890 collector.write(ch)
876 891 self.buf = collector.getvalue()
877 892 self.iterempty = True
878 893
879 894 def read(self, l):
880 895 """Read L bytes of data from the iterator of chunks of data.
881 896 Returns less than L bytes if the iterator runs dry."""
882 897 if l > len(self.buf) and not self.iterempty:
883 898 # Clamp to a multiple of self.targetsize
884 899 targetsize = self.targetsize * ((l // self.targetsize) + 1)
885 900 collector = cStringIO.StringIO()
886 901 collector.write(self.buf)
887 902 collected = len(self.buf)
888 903 for chunk in self.in_iter:
889 904 collector.write(chunk)
890 905 collected += len(chunk)
891 906 if collected >= targetsize:
892 907 break
893 908 if collected < targetsize:
894 909 self.iterempty = True
895 910 self.buf = collector.getvalue()
896 911 s, self.buf = self.buf[:l], buffer(self.buf, l)
897 912 return s
898 913
899 914 def filechunkiter(f, size=65536, limit=None):
900 915 """Create a generator that produces the data in the file size
901 916 (default 65536) bytes at a time, up to optional limit (default is
902 917 to read all data). Chunks may be less than size bytes if the
903 918 chunk is the last chunk in the file, or the file is a socket or
904 919 some other type of file that sometimes reads less data than is
905 920 requested."""
906 921 assert size >= 0
907 922 assert limit is None or limit >= 0
908 923 while True:
909 924 if limit is None: nbytes = size
910 925 else: nbytes = min(limit, size)
911 926 s = nbytes and f.read(nbytes)
912 927 if not s: break
913 928 if limit: limit -= len(s)
914 929 yield s
915 930
916 931 def makedate():
917 932 lt = time.localtime()
918 933 if lt[8] == 1 and time.daylight:
919 934 tz = time.altzone
920 935 else:
921 936 tz = time.timezone
922 937 return time.mktime(lt), tz
923 938
924 939 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
925 940 """represent a (unixtime, offset) tuple as a localized time.
926 941 unixtime is seconds since the epoch, and offset is the time zone's
927 942 number of seconds away from UTC. if timezone is false, do not
928 943 append time zone to string."""
929 944 t, tz = date or makedate()
930 945 s = time.strftime(format, time.gmtime(float(t) - tz))
931 946 if timezone:
932 947 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
933 948 return s
934 949
935 950 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
936 951 """parse a localized time string and return a (unixtime, offset) tuple.
937 952 if the string cannot be parsed, ValueError is raised."""
938 953 def hastimezone(string):
939 954 return (string[-4:].isdigit() and
940 955 (string[-5] == '+' or string[-5] == '-') and
941 956 string[-6].isspace())
942 957
943 958 # NOTE: unixtime = localunixtime + offset
944 959 if hastimezone(string):
945 960 date, tz = string[:-6], string[-5:]
946 961 tz = int(tz)
947 962 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
948 963 else:
949 964 date, offset = string, None
950 965 timetuple = time.strptime(date, format)
951 966 localunixtime = int(calendar.timegm(timetuple))
952 967 if offset is None:
953 968 # local timezone
954 969 unixtime = int(time.mktime(timetuple))
955 970 offset = unixtime - localunixtime
956 971 else:
957 972 unixtime = localunixtime + offset
958 973 return unixtime, offset
959 974
960 975 def parsedate(string, formats=None):
961 976 """parse a localized time string and return a (unixtime, offset) tuple.
962 977 The date may be a "unixtime offset" string or in one of the specified
963 978 formats."""
964 979 if not formats:
965 980 formats = defaultdateformats
966 981 try:
967 982 when, offset = map(int, string.split(' '))
968 983 except ValueError:
969 984 for format in formats:
970 985 try:
971 986 when, offset = strdate(string, format)
972 987 except ValueError:
973 988 pass
974 989 else:
975 990 break
976 991 else:
977 992 raise ValueError(_('invalid date: %r '
978 993 'see hg(1) manual page for details')
979 994 % string)
980 995 # validate explicit (probably user-specified) date and
981 996 # time zone offset. values must fit in signed 32 bits for
982 997 # current 32-bit linux runtimes. timezones go from UTC-12
983 998 # to UTC+14
984 999 if abs(when) > 0x7fffffff:
985 1000 raise ValueError(_('date exceeds 32 bits: %d') % when)
986 1001 if offset < -50400 or offset > 43200:
987 1002 raise ValueError(_('impossible time zone offset: %d') % offset)
988 1003 return when, offset
989 1004
990 1005 def shortuser(user):
991 1006 """Return a short representation of a user name or email address."""
992 1007 f = user.find('@')
993 1008 if f >= 0:
994 1009 user = user[:f]
995 1010 f = user.find('<')
996 1011 if f >= 0:
997 1012 user = user[f+1:]
998 1013 f = user.find(' ')
999 1014 if f >= 0:
1000 1015 user = user[:f]
1001 1016 f = user.find('.')
1002 1017 if f >= 0:
1003 1018 user = user[:f]
1004 1019 return user
1005 1020
1006 1021 def walkrepos(path):
1007 1022 '''yield every hg repository under path, recursively.'''
1008 1023 def errhandler(err):
1009 1024 if err.filename == path:
1010 1025 raise err
1011 1026
1012 1027 for root, dirs, files in os.walk(path, onerror=errhandler):
1013 1028 for d in dirs:
1014 1029 if d == '.hg':
1015 1030 yield root
1016 1031 dirs[:] = []
1017 1032 break
1018 1033
1019 1034 _rcpath = None
1020 1035
1021 1036 def rcpath():
1022 1037 '''return hgrc search path. if env var HGRCPATH is set, use it.
1023 1038 for each item in path, if directory, use files ending in .rc,
1024 1039 else use item.
1025 1040 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1026 1041 if no HGRCPATH, use default os-specific path.'''
1027 1042 global _rcpath
1028 1043 if _rcpath is None:
1029 1044 if 'HGRCPATH' in os.environ:
1030 1045 _rcpath = []
1031 1046 for p in os.environ['HGRCPATH'].split(os.pathsep):
1032 1047 if not p: continue
1033 1048 if os.path.isdir(p):
1034 1049 for f in os.listdir(p):
1035 1050 if f.endswith('.rc'):
1036 1051 _rcpath.append(os.path.join(p, f))
1037 1052 else:
1038 1053 _rcpath.append(p)
1039 1054 else:
1040 1055 _rcpath = os_rcpath()
1041 1056 return _rcpath
1042 1057
1043 1058 def bytecount(nbytes):
1044 1059 '''return byte count formatted as readable string, with units'''
1045 1060
1046 1061 units = (
1047 1062 (100, 1<<30, _('%.0f GB')),
1048 1063 (10, 1<<30, _('%.1f GB')),
1049 1064 (1, 1<<30, _('%.2f GB')),
1050 1065 (100, 1<<20, _('%.0f MB')),
1051 1066 (10, 1<<20, _('%.1f MB')),
1052 1067 (1, 1<<20, _('%.2f MB')),
1053 1068 (100, 1<<10, _('%.0f KB')),
1054 1069 (10, 1<<10, _('%.1f KB')),
1055 1070 (1, 1<<10, _('%.2f KB')),
1056 1071 (1, 1, _('%.0f bytes')),
1057 1072 )
1058 1073
1059 1074 for multiplier, divisor, format in units:
1060 1075 if nbytes >= divisor * multiplier:
1061 1076 return format % (nbytes / float(divisor))
1062 1077 return units[-1][2] % nbytes
1063 1078
1064 1079 def drop_scheme(scheme, path):
1065 1080 sc = scheme + ':'
1066 1081 if path.startswith(sc):
1067 1082 path = path[len(sc):]
1068 1083 if path.startswith('//'):
1069 1084 path = path[2:]
1070 1085 return path
@@ -1,204 +1,208
1 1 #!/usr/bin/env python
2 2 # Since it's not easy to write a test that portably deals
3 3 # with files from different users/groups, we cheat a bit by
4 4 # monkey-patching some functions in the util module
5 5
6 6 import os
7 7 from mercurial import ui, util
8 8
9 9 hgrc = os.environ['HGRCPATH']
10 10
11 11 def testui(user='foo', group='bar', tusers=(), tgroups=(),
12 12 cuser='foo', cgroup='bar', debug=False, silent=False):
13 13 # user, group => owners of the file
14 14 # tusers, tgroups => trusted users/groups
15 15 # cuser, cgroup => user/group of the current process
16 16
17 17 # write a global hgrc with the list of trusted users/groups and
18 18 # some setting so that we can be sure it was read
19 19 f = open(hgrc, 'w')
20 20 f.write('[paths]\n')
21 21 f.write('global = /some/path\n\n')
22 22
23 23 if tusers or tgroups:
24 24 f.write('[trusted]\n')
25 25 if tusers:
26 26 f.write('users = %s\n' % ', '.join(tusers))
27 27 if tgroups:
28 28 f.write('groups = %s\n' % ', '.join(tgroups))
29 29 f.close()
30 30
31 31 # override the functions that give names to uids and gids
32 32 def username(uid=None):
33 33 if uid is None:
34 34 return cuser
35 35 return user
36 36 util.username = username
37 37
38 38 def groupname(gid=None):
39 39 if gid is None:
40 40 return 'bar'
41 41 return group
42 42 util.groupname = groupname
43 43
44 def isowner(fp, st=None):
45 return user == cuser
46 util.isowner = isowner
47
44 48 # try to read everything
45 49 #print '# File belongs to user %s, group %s' % (user, group)
46 50 #print '# trusted users = %s; trusted groups = %s' % (tusers, tgroups)
47 51 kind = ('different', 'same')
48 52 who = ('', 'user', 'group', 'user and the group')
49 53 trusted = who[(user in tusers) + 2*(group in tgroups)]
50 54 if trusted:
51 55 trusted = ', but we trust the ' + trusted
52 56 print '# %s user, %s group%s' % (kind[user == cuser], kind[group == cgroup],
53 57 trusted)
54 58
55 59 parentui = ui.ui()
56 60 parentui.updateopts(debug=debug)
57 61 u = ui.ui(parentui=parentui)
58 62 u.readconfig('.hg/hgrc')
59 63 if silent:
60 64 return u
61 65 print 'trusted'
62 66 for name, path in u.configitems('paths'):
63 67 print ' ', name, '=', path
64 68 print 'untrusted'
65 69 for name, path in u.configitems('paths', untrusted=True):
66 70 print '.',
67 71 u.config('paths', name) # warning with debug=True
68 72 print '.',
69 73 u.config('paths', name, untrusted=True) # no warnings
70 74 print name, '=', path
71 75 print
72 76
73 77 return u
74 78
75 79 os.mkdir('repo')
76 80 os.chdir('repo')
77 81 os.mkdir('.hg')
78 82 f = open('.hg/hgrc', 'w')
79 83 f.write('[paths]\n')
80 84 f.write('local = /another/path\n\n')
81 85 f.write('interpolated = %(global)s%(local)s\n\n')
82 86 f.close()
83 87
84 88 #print '# Everything is run by user foo, group bar\n'
85 89
86 90 # same user, same group
87 91 testui()
88 92 # same user, different group
89 93 testui(group='def')
90 94 # different user, same group
91 95 testui(user='abc')
92 96 # ... but we trust the group
93 97 testui(user='abc', tgroups=['bar'])
94 98 # different user, different group
95 99 testui(user='abc', group='def')
96 100 # ... but we trust the user
97 101 testui(user='abc', group='def', tusers=['abc'])
98 102 # ... but we trust the group
99 103 testui(user='abc', group='def', tgroups=['def'])
100 104 # ... but we trust the user and the group
101 105 testui(user='abc', group='def', tusers=['abc'], tgroups=['def'])
102 106 # ... but we trust all users
103 107 print '# we trust all users'
104 108 testui(user='abc', group='def', tusers=['*'])
105 109 # ... but we trust all groups
106 110 print '# we trust all groups'
107 111 testui(user='abc', group='def', tgroups=['*'])
108 112 # ... but we trust the whole universe
109 113 print '# we trust all users and groups'
110 114 testui(user='abc', group='def', tusers=['*'], tgroups=['*'])
111 115 # ... check that users and groups are in different namespaces
112 116 print "# we don't get confused by users and groups with the same name"
113 117 testui(user='abc', group='def', tusers=['def'], tgroups=['abc'])
114 118 # ... lists of user names work
115 119 print "# list of user names"
116 120 testui(user='abc', group='def', tusers=['foo', 'xyz', 'abc', 'bleh'],
117 121 tgroups=['bar', 'baz', 'qux'])
118 122 # ... lists of group names work
119 123 print "# list of group names"
120 124 testui(user='abc', group='def', tusers=['foo', 'xyz', 'bleh'],
121 125 tgroups=['bar', 'def', 'baz', 'qux'])
122 126
123 127 print "# Can't figure out the name of the user running this process"
124 128 testui(user='abc', group='def', cuser=None)
125 129
126 130 print "# prints debug warnings"
127 131 u = testui(user='abc', group='def', cuser='foo', debug=True)
128 132
129 133 print "# ui.readsections"
130 134 filename = 'foobar'
131 135 f = open(filename, 'w')
132 136 f.write('[foobar]\n')
133 137 f.write('baz = quux\n')
134 138 f.close()
135 139 u.readsections(filename, 'foobar')
136 140 print u.config('foobar', 'baz')
137 141
138 142 print
139 143 print "# read trusted, untrusted, new ui, trusted"
140 144 u = ui.ui()
141 145 u.updateopts(debug=True)
142 146 u.readconfig(filename)
143 147 u2 = ui.ui(parentui=u)
144 148 def username(uid=None):
145 149 return 'foo'
146 150 util.username = username
147 151 u2.readconfig('.hg/hgrc')
148 152 print 'trusted:'
149 153 print u2.config('foobar', 'baz')
150 154 print u2.config('paths', 'interpolated')
151 155 print 'untrusted:'
152 156 print u2.config('foobar', 'baz', untrusted=True)
153 157 print u2.config('paths', 'interpolated', untrusted=True)
154 158
155 159 print
156 160 print "# error handling"
157 161
158 162 def assertraises(f, exc=util.Abort):
159 163 try:
160 164 f()
161 165 except exc, inst:
162 166 print 'raised', inst.__class__.__name__
163 167 else:
164 168 print 'no exception?!'
165 169
166 170 print "# file doesn't exist"
167 171 os.unlink('.hg/hgrc')
168 172 assert not os.path.exists('.hg/hgrc')
169 173 testui(debug=True, silent=True)
170 174 testui(user='abc', group='def', debug=True, silent=True)
171 175
172 176 print
173 177 print "# parse error"
174 178 f = open('.hg/hgrc', 'w')
175 179 f.write('foo = bar')
176 180 f.close()
177 181 testui(user='abc', group='def', silent=True)
178 182 assertraises(lambda: testui(debug=True, silent=True))
179 183
180 184 print
181 185 print "# interpolation error"
182 186 f = open('.hg/hgrc', 'w')
183 187 f.write('[foo]\n')
184 188 f.write('bar = %(')
185 189 f.close()
186 190 u = testui(debug=True, silent=True)
187 191 print '# regular config:'
188 192 print ' trusted',
189 193 assertraises(lambda: u.config('foo', 'bar'))
190 194 print 'untrusted',
191 195 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
192 196
193 197 u = testui(user='abc', group='def', debug=True, silent=True)
194 198 print ' trusted ',
195 199 print u.config('foo', 'bar')
196 200 print 'untrusted',
197 201 assertraises(lambda: u.config('foo', 'bar', untrusted=True))
198 202
199 203 print '# configitems:'
200 204 print ' trusted ',
201 205 print u.configitems('foo')
202 206 print 'untrusted',
203 207 assertraises(lambda: u.configitems('foo', untrusted=True))
204 208
General Comments 0
You need to be logged in to leave comments. Login now