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