##// END OF EJS Templates
refactor header handling for changelog formatting
Matt Mackall -
r3644:b7547efe default
parent child Browse files
Show More
@@ -1,445 +1,441 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import gettext as _
9 9 from demandload import *
10 10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 11 demandload(globals(), "ConfigParser traceback util")
12 12
13 13 def dupconfig(orig):
14 14 new = util.configparser(orig.defaults())
15 15 updateconfig(orig, new)
16 16 return new
17 17
18 18 def updateconfig(source, dest, sections=None):
19 19 if not sections:
20 20 sections = source.sections()
21 21 for section in sections:
22 22 if not dest.has_section(section):
23 23 dest.add_section(section)
24 24 for name, value in source.items(section, raw=True):
25 25 dest.set(section, name, value)
26 26
27 27 class ui(object):
28 28 def __init__(self, verbose=False, debug=False, quiet=False,
29 29 interactive=True, traceback=False, report_untrusted=True,
30 30 parentui=None):
31 31 self.overlay = None
32 32 self.header = []
33 33 self.prev_header = []
34 34 if parentui is None:
35 35 # this is the parent of all ui children
36 36 self.parentui = None
37 37 self.readhooks = []
38 38 self.quiet = quiet
39 39 self.verbose = verbose
40 40 self.debugflag = debug
41 41 self.interactive = interactive
42 42 self.traceback = traceback
43 43 self.report_untrusted = report_untrusted
44 44 self.trusted_users = {}
45 45 self.trusted_groups = {}
46 46 # if ucdata is not None, its keys must be a superset of cdata's
47 47 self.cdata = util.configparser()
48 48 self.ucdata = None
49 49 self.readconfig(util.rcpath())
50 50 self.updateopts(verbose, debug, quiet, interactive)
51 51 else:
52 52 # parentui may point to an ui object which is already a child
53 53 self.parentui = parentui.parentui or parentui
54 54 self.readhooks = self.parentui.readhooks[:]
55 55 self.trusted_users = parentui.trusted_users.copy()
56 56 self.trusted_groups = parentui.trusted_groups.copy()
57 57 self.cdata = dupconfig(self.parentui.cdata)
58 58 if self.parentui.ucdata:
59 59 self.ucdata = dupconfig(self.parentui.ucdata)
60 60 if self.parentui.overlay:
61 61 self.overlay = dupconfig(self.parentui.overlay)
62 62
63 63 def __getattr__(self, key):
64 64 return getattr(self.parentui, key)
65 65
66 66 def updateopts(self, verbose=False, debug=False, quiet=False,
67 67 interactive=True, traceback=False, config=[]):
68 68 for section, name, value in config:
69 69 self.setconfig(section, name, value)
70 70
71 71 if quiet or verbose or debug:
72 72 self.setconfig('ui', 'quiet', str(bool(quiet)))
73 73 self.setconfig('ui', 'verbose', str(bool(verbose)))
74 74 self.setconfig('ui', 'debug', str(bool(debug)))
75 75
76 76 self.verbosity_constraints()
77 77
78 78 if not interactive:
79 79 self.setconfig('ui', 'interactive', 'False')
80 80 self.interactive = False
81 81
82 82 self.traceback = self.traceback or traceback
83 83
84 84 def verbosity_constraints(self):
85 85 self.quiet = self.configbool('ui', 'quiet')
86 86 self.verbose = self.configbool('ui', 'verbose')
87 87 self.debugflag = self.configbool('ui', 'debug')
88 88
89 89 if self.debugflag:
90 90 self.verbose = True
91 91 self.quiet = False
92 92 elif self.verbose and self.quiet:
93 93 self.quiet = self.verbose = False
94 94
95 95 def _is_trusted(self, fp, f, warn=True):
96 96 tusers = self.trusted_users
97 97 tgroups = self.trusted_groups
98 98 if (tusers or tgroups) and '*' not in tusers and '*' not in tgroups:
99 99 st = util.fstat(fp)
100 100 user = util.username(st.st_uid)
101 101 group = util.groupname(st.st_gid)
102 102 if user not in tusers and group not in tgroups:
103 103 if warn and self.report_untrusted:
104 104 self.warn(_('Not trusting file %s from untrusted '
105 105 'user %s, group %s\n') % (f, user, group))
106 106 return False
107 107 return True
108 108
109 109 def readconfig(self, fn, root=None):
110 110 if isinstance(fn, basestring):
111 111 fn = [fn]
112 112 for f in fn:
113 113 try:
114 114 fp = open(f)
115 115 except IOError:
116 116 continue
117 117 cdata = self.cdata
118 118 trusted = self._is_trusted(fp, f)
119 119 if not trusted:
120 120 if self.ucdata is None:
121 121 self.ucdata = dupconfig(self.cdata)
122 122 cdata = self.ucdata
123 123 elif self.ucdata is not None:
124 124 # use a separate configparser, so that we don't accidentally
125 125 # override ucdata settings later on.
126 126 cdata = util.configparser()
127 127
128 128 try:
129 129 cdata.readfp(fp, f)
130 130 except ConfigParser.ParsingError, inst:
131 131 msg = _("Failed to parse %s\n%s") % (f, inst)
132 132 if trusted:
133 133 raise util.Abort(msg)
134 134 self.warn(_("Ignored: %s\n") % msg)
135 135
136 136 if trusted:
137 137 if cdata != self.cdata:
138 138 updateconfig(cdata, self.cdata)
139 139 if self.ucdata is not None:
140 140 updateconfig(cdata, self.ucdata)
141 141 # override data from config files with data set with ui.setconfig
142 142 if self.overlay:
143 143 updateconfig(self.overlay, self.cdata)
144 144 if root is None:
145 145 root = os.path.expanduser('~')
146 146 self.fixconfig(root=root)
147 147 for hook in self.readhooks:
148 148 hook(self)
149 149
150 150 def addreadhook(self, hook):
151 151 self.readhooks.append(hook)
152 152
153 153 def readsections(self, filename, *sections):
154 154 """Read filename and add only the specified sections to the config data
155 155
156 156 The settings are added to the trusted config data.
157 157 """
158 158 if not sections:
159 159 return
160 160
161 161 cdata = util.configparser()
162 162 try:
163 163 cdata.read(filename)
164 164 except ConfigParser.ParsingError, inst:
165 165 raise util.Abort(_("failed to parse %s\n%s") % (filename,
166 166 inst))
167 167
168 168 for section in sections:
169 169 if not cdata.has_section(section):
170 170 cdata.add_section(section)
171 171
172 172 updateconfig(cdata, self.cdata, sections)
173 173 if self.ucdata:
174 174 updateconfig(cdata, self.ucdata, sections)
175 175
176 176 def fixconfig(self, section=None, name=None, value=None, root=None):
177 177 # translate paths relative to root (or home) into absolute paths
178 178 if section is None or section == 'paths':
179 179 if root is None:
180 180 root = os.getcwd()
181 181 items = section and [(name, value)] or []
182 182 for cdata in self.cdata, self.ucdata, self.overlay:
183 183 if not cdata: continue
184 184 if not items and cdata.has_section('paths'):
185 185 pathsitems = cdata.items('paths')
186 186 else:
187 187 pathsitems = items
188 188 for n, path in pathsitems:
189 189 if path and "://" not in path and not os.path.isabs(path):
190 190 cdata.set("paths", n, os.path.join(root, path))
191 191
192 192 # update quiet/verbose/debug and interactive status
193 193 if section is None or section == 'ui':
194 194 if name is None or name in ('quiet', 'verbose', 'debug'):
195 195 self.verbosity_constraints()
196 196
197 197 if name is None or name == 'interactive':
198 198 self.interactive = self.configbool("ui", "interactive", True)
199 199
200 200 # update trust information
201 201 if section is None or section == 'trusted':
202 202 user = util.username()
203 203 if user is not None:
204 204 self.trusted_users[user] = 1
205 205 for user in self.configlist('trusted', 'users'):
206 206 self.trusted_users[user] = 1
207 207 for group in self.configlist('trusted', 'groups'):
208 208 self.trusted_groups[group] = 1
209 209
210 210 def setconfig(self, section, name, value):
211 211 if not self.overlay:
212 212 self.overlay = util.configparser()
213 213 for cdata in (self.overlay, self.cdata, self.ucdata):
214 214 if not cdata: continue
215 215 if not cdata.has_section(section):
216 216 cdata.add_section(section)
217 217 cdata.set(section, name, value)
218 218 self.fixconfig(section, name, value)
219 219
220 220 def _get_cdata(self, untrusted):
221 221 if untrusted and self.ucdata:
222 222 return self.ucdata
223 223 return self.cdata
224 224
225 225 def _config(self, section, name, default, funcname, untrusted, abort):
226 226 cdata = self._get_cdata(untrusted)
227 227 if cdata.has_option(section, name):
228 228 try:
229 229 func = getattr(cdata, funcname)
230 230 return func(section, name)
231 231 except ConfigParser.InterpolationError, inst:
232 232 msg = _("Error in configuration section [%s] "
233 233 "parameter '%s':\n%s") % (section, name, inst)
234 234 if abort:
235 235 raise util.Abort(msg)
236 236 self.warn(_("Ignored: %s\n") % msg)
237 237 return default
238 238
239 239 def _configcommon(self, section, name, default, funcname, untrusted):
240 240 value = self._config(section, name, default, funcname,
241 241 untrusted, abort=True)
242 242 if self.debugflag and not untrusted and self.ucdata:
243 243 uvalue = self._config(section, name, None, funcname,
244 244 untrusted=True, abort=False)
245 245 if uvalue is not None and uvalue != value:
246 246 self.warn(_("Ignoring untrusted configuration option "
247 247 "%s.%s = %s\n") % (section, name, uvalue))
248 248 return value
249 249
250 250 def config(self, section, name, default=None, untrusted=False):
251 251 return self._configcommon(section, name, default, 'get', untrusted)
252 252
253 253 def configbool(self, section, name, default=False, untrusted=False):
254 254 return self._configcommon(section, name, default, 'getboolean',
255 255 untrusted)
256 256
257 257 def configlist(self, section, name, default=None, untrusted=False):
258 258 """Return a list of comma/space separated strings"""
259 259 result = self.config(section, name, untrusted=untrusted)
260 260 if result is None:
261 261 result = default or []
262 262 if isinstance(result, basestring):
263 263 result = result.replace(",", " ").split()
264 264 return result
265 265
266 266 def has_config(self, section, untrusted=False):
267 267 '''tell whether section exists in config.'''
268 268 cdata = self._get_cdata(untrusted)
269 269 return cdata.has_section(section)
270 270
271 271 def _configitems(self, section, untrusted, abort):
272 272 items = {}
273 273 cdata = self._get_cdata(untrusted)
274 274 if cdata.has_section(section):
275 275 try:
276 276 items.update(dict(cdata.items(section)))
277 277 except ConfigParser.InterpolationError, inst:
278 278 msg = _("Error in configuration section [%s]:\n"
279 279 "%s") % (section, inst)
280 280 if abort:
281 281 raise util.Abort(msg)
282 282 self.warn(_("Ignored: %s\n") % msg)
283 283 return items
284 284
285 285 def configitems(self, section, untrusted=False):
286 286 items = self._configitems(section, untrusted=untrusted, abort=True)
287 287 if self.debugflag and not untrusted and self.ucdata:
288 288 uitems = self._configitems(section, untrusted=True, abort=False)
289 289 keys = uitems.keys()
290 290 keys.sort()
291 291 for k in keys:
292 292 if uitems[k] != items.get(k):
293 293 self.warn(_("Ignoring untrusted configuration option "
294 294 "%s.%s = %s\n") % (section, k, uitems[k]))
295 295 x = items.items()
296 296 x.sort()
297 297 return x
298 298
299 299 def walkconfig(self, untrusted=False):
300 300 cdata = self._get_cdata(untrusted)
301 301 sections = cdata.sections()
302 302 sections.sort()
303 303 for section in sections:
304 304 for name, value in self.configitems(section, untrusted):
305 305 yield section, name, value.replace('\n', '\\n')
306 306
307 307 def extensions(self):
308 308 result = self.configitems("extensions")
309 309 for i, (key, value) in enumerate(result):
310 310 if value:
311 311 result[i] = (key, os.path.expanduser(value))
312 312 return result
313 313
314 314 def hgignorefiles(self):
315 315 result = []
316 316 for key, value in self.configitems("ui"):
317 317 if key == 'ignore' or key.startswith('ignore.'):
318 318 result.append(os.path.expanduser(value))
319 319 return result
320 320
321 321 def configrevlog(self):
322 322 result = {}
323 323 for key, value in self.configitems("revlog"):
324 324 result[key.lower()] = value
325 325 return result
326 326
327 327 def username(self):
328 328 """Return default username to be used in commits.
329 329
330 330 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
331 331 and stop searching if one of these is set.
332 332 Abort if no username is found, to force specifying the commit user
333 333 with line option or repo hgrc.
334 334 """
335 335 user = os.environ.get("HGUSER")
336 336 if user is None:
337 337 user = self.config("ui", "username")
338 338 if user is None:
339 339 user = os.environ.get("EMAIL")
340 340 if not user:
341 341 self.status(_("Please choose a commit username to be recorded "
342 342 "in the changelog via\ncommand line option "
343 343 '(-u "First Last <email@example.com>"), in the\n'
344 344 "configuration files (hgrc), or by setting the "
345 345 "EMAIL environment variable.\n\n"))
346 346 raise util.Abort(_("No commit username specified!"))
347 347 return user
348 348
349 349 def shortuser(self, user):
350 350 """Return a short representation of a user name or email address."""
351 351 if not self.verbose: user = util.shortuser(user)
352 352 return user
353 353
354 354 def expandpath(self, loc, default=None):
355 355 """Return repository location relative to cwd or from [paths]"""
356 356 if "://" in loc or os.path.isdir(loc):
357 357 return loc
358 358
359 359 path = self.config("paths", loc)
360 360 if not path and default is not None:
361 361 path = self.config("paths", default)
362 362 return path or loc
363 363
364 364 def write(self, *args):
365 365 if self.header:
366 366 if self.header != self.prev_header:
367 367 self.prev_header = self.header
368 368 self.write(*self.header)
369 369 self.header = []
370 370 for a in args:
371 371 sys.stdout.write(str(a))
372 372
373 def write_header(self, *args):
374 for a in args:
375 self.header.append(str(a))
376
377 373 def write_err(self, *args):
378 374 try:
379 375 if not sys.stdout.closed: sys.stdout.flush()
380 376 for a in args:
381 377 sys.stderr.write(str(a))
382 378 except IOError, inst:
383 379 if inst.errno != errno.EPIPE:
384 380 raise
385 381
386 382 def flush(self):
387 383 try: sys.stdout.flush()
388 384 except: pass
389 385 try: sys.stderr.flush()
390 386 except: pass
391 387
392 388 def readline(self):
393 389 return sys.stdin.readline()[:-1]
394 390 def prompt(self, msg, pat=None, default="y"):
395 391 if not self.interactive: return default
396 392 while 1:
397 393 self.write(msg, " ")
398 394 r = self.readline()
399 395 if not pat or re.match(pat, r):
400 396 return r
401 397 else:
402 398 self.write(_("unrecognized response\n"))
403 399 def getpass(self, prompt=None, default=None):
404 400 if not self.interactive: return default
405 401 return getpass.getpass(prompt or _('password: '))
406 402 def status(self, *msg):
407 403 if not self.quiet: self.write(*msg)
408 404 def warn(self, *msg):
409 405 self.write_err(*msg)
410 406 def note(self, *msg):
411 407 if self.verbose: self.write(*msg)
412 408 def debug(self, *msg):
413 409 if self.debugflag: self.write(*msg)
414 410 def edit(self, text, user):
415 411 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
416 412 text=True)
417 413 try:
418 414 f = os.fdopen(fd, "w")
419 415 f.write(text)
420 416 f.close()
421 417
422 418 editor = (os.environ.get("HGEDITOR") or
423 419 self.config("ui", "editor") or
424 420 os.environ.get("EDITOR", "vi"))
425 421
426 422 util.system("%s \"%s\"" % (editor, name),
427 423 environ={'HGUSER': user},
428 424 onerr=util.Abort, errprefix=_("edit failed"))
429 425
430 426 f = open(name)
431 427 t = f.read()
432 428 f.close()
433 429 t = re.sub("(?m)^HG:.*\n", "", t)
434 430 finally:
435 431 os.unlink(name)
436 432
437 433 return t
438 434
439 435 def print_exc(self):
440 436 '''print exception traceback if traceback printing enabled.
441 437 only to call in exception handler. returns true if traceback
442 438 printed.'''
443 439 if self.traceback:
444 440 traceback.print_exc()
445 441 return self.traceback
General Comments 0
You need to be logged in to leave comments. Login now