##// END OF EJS Templates
diff: improve ui.write performance when not coloring on Windows...
Joerg Sonnenberger -
r35979:0ff41ced default
parent child Browse files
Show More
@@ -1,931 +1,944 b''
1 # logcmdutil.py - utility for log-like commands
1 # logcmdutil.py - utility for log-like commands
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import itertools
10 import itertools
11 import os
11 import os
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 )
17 )
18
18
19 from . import (
19 from . import (
20 dagop,
20 dagop,
21 encoding,
21 encoding,
22 error,
22 error,
23 formatter,
23 formatter,
24 graphmod,
24 graphmod,
25 match as matchmod,
25 match as matchmod,
26 mdiff,
26 mdiff,
27 patch,
27 patch,
28 pathutil,
28 pathutil,
29 pycompat,
29 pycompat,
30 revset,
30 revset,
31 revsetlang,
31 revsetlang,
32 scmutil,
32 scmutil,
33 smartset,
33 smartset,
34 templatekw,
34 templatekw,
35 templater,
35 templater,
36 util,
36 util,
37 )
37 )
38
38
39 def getlimit(opts):
39 def getlimit(opts):
40 """get the log limit according to option -l/--limit"""
40 """get the log limit according to option -l/--limit"""
41 limit = opts.get('limit')
41 limit = opts.get('limit')
42 if limit:
42 if limit:
43 try:
43 try:
44 limit = int(limit)
44 limit = int(limit)
45 except ValueError:
45 except ValueError:
46 raise error.Abort(_('limit must be a positive integer'))
46 raise error.Abort(_('limit must be a positive integer'))
47 if limit <= 0:
47 if limit <= 0:
48 raise error.Abort(_('limit must be positive'))
48 raise error.Abort(_('limit must be positive'))
49 else:
49 else:
50 limit = None
50 limit = None
51 return limit
51 return limit
52
52
53 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
53 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
54 changes=None, stat=False, fp=None, prefix='',
54 changes=None, stat=False, fp=None, prefix='',
55 root='', listsubrepos=False, hunksfilterfn=None):
55 root='', listsubrepos=False, hunksfilterfn=None):
56 '''show diff or diffstat.'''
56 '''show diff or diffstat.'''
57 if fp is None:
57 if fp is None:
58 write = ui.write
58 write = ui.write
59 else:
59 else:
60 def write(s, **kw):
60 def write(s, **kw):
61 fp.write(s)
61 fp.write(s)
62
62
63 if root:
63 if root:
64 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
64 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
65 else:
65 else:
66 relroot = ''
66 relroot = ''
67 if relroot != '':
67 if relroot != '':
68 # XXX relative roots currently don't work if the root is within a
68 # XXX relative roots currently don't work if the root is within a
69 # subrepo
69 # subrepo
70 uirelroot = match.uipath(relroot)
70 uirelroot = match.uipath(relroot)
71 relroot += '/'
71 relroot += '/'
72 for matchroot in match.files():
72 for matchroot in match.files():
73 if not matchroot.startswith(relroot):
73 if not matchroot.startswith(relroot):
74 ui.warn(_('warning: %s not inside relative root %s\n') % (
74 ui.warn(_('warning: %s not inside relative root %s\n') % (
75 match.uipath(matchroot), uirelroot))
75 match.uipath(matchroot), uirelroot))
76
76
77 if stat:
77 if stat:
78 diffopts = diffopts.copy(context=0, noprefix=False)
78 diffopts = diffopts.copy(context=0, noprefix=False)
79 width = 80
79 width = 80
80 if not ui.plain():
80 if not ui.plain():
81 width = ui.termwidth()
81 width = ui.termwidth()
82 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
82
83 prefix=prefix, relroot=relroot,
83 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
84 hunksfilterfn=hunksfilterfn)
84 prefix=prefix, relroot=relroot,
85 for chunk, label in patch.diffstatui(util.iterlines(chunks),
85 hunksfilterfn=hunksfilterfn)
86 width=width):
86
87 write(chunk, label=label)
87 if fp is not None or ui.canwritewithoutlabels():
88 if stat:
89 chunks = patch.diffstat(util.iterlines(chunks), width=width)
90 for chunk in util.filechunkiter(util.chunkbuffer(chunks)):
91 write(chunk)
88 else:
92 else:
89 for chunk, label in patch.diffui(repo, node1, node2, match,
93 if stat:
90 changes, opts=diffopts, prefix=prefix,
94 chunks = patch.diffstatui(util.iterlines(chunks), width=width)
91 relroot=relroot,
95 else:
92 hunksfilterfn=hunksfilterfn):
96 chunks = patch.difflabel(lambda chunks, **kwargs: chunks, chunks,
93 write(chunk, label=label)
97 opts=diffopts)
98 if ui.canbatchlabeledwrites():
99 def gen():
100 for chunk, label in chunks:
101 yield ui.label(chunk, label=label)
102 for chunk in util.filechunkiter(util.chunkbuffer(gen())):
103 write(chunk)
104 else:
105 for chunk, label in chunks:
106 write(chunk, label=label)
94
107
95 if listsubrepos:
108 if listsubrepos:
96 ctx1 = repo[node1]
109 ctx1 = repo[node1]
97 ctx2 = repo[node2]
110 ctx2 = repo[node2]
98 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
111 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
99 tempnode2 = node2
112 tempnode2 = node2
100 try:
113 try:
101 if node2 is not None:
114 if node2 is not None:
102 tempnode2 = ctx2.substate[subpath][1]
115 tempnode2 = ctx2.substate[subpath][1]
103 except KeyError:
116 except KeyError:
104 # A subrepo that existed in node1 was deleted between node1 and
117 # A subrepo that existed in node1 was deleted between node1 and
105 # node2 (inclusive). Thus, ctx2's substate won't contain that
118 # node2 (inclusive). Thus, ctx2's substate won't contain that
106 # subpath. The best we can do is to ignore it.
119 # subpath. The best we can do is to ignore it.
107 tempnode2 = None
120 tempnode2 = None
108 submatch = matchmod.subdirmatcher(subpath, match)
121 submatch = matchmod.subdirmatcher(subpath, match)
109 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
122 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
110 stat=stat, fp=fp, prefix=prefix)
123 stat=stat, fp=fp, prefix=prefix)
111
124
112 def changesetlabels(ctx):
125 def changesetlabels(ctx):
113 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
126 labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
114 if ctx.obsolete():
127 if ctx.obsolete():
115 labels.append('changeset.obsolete')
128 labels.append('changeset.obsolete')
116 if ctx.isunstable():
129 if ctx.isunstable():
117 labels.append('changeset.unstable')
130 labels.append('changeset.unstable')
118 for instability in ctx.instabilities():
131 for instability in ctx.instabilities():
119 labels.append('instability.%s' % instability)
132 labels.append('instability.%s' % instability)
120 return ' '.join(labels)
133 return ' '.join(labels)
121
134
122 class changesetprinter(object):
135 class changesetprinter(object):
123 '''show changeset information when templating not requested.'''
136 '''show changeset information when templating not requested.'''
124
137
125 def __init__(self, ui, repo, matchfn=None, diffopts=None, buffered=False):
138 def __init__(self, ui, repo, matchfn=None, diffopts=None, buffered=False):
126 self.ui = ui
139 self.ui = ui
127 self.repo = repo
140 self.repo = repo
128 self.buffered = buffered
141 self.buffered = buffered
129 self.matchfn = matchfn
142 self.matchfn = matchfn
130 self.diffopts = diffopts or {}
143 self.diffopts = diffopts or {}
131 self.header = {}
144 self.header = {}
132 self.hunk = {}
145 self.hunk = {}
133 self.lastheader = None
146 self.lastheader = None
134 self.footer = None
147 self.footer = None
135 self._columns = templatekw.getlogcolumns()
148 self._columns = templatekw.getlogcolumns()
136
149
137 def flush(self, ctx):
150 def flush(self, ctx):
138 rev = ctx.rev()
151 rev = ctx.rev()
139 if rev in self.header:
152 if rev in self.header:
140 h = self.header[rev]
153 h = self.header[rev]
141 if h != self.lastheader:
154 if h != self.lastheader:
142 self.lastheader = h
155 self.lastheader = h
143 self.ui.write(h)
156 self.ui.write(h)
144 del self.header[rev]
157 del self.header[rev]
145 if rev in self.hunk:
158 if rev in self.hunk:
146 self.ui.write(self.hunk[rev])
159 self.ui.write(self.hunk[rev])
147 del self.hunk[rev]
160 del self.hunk[rev]
148
161
149 def close(self):
162 def close(self):
150 if self.footer:
163 if self.footer:
151 self.ui.write(self.footer)
164 self.ui.write(self.footer)
152
165
153 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
166 def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
154 **props):
167 **props):
155 props = pycompat.byteskwargs(props)
168 props = pycompat.byteskwargs(props)
156 if self.buffered:
169 if self.buffered:
157 self.ui.pushbuffer(labeled=True)
170 self.ui.pushbuffer(labeled=True)
158 self._show(ctx, copies, matchfn, hunksfilterfn, props)
171 self._show(ctx, copies, matchfn, hunksfilterfn, props)
159 self.hunk[ctx.rev()] = self.ui.popbuffer()
172 self.hunk[ctx.rev()] = self.ui.popbuffer()
160 else:
173 else:
161 self._show(ctx, copies, matchfn, hunksfilterfn, props)
174 self._show(ctx, copies, matchfn, hunksfilterfn, props)
162
175
163 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
176 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
164 '''show a single changeset or file revision'''
177 '''show a single changeset or file revision'''
165 changenode = ctx.node()
178 changenode = ctx.node()
166 rev = ctx.rev()
179 rev = ctx.rev()
167
180
168 if self.ui.quiet:
181 if self.ui.quiet:
169 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
182 self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
170 label='log.node')
183 label='log.node')
171 return
184 return
172
185
173 columns = self._columns
186 columns = self._columns
174 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
187 self.ui.write(columns['changeset'] % scmutil.formatchangeid(ctx),
175 label=changesetlabels(ctx))
188 label=changesetlabels(ctx))
176
189
177 # branches are shown first before any other names due to backwards
190 # branches are shown first before any other names due to backwards
178 # compatibility
191 # compatibility
179 branch = ctx.branch()
192 branch = ctx.branch()
180 # don't show the default branch name
193 # don't show the default branch name
181 if branch != 'default':
194 if branch != 'default':
182 self.ui.write(columns['branch'] % branch, label='log.branch')
195 self.ui.write(columns['branch'] % branch, label='log.branch')
183
196
184 for nsname, ns in self.repo.names.iteritems():
197 for nsname, ns in self.repo.names.iteritems():
185 # branches has special logic already handled above, so here we just
198 # branches has special logic already handled above, so here we just
186 # skip it
199 # skip it
187 if nsname == 'branches':
200 if nsname == 'branches':
188 continue
201 continue
189 # we will use the templatename as the color name since those two
202 # we will use the templatename as the color name since those two
190 # should be the same
203 # should be the same
191 for name in ns.names(self.repo, changenode):
204 for name in ns.names(self.repo, changenode):
192 self.ui.write(ns.logfmt % name,
205 self.ui.write(ns.logfmt % name,
193 label='log.%s' % ns.colorname)
206 label='log.%s' % ns.colorname)
194 if self.ui.debugflag:
207 if self.ui.debugflag:
195 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
208 self.ui.write(columns['phase'] % ctx.phasestr(), label='log.phase')
196 for pctx in scmutil.meaningfulparents(self.repo, ctx):
209 for pctx in scmutil.meaningfulparents(self.repo, ctx):
197 label = 'log.parent changeset.%s' % pctx.phasestr()
210 label = 'log.parent changeset.%s' % pctx.phasestr()
198 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
211 self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
199 label=label)
212 label=label)
200
213
201 if self.ui.debugflag and rev is not None:
214 if self.ui.debugflag and rev is not None:
202 mnode = ctx.manifestnode()
215 mnode = ctx.manifestnode()
203 mrev = self.repo.manifestlog._revlog.rev(mnode)
216 mrev = self.repo.manifestlog._revlog.rev(mnode)
204 self.ui.write(columns['manifest']
217 self.ui.write(columns['manifest']
205 % scmutil.formatrevnode(self.ui, mrev, mnode),
218 % scmutil.formatrevnode(self.ui, mrev, mnode),
206 label='ui.debug log.manifest')
219 label='ui.debug log.manifest')
207 self.ui.write(columns['user'] % ctx.user(), label='log.user')
220 self.ui.write(columns['user'] % ctx.user(), label='log.user')
208 self.ui.write(columns['date'] % util.datestr(ctx.date()),
221 self.ui.write(columns['date'] % util.datestr(ctx.date()),
209 label='log.date')
222 label='log.date')
210
223
211 if ctx.isunstable():
224 if ctx.isunstable():
212 instabilities = ctx.instabilities()
225 instabilities = ctx.instabilities()
213 self.ui.write(columns['instability'] % ', '.join(instabilities),
226 self.ui.write(columns['instability'] % ', '.join(instabilities),
214 label='log.instability')
227 label='log.instability')
215
228
216 elif ctx.obsolete():
229 elif ctx.obsolete():
217 self._showobsfate(ctx)
230 self._showobsfate(ctx)
218
231
219 self._exthook(ctx)
232 self._exthook(ctx)
220
233
221 if self.ui.debugflag:
234 if self.ui.debugflag:
222 files = ctx.p1().status(ctx)[:3]
235 files = ctx.p1().status(ctx)[:3]
223 for key, value in zip(['files', 'files+', 'files-'], files):
236 for key, value in zip(['files', 'files+', 'files-'], files):
224 if value:
237 if value:
225 self.ui.write(columns[key] % " ".join(value),
238 self.ui.write(columns[key] % " ".join(value),
226 label='ui.debug log.files')
239 label='ui.debug log.files')
227 elif ctx.files() and self.ui.verbose:
240 elif ctx.files() and self.ui.verbose:
228 self.ui.write(columns['files'] % " ".join(ctx.files()),
241 self.ui.write(columns['files'] % " ".join(ctx.files()),
229 label='ui.note log.files')
242 label='ui.note log.files')
230 if copies and self.ui.verbose:
243 if copies and self.ui.verbose:
231 copies = ['%s (%s)' % c for c in copies]
244 copies = ['%s (%s)' % c for c in copies]
232 self.ui.write(columns['copies'] % ' '.join(copies),
245 self.ui.write(columns['copies'] % ' '.join(copies),
233 label='ui.note log.copies')
246 label='ui.note log.copies')
234
247
235 extra = ctx.extra()
248 extra = ctx.extra()
236 if extra and self.ui.debugflag:
249 if extra and self.ui.debugflag:
237 for key, value in sorted(extra.items()):
250 for key, value in sorted(extra.items()):
238 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
251 self.ui.write(columns['extra'] % (key, util.escapestr(value)),
239 label='ui.debug log.extra')
252 label='ui.debug log.extra')
240
253
241 description = ctx.description().strip()
254 description = ctx.description().strip()
242 if description:
255 if description:
243 if self.ui.verbose:
256 if self.ui.verbose:
244 self.ui.write(_("description:\n"),
257 self.ui.write(_("description:\n"),
245 label='ui.note log.description')
258 label='ui.note log.description')
246 self.ui.write(description,
259 self.ui.write(description,
247 label='ui.note log.description')
260 label='ui.note log.description')
248 self.ui.write("\n\n")
261 self.ui.write("\n\n")
249 else:
262 else:
250 self.ui.write(columns['summary'] % description.splitlines()[0],
263 self.ui.write(columns['summary'] % description.splitlines()[0],
251 label='log.summary')
264 label='log.summary')
252 self.ui.write("\n")
265 self.ui.write("\n")
253
266
254 self._showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
267 self._showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
255
268
256 def _showobsfate(self, ctx):
269 def _showobsfate(self, ctx):
257 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
270 obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
258
271
259 if obsfate:
272 if obsfate:
260 for obsfateline in obsfate:
273 for obsfateline in obsfate:
261 self.ui.write(self._columns['obsolete'] % obsfateline,
274 self.ui.write(self._columns['obsolete'] % obsfateline,
262 label='log.obsfate')
275 label='log.obsfate')
263
276
264 def _exthook(self, ctx):
277 def _exthook(self, ctx):
265 '''empty method used by extension as a hook point
278 '''empty method used by extension as a hook point
266 '''
279 '''
267
280
268 def _showpatch(self, ctx, matchfn, hunksfilterfn=None):
281 def _showpatch(self, ctx, matchfn, hunksfilterfn=None):
269 if not matchfn:
282 if not matchfn:
270 matchfn = self.matchfn
283 matchfn = self.matchfn
271 if matchfn:
284 if matchfn:
272 stat = self.diffopts.get('stat')
285 stat = self.diffopts.get('stat')
273 diff = self.diffopts.get('patch')
286 diff = self.diffopts.get('patch')
274 diffopts = patch.diffallopts(self.ui, self.diffopts)
287 diffopts = patch.diffallopts(self.ui, self.diffopts)
275 node = ctx.node()
288 node = ctx.node()
276 prev = ctx.p1().node()
289 prev = ctx.p1().node()
277 if stat:
290 if stat:
278 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
291 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
279 match=matchfn, stat=True,
292 match=matchfn, stat=True,
280 hunksfilterfn=hunksfilterfn)
293 hunksfilterfn=hunksfilterfn)
281 if diff:
294 if diff:
282 if stat:
295 if stat:
283 self.ui.write("\n")
296 self.ui.write("\n")
284 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
297 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
285 match=matchfn, stat=False,
298 match=matchfn, stat=False,
286 hunksfilterfn=hunksfilterfn)
299 hunksfilterfn=hunksfilterfn)
287 if stat or diff:
300 if stat or diff:
288 self.ui.write("\n")
301 self.ui.write("\n")
289
302
290 class jsonchangeset(changesetprinter):
303 class jsonchangeset(changesetprinter):
291 '''format changeset information.'''
304 '''format changeset information.'''
292
305
293 def __init__(self, ui, repo, matchfn=None, diffopts=None, buffered=False):
306 def __init__(self, ui, repo, matchfn=None, diffopts=None, buffered=False):
294 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
307 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
295 self.cache = {}
308 self.cache = {}
296 self._first = True
309 self._first = True
297
310
298 def close(self):
311 def close(self):
299 if not self._first:
312 if not self._first:
300 self.ui.write("\n]\n")
313 self.ui.write("\n]\n")
301 else:
314 else:
302 self.ui.write("[]\n")
315 self.ui.write("[]\n")
303
316
304 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
317 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
305 '''show a single changeset or file revision'''
318 '''show a single changeset or file revision'''
306 rev = ctx.rev()
319 rev = ctx.rev()
307 if rev is None:
320 if rev is None:
308 jrev = jnode = 'null'
321 jrev = jnode = 'null'
309 else:
322 else:
310 jrev = '%d' % rev
323 jrev = '%d' % rev
311 jnode = '"%s"' % hex(ctx.node())
324 jnode = '"%s"' % hex(ctx.node())
312 j = encoding.jsonescape
325 j = encoding.jsonescape
313
326
314 if self._first:
327 if self._first:
315 self.ui.write("[\n {")
328 self.ui.write("[\n {")
316 self._first = False
329 self._first = False
317 else:
330 else:
318 self.ui.write(",\n {")
331 self.ui.write(",\n {")
319
332
320 if self.ui.quiet:
333 if self.ui.quiet:
321 self.ui.write(('\n "rev": %s') % jrev)
334 self.ui.write(('\n "rev": %s') % jrev)
322 self.ui.write((',\n "node": %s') % jnode)
335 self.ui.write((',\n "node": %s') % jnode)
323 self.ui.write('\n }')
336 self.ui.write('\n }')
324 return
337 return
325
338
326 self.ui.write(('\n "rev": %s') % jrev)
339 self.ui.write(('\n "rev": %s') % jrev)
327 self.ui.write((',\n "node": %s') % jnode)
340 self.ui.write((',\n "node": %s') % jnode)
328 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
341 self.ui.write((',\n "branch": "%s"') % j(ctx.branch()))
329 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
342 self.ui.write((',\n "phase": "%s"') % ctx.phasestr())
330 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
343 self.ui.write((',\n "user": "%s"') % j(ctx.user()))
331 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
344 self.ui.write((',\n "date": [%d, %d]') % ctx.date())
332 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
345 self.ui.write((',\n "desc": "%s"') % j(ctx.description()))
333
346
334 self.ui.write((',\n "bookmarks": [%s]') %
347 self.ui.write((',\n "bookmarks": [%s]') %
335 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
348 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
336 self.ui.write((',\n "tags": [%s]') %
349 self.ui.write((',\n "tags": [%s]') %
337 ", ".join('"%s"' % j(t) for t in ctx.tags()))
350 ", ".join('"%s"' % j(t) for t in ctx.tags()))
338 self.ui.write((',\n "parents": [%s]') %
351 self.ui.write((',\n "parents": [%s]') %
339 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
352 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
340
353
341 if self.ui.debugflag:
354 if self.ui.debugflag:
342 if rev is None:
355 if rev is None:
343 jmanifestnode = 'null'
356 jmanifestnode = 'null'
344 else:
357 else:
345 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
358 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
346 self.ui.write((',\n "manifest": %s') % jmanifestnode)
359 self.ui.write((',\n "manifest": %s') % jmanifestnode)
347
360
348 self.ui.write((',\n "extra": {%s}') %
361 self.ui.write((',\n "extra": {%s}') %
349 ", ".join('"%s": "%s"' % (j(k), j(v))
362 ", ".join('"%s": "%s"' % (j(k), j(v))
350 for k, v in ctx.extra().items()))
363 for k, v in ctx.extra().items()))
351
364
352 files = ctx.p1().status(ctx)
365 files = ctx.p1().status(ctx)
353 self.ui.write((',\n "modified": [%s]') %
366 self.ui.write((',\n "modified": [%s]') %
354 ", ".join('"%s"' % j(f) for f in files[0]))
367 ", ".join('"%s"' % j(f) for f in files[0]))
355 self.ui.write((',\n "added": [%s]') %
368 self.ui.write((',\n "added": [%s]') %
356 ", ".join('"%s"' % j(f) for f in files[1]))
369 ", ".join('"%s"' % j(f) for f in files[1]))
357 self.ui.write((',\n "removed": [%s]') %
370 self.ui.write((',\n "removed": [%s]') %
358 ", ".join('"%s"' % j(f) for f in files[2]))
371 ", ".join('"%s"' % j(f) for f in files[2]))
359
372
360 elif self.ui.verbose:
373 elif self.ui.verbose:
361 self.ui.write((',\n "files": [%s]') %
374 self.ui.write((',\n "files": [%s]') %
362 ", ".join('"%s"' % j(f) for f in ctx.files()))
375 ", ".join('"%s"' % j(f) for f in ctx.files()))
363
376
364 if copies:
377 if copies:
365 self.ui.write((',\n "copies": {%s}') %
378 self.ui.write((',\n "copies": {%s}') %
366 ", ".join('"%s": "%s"' % (j(k), j(v))
379 ", ".join('"%s": "%s"' % (j(k), j(v))
367 for k, v in copies))
380 for k, v in copies))
368
381
369 matchfn = self.matchfn
382 matchfn = self.matchfn
370 if matchfn:
383 if matchfn:
371 stat = self.diffopts.get('stat')
384 stat = self.diffopts.get('stat')
372 diff = self.diffopts.get('patch')
385 diff = self.diffopts.get('patch')
373 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
386 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
374 node, prev = ctx.node(), ctx.p1().node()
387 node, prev = ctx.node(), ctx.p1().node()
375 if stat:
388 if stat:
376 self.ui.pushbuffer()
389 self.ui.pushbuffer()
377 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
390 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
378 match=matchfn, stat=True)
391 match=matchfn, stat=True)
379 self.ui.write((',\n "diffstat": "%s"')
392 self.ui.write((',\n "diffstat": "%s"')
380 % j(self.ui.popbuffer()))
393 % j(self.ui.popbuffer()))
381 if diff:
394 if diff:
382 self.ui.pushbuffer()
395 self.ui.pushbuffer()
383 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
396 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
384 match=matchfn, stat=False)
397 match=matchfn, stat=False)
385 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
398 self.ui.write((',\n "diff": "%s"') % j(self.ui.popbuffer()))
386
399
387 self.ui.write("\n }")
400 self.ui.write("\n }")
388
401
389 class changesettemplater(changesetprinter):
402 class changesettemplater(changesetprinter):
390 '''format changeset information.
403 '''format changeset information.
391
404
392 Note: there are a variety of convenience functions to build a
405 Note: there are a variety of convenience functions to build a
393 changesettemplater for common cases. See functions such as:
406 changesettemplater for common cases. See functions such as:
394 maketemplater, changesetdisplayer, buildcommittemplate, or other
407 maketemplater, changesetdisplayer, buildcommittemplate, or other
395 functions that use changesest_templater.
408 functions that use changesest_templater.
396 '''
409 '''
397
410
398 # Arguments before "buffered" used to be positional. Consider not
411 # Arguments before "buffered" used to be positional. Consider not
399 # adding/removing arguments before "buffered" to not break callers.
412 # adding/removing arguments before "buffered" to not break callers.
400 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
413 def __init__(self, ui, repo, tmplspec, matchfn=None, diffopts=None,
401 buffered=False):
414 buffered=False):
402 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
415 changesetprinter.__init__(self, ui, repo, matchfn, diffopts, buffered)
403 tres = formatter.templateresources(ui, repo)
416 tres = formatter.templateresources(ui, repo)
404 self.t = formatter.loadtemplater(ui, tmplspec,
417 self.t = formatter.loadtemplater(ui, tmplspec,
405 defaults=templatekw.keywords,
418 defaults=templatekw.keywords,
406 resources=tres,
419 resources=tres,
407 cache=templatekw.defaulttempl)
420 cache=templatekw.defaulttempl)
408 self._counter = itertools.count()
421 self._counter = itertools.count()
409 self.cache = tres['cache'] # shared with _graphnodeformatter()
422 self.cache = tres['cache'] # shared with _graphnodeformatter()
410
423
411 self._tref = tmplspec.ref
424 self._tref = tmplspec.ref
412 self._parts = {'header': '', 'footer': '',
425 self._parts = {'header': '', 'footer': '',
413 tmplspec.ref: tmplspec.ref,
426 tmplspec.ref: tmplspec.ref,
414 'docheader': '', 'docfooter': '',
427 'docheader': '', 'docfooter': '',
415 'separator': ''}
428 'separator': ''}
416 if tmplspec.mapfile:
429 if tmplspec.mapfile:
417 # find correct templates for current mode, for backward
430 # find correct templates for current mode, for backward
418 # compatibility with 'log -v/-q/--debug' using a mapfile
431 # compatibility with 'log -v/-q/--debug' using a mapfile
419 tmplmodes = [
432 tmplmodes = [
420 (True, ''),
433 (True, ''),
421 (self.ui.verbose, '_verbose'),
434 (self.ui.verbose, '_verbose'),
422 (self.ui.quiet, '_quiet'),
435 (self.ui.quiet, '_quiet'),
423 (self.ui.debugflag, '_debug'),
436 (self.ui.debugflag, '_debug'),
424 ]
437 ]
425 for mode, postfix in tmplmodes:
438 for mode, postfix in tmplmodes:
426 for t in self._parts:
439 for t in self._parts:
427 cur = t + postfix
440 cur = t + postfix
428 if mode and cur in self.t:
441 if mode and cur in self.t:
429 self._parts[t] = cur
442 self._parts[t] = cur
430 else:
443 else:
431 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
444 partnames = [p for p in self._parts.keys() if p != tmplspec.ref]
432 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
445 m = formatter.templatepartsmap(tmplspec, self.t, partnames)
433 self._parts.update(m)
446 self._parts.update(m)
434
447
435 if self._parts['docheader']:
448 if self._parts['docheader']:
436 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
449 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
437
450
438 def close(self):
451 def close(self):
439 if self._parts['docfooter']:
452 if self._parts['docfooter']:
440 if not self.footer:
453 if not self.footer:
441 self.footer = ""
454 self.footer = ""
442 self.footer += templater.stringify(self.t(self._parts['docfooter']))
455 self.footer += templater.stringify(self.t(self._parts['docfooter']))
443 return super(changesettemplater, self).close()
456 return super(changesettemplater, self).close()
444
457
445 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
458 def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
446 '''show a single changeset or file revision'''
459 '''show a single changeset or file revision'''
447 props = props.copy()
460 props = props.copy()
448 props['ctx'] = ctx
461 props['ctx'] = ctx
449 props['index'] = index = next(self._counter)
462 props['index'] = index = next(self._counter)
450 props['revcache'] = {'copies': copies}
463 props['revcache'] = {'copies': copies}
451 props = pycompat.strkwargs(props)
464 props = pycompat.strkwargs(props)
452
465
453 # write separator, which wouldn't work well with the header part below
466 # write separator, which wouldn't work well with the header part below
454 # since there's inherently a conflict between header (across items) and
467 # since there's inherently a conflict between header (across items) and
455 # separator (per item)
468 # separator (per item)
456 if self._parts['separator'] and index > 0:
469 if self._parts['separator'] and index > 0:
457 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
470 self.ui.write(templater.stringify(self.t(self._parts['separator'])))
458
471
459 # write header
472 # write header
460 if self._parts['header']:
473 if self._parts['header']:
461 h = templater.stringify(self.t(self._parts['header'], **props))
474 h = templater.stringify(self.t(self._parts['header'], **props))
462 if self.buffered:
475 if self.buffered:
463 self.header[ctx.rev()] = h
476 self.header[ctx.rev()] = h
464 else:
477 else:
465 if self.lastheader != h:
478 if self.lastheader != h:
466 self.lastheader = h
479 self.lastheader = h
467 self.ui.write(h)
480 self.ui.write(h)
468
481
469 # write changeset metadata, then patch if requested
482 # write changeset metadata, then patch if requested
470 key = self._parts[self._tref]
483 key = self._parts[self._tref]
471 self.ui.write(templater.stringify(self.t(key, **props)))
484 self.ui.write(templater.stringify(self.t(key, **props)))
472 self._showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
485 self._showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
473
486
474 if self._parts['footer']:
487 if self._parts['footer']:
475 if not self.footer:
488 if not self.footer:
476 self.footer = templater.stringify(
489 self.footer = templater.stringify(
477 self.t(self._parts['footer'], **props))
490 self.t(self._parts['footer'], **props))
478
491
479 def templatespec(tmpl, mapfile):
492 def templatespec(tmpl, mapfile):
480 if mapfile:
493 if mapfile:
481 return formatter.templatespec('changeset', tmpl, mapfile)
494 return formatter.templatespec('changeset', tmpl, mapfile)
482 else:
495 else:
483 return formatter.templatespec('', tmpl, None)
496 return formatter.templatespec('', tmpl, None)
484
497
485 def _lookuptemplate(ui, tmpl, style):
498 def _lookuptemplate(ui, tmpl, style):
486 """Find the template matching the given template spec or style
499 """Find the template matching the given template spec or style
487
500
488 See formatter.lookuptemplate() for details.
501 See formatter.lookuptemplate() for details.
489 """
502 """
490
503
491 # ui settings
504 # ui settings
492 if not tmpl and not style: # template are stronger than style
505 if not tmpl and not style: # template are stronger than style
493 tmpl = ui.config('ui', 'logtemplate')
506 tmpl = ui.config('ui', 'logtemplate')
494 if tmpl:
507 if tmpl:
495 return templatespec(templater.unquotestring(tmpl), None)
508 return templatespec(templater.unquotestring(tmpl), None)
496 else:
509 else:
497 style = util.expandpath(ui.config('ui', 'style'))
510 style = util.expandpath(ui.config('ui', 'style'))
498
511
499 if not tmpl and style:
512 if not tmpl and style:
500 mapfile = style
513 mapfile = style
501 if not os.path.split(mapfile)[0]:
514 if not os.path.split(mapfile)[0]:
502 mapname = (templater.templatepath('map-cmdline.' + mapfile)
515 mapname = (templater.templatepath('map-cmdline.' + mapfile)
503 or templater.templatepath(mapfile))
516 or templater.templatepath(mapfile))
504 if mapname:
517 if mapname:
505 mapfile = mapname
518 mapfile = mapname
506 return templatespec(None, mapfile)
519 return templatespec(None, mapfile)
507
520
508 if not tmpl:
521 if not tmpl:
509 return templatespec(None, None)
522 return templatespec(None, None)
510
523
511 return formatter.lookuptemplate(ui, 'changeset', tmpl)
524 return formatter.lookuptemplate(ui, 'changeset', tmpl)
512
525
513 def maketemplater(ui, repo, tmpl, buffered=False):
526 def maketemplater(ui, repo, tmpl, buffered=False):
514 """Create a changesettemplater from a literal template 'tmpl'
527 """Create a changesettemplater from a literal template 'tmpl'
515 byte-string."""
528 byte-string."""
516 spec = templatespec(tmpl, None)
529 spec = templatespec(tmpl, None)
517 return changesettemplater(ui, repo, spec, buffered=buffered)
530 return changesettemplater(ui, repo, spec, buffered=buffered)
518
531
519 def changesetdisplayer(ui, repo, opts, buffered=False):
532 def changesetdisplayer(ui, repo, opts, buffered=False):
520 """show one changeset using template or regular display.
533 """show one changeset using template or regular display.
521
534
522 Display format will be the first non-empty hit of:
535 Display format will be the first non-empty hit of:
523 1. option 'template'
536 1. option 'template'
524 2. option 'style'
537 2. option 'style'
525 3. [ui] setting 'logtemplate'
538 3. [ui] setting 'logtemplate'
526 4. [ui] setting 'style'
539 4. [ui] setting 'style'
527 If all of these values are either the unset or the empty string,
540 If all of these values are either the unset or the empty string,
528 regular display via changesetprinter() is done.
541 regular display via changesetprinter() is done.
529 """
542 """
530 # options
543 # options
531 match = None
544 match = None
532 if opts.get('patch') or opts.get('stat'):
545 if opts.get('patch') or opts.get('stat'):
533 match = scmutil.matchall(repo)
546 match = scmutil.matchall(repo)
534
547
535 if opts.get('template') == 'json':
548 if opts.get('template') == 'json':
536 return jsonchangeset(ui, repo, match, opts, buffered)
549 return jsonchangeset(ui, repo, match, opts, buffered)
537
550
538 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
551 spec = _lookuptemplate(ui, opts.get('template'), opts.get('style'))
539
552
540 if not spec.ref and not spec.tmpl and not spec.mapfile:
553 if not spec.ref and not spec.tmpl and not spec.mapfile:
541 return changesetprinter(ui, repo, match, opts, buffered)
554 return changesetprinter(ui, repo, match, opts, buffered)
542
555
543 return changesettemplater(ui, repo, spec, match, opts, buffered)
556 return changesettemplater(ui, repo, spec, match, opts, buffered)
544
557
545 def _makematcher(repo, revs, pats, opts):
558 def _makematcher(repo, revs, pats, opts):
546 """Build matcher and expanded patterns from log options
559 """Build matcher and expanded patterns from log options
547
560
548 If --follow, revs are the revisions to follow from.
561 If --follow, revs are the revisions to follow from.
549
562
550 Returns (match, pats, slowpath) where
563 Returns (match, pats, slowpath) where
551 - match: a matcher built from the given pats and -I/-X opts
564 - match: a matcher built from the given pats and -I/-X opts
552 - pats: patterns used (globs are expanded on Windows)
565 - pats: patterns used (globs are expanded on Windows)
553 - slowpath: True if patterns aren't as simple as scanning filelogs
566 - slowpath: True if patterns aren't as simple as scanning filelogs
554 """
567 """
555 # pats/include/exclude are passed to match.match() directly in
568 # pats/include/exclude are passed to match.match() directly in
556 # _matchfiles() revset but walkchangerevs() builds its matcher with
569 # _matchfiles() revset but walkchangerevs() builds its matcher with
557 # scmutil.match(). The difference is input pats are globbed on
570 # scmutil.match(). The difference is input pats are globbed on
558 # platforms without shell expansion (windows).
571 # platforms without shell expansion (windows).
559 wctx = repo[None]
572 wctx = repo[None]
560 match, pats = scmutil.matchandpats(wctx, pats, opts)
573 match, pats = scmutil.matchandpats(wctx, pats, opts)
561 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
574 slowpath = match.anypats() or (not match.always() and opts.get('removed'))
562 if not slowpath:
575 if not slowpath:
563 follow = opts.get('follow') or opts.get('follow_first')
576 follow = opts.get('follow') or opts.get('follow_first')
564 startctxs = []
577 startctxs = []
565 if follow and opts.get('rev'):
578 if follow and opts.get('rev'):
566 startctxs = [repo[r] for r in revs]
579 startctxs = [repo[r] for r in revs]
567 for f in match.files():
580 for f in match.files():
568 if follow and startctxs:
581 if follow and startctxs:
569 # No idea if the path was a directory at that revision, so
582 # No idea if the path was a directory at that revision, so
570 # take the slow path.
583 # take the slow path.
571 if any(f not in c for c in startctxs):
584 if any(f not in c for c in startctxs):
572 slowpath = True
585 slowpath = True
573 continue
586 continue
574 elif follow and f not in wctx:
587 elif follow and f not in wctx:
575 # If the file exists, it may be a directory, so let it
588 # If the file exists, it may be a directory, so let it
576 # take the slow path.
589 # take the slow path.
577 if os.path.exists(repo.wjoin(f)):
590 if os.path.exists(repo.wjoin(f)):
578 slowpath = True
591 slowpath = True
579 continue
592 continue
580 else:
593 else:
581 raise error.Abort(_('cannot follow file not in parent '
594 raise error.Abort(_('cannot follow file not in parent '
582 'revision: "%s"') % f)
595 'revision: "%s"') % f)
583 filelog = repo.file(f)
596 filelog = repo.file(f)
584 if not filelog:
597 if not filelog:
585 # A zero count may be a directory or deleted file, so
598 # A zero count may be a directory or deleted file, so
586 # try to find matching entries on the slow path.
599 # try to find matching entries on the slow path.
587 if follow:
600 if follow:
588 raise error.Abort(
601 raise error.Abort(
589 _('cannot follow nonexistent file: "%s"') % f)
602 _('cannot follow nonexistent file: "%s"') % f)
590 slowpath = True
603 slowpath = True
591
604
592 # We decided to fall back to the slowpath because at least one
605 # We decided to fall back to the slowpath because at least one
593 # of the paths was not a file. Check to see if at least one of them
606 # of the paths was not a file. Check to see if at least one of them
594 # existed in history - in that case, we'll continue down the
607 # existed in history - in that case, we'll continue down the
595 # slowpath; otherwise, we can turn off the slowpath
608 # slowpath; otherwise, we can turn off the slowpath
596 if slowpath:
609 if slowpath:
597 for path in match.files():
610 for path in match.files():
598 if path == '.' or path in repo.store:
611 if path == '.' or path in repo.store:
599 break
612 break
600 else:
613 else:
601 slowpath = False
614 slowpath = False
602
615
603 return match, pats, slowpath
616 return match, pats, slowpath
604
617
605 def _fileancestors(repo, revs, match, followfirst):
618 def _fileancestors(repo, revs, match, followfirst):
606 fctxs = []
619 fctxs = []
607 for r in revs:
620 for r in revs:
608 ctx = repo[r]
621 ctx = repo[r]
609 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
622 fctxs.extend(ctx[f].introfilectx() for f in ctx.walk(match))
610
623
611 # When displaying a revision with --patch --follow FILE, we have
624 # When displaying a revision with --patch --follow FILE, we have
612 # to know which file of the revision must be diffed. With
625 # to know which file of the revision must be diffed. With
613 # --follow, we want the names of the ancestors of FILE in the
626 # --follow, we want the names of the ancestors of FILE in the
614 # revision, stored in "fcache". "fcache" is populated as a side effect
627 # revision, stored in "fcache". "fcache" is populated as a side effect
615 # of the graph traversal.
628 # of the graph traversal.
616 fcache = {}
629 fcache = {}
617 def filematcher(rev):
630 def filematcher(rev):
618 return scmutil.matchfiles(repo, fcache.get(rev, []))
631 return scmutil.matchfiles(repo, fcache.get(rev, []))
619
632
620 def revgen():
633 def revgen():
621 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
634 for rev, cs in dagop.filectxancestors(fctxs, followfirst=followfirst):
622 fcache[rev] = [c.path() for c in cs]
635 fcache[rev] = [c.path() for c in cs]
623 yield rev
636 yield rev
624 return smartset.generatorset(revgen(), iterasc=False), filematcher
637 return smartset.generatorset(revgen(), iterasc=False), filematcher
625
638
626 def _makenofollowfilematcher(repo, pats, opts):
639 def _makenofollowfilematcher(repo, pats, opts):
627 '''hook for extensions to override the filematcher for non-follow cases'''
640 '''hook for extensions to override the filematcher for non-follow cases'''
628 return None
641 return None
629
642
630 _opt2logrevset = {
643 _opt2logrevset = {
631 'no_merges': ('not merge()', None),
644 'no_merges': ('not merge()', None),
632 'only_merges': ('merge()', None),
645 'only_merges': ('merge()', None),
633 '_matchfiles': (None, '_matchfiles(%ps)'),
646 '_matchfiles': (None, '_matchfiles(%ps)'),
634 'date': ('date(%s)', None),
647 'date': ('date(%s)', None),
635 'branch': ('branch(%s)', '%lr'),
648 'branch': ('branch(%s)', '%lr'),
636 '_patslog': ('filelog(%s)', '%lr'),
649 '_patslog': ('filelog(%s)', '%lr'),
637 'keyword': ('keyword(%s)', '%lr'),
650 'keyword': ('keyword(%s)', '%lr'),
638 'prune': ('ancestors(%s)', 'not %lr'),
651 'prune': ('ancestors(%s)', 'not %lr'),
639 'user': ('user(%s)', '%lr'),
652 'user': ('user(%s)', '%lr'),
640 }
653 }
641
654
642 def _makerevset(repo, match, pats, slowpath, opts):
655 def _makerevset(repo, match, pats, slowpath, opts):
643 """Return a revset string built from log options and file patterns"""
656 """Return a revset string built from log options and file patterns"""
644 opts = dict(opts)
657 opts = dict(opts)
645 # follow or not follow?
658 # follow or not follow?
646 follow = opts.get('follow') or opts.get('follow_first')
659 follow = opts.get('follow') or opts.get('follow_first')
647
660
648 # branch and only_branch are really aliases and must be handled at
661 # branch and only_branch are really aliases and must be handled at
649 # the same time
662 # the same time
650 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
663 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
651 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
664 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
652
665
653 if slowpath:
666 if slowpath:
654 # See walkchangerevs() slow path.
667 # See walkchangerevs() slow path.
655 #
668 #
656 # pats/include/exclude cannot be represented as separate
669 # pats/include/exclude cannot be represented as separate
657 # revset expressions as their filtering logic applies at file
670 # revset expressions as their filtering logic applies at file
658 # level. For instance "-I a -X b" matches a revision touching
671 # level. For instance "-I a -X b" matches a revision touching
659 # "a" and "b" while "file(a) and not file(b)" does
672 # "a" and "b" while "file(a) and not file(b)" does
660 # not. Besides, filesets are evaluated against the working
673 # not. Besides, filesets are evaluated against the working
661 # directory.
674 # directory.
662 matchargs = ['r:', 'd:relpath']
675 matchargs = ['r:', 'd:relpath']
663 for p in pats:
676 for p in pats:
664 matchargs.append('p:' + p)
677 matchargs.append('p:' + p)
665 for p in opts.get('include', []):
678 for p in opts.get('include', []):
666 matchargs.append('i:' + p)
679 matchargs.append('i:' + p)
667 for p in opts.get('exclude', []):
680 for p in opts.get('exclude', []):
668 matchargs.append('x:' + p)
681 matchargs.append('x:' + p)
669 opts['_matchfiles'] = matchargs
682 opts['_matchfiles'] = matchargs
670 elif not follow:
683 elif not follow:
671 opts['_patslog'] = list(pats)
684 opts['_patslog'] = list(pats)
672
685
673 expr = []
686 expr = []
674 for op, val in sorted(opts.iteritems()):
687 for op, val in sorted(opts.iteritems()):
675 if not val:
688 if not val:
676 continue
689 continue
677 if op not in _opt2logrevset:
690 if op not in _opt2logrevset:
678 continue
691 continue
679 revop, listop = _opt2logrevset[op]
692 revop, listop = _opt2logrevset[op]
680 if revop and '%' not in revop:
693 if revop and '%' not in revop:
681 expr.append(revop)
694 expr.append(revop)
682 elif not listop:
695 elif not listop:
683 expr.append(revsetlang.formatspec(revop, val))
696 expr.append(revsetlang.formatspec(revop, val))
684 else:
697 else:
685 if revop:
698 if revop:
686 val = [revsetlang.formatspec(revop, v) for v in val]
699 val = [revsetlang.formatspec(revop, v) for v in val]
687 expr.append(revsetlang.formatspec(listop, val))
700 expr.append(revsetlang.formatspec(listop, val))
688
701
689 if expr:
702 if expr:
690 expr = '(' + ' and '.join(expr) + ')'
703 expr = '(' + ' and '.join(expr) + ')'
691 else:
704 else:
692 expr = None
705 expr = None
693 return expr
706 return expr
694
707
695 def _initialrevs(repo, opts):
708 def _initialrevs(repo, opts):
696 """Return the initial set of revisions to be filtered or followed"""
709 """Return the initial set of revisions to be filtered or followed"""
697 follow = opts.get('follow') or opts.get('follow_first')
710 follow = opts.get('follow') or opts.get('follow_first')
698 if opts.get('rev'):
711 if opts.get('rev'):
699 revs = scmutil.revrange(repo, opts['rev'])
712 revs = scmutil.revrange(repo, opts['rev'])
700 elif follow and repo.dirstate.p1() == nullid:
713 elif follow and repo.dirstate.p1() == nullid:
701 revs = smartset.baseset()
714 revs = smartset.baseset()
702 elif follow:
715 elif follow:
703 revs = repo.revs('.')
716 revs = repo.revs('.')
704 else:
717 else:
705 revs = smartset.spanset(repo)
718 revs = smartset.spanset(repo)
706 revs.reverse()
719 revs.reverse()
707 return revs
720 return revs
708
721
709 def getrevs(repo, pats, opts):
722 def getrevs(repo, pats, opts):
710 """Return (revs, filematcher) where revs is a smartset
723 """Return (revs, filematcher) where revs is a smartset
711
724
712 filematcher is a callable taking a revision number and returning a match
725 filematcher is a callable taking a revision number and returning a match
713 objects filtering the files to be detailed when displaying the revision.
726 objects filtering the files to be detailed when displaying the revision.
714 """
727 """
715 follow = opts.get('follow') or opts.get('follow_first')
728 follow = opts.get('follow') or opts.get('follow_first')
716 followfirst = opts.get('follow_first')
729 followfirst = opts.get('follow_first')
717 limit = getlimit(opts)
730 limit = getlimit(opts)
718 revs = _initialrevs(repo, opts)
731 revs = _initialrevs(repo, opts)
719 if not revs:
732 if not revs:
720 return smartset.baseset(), None
733 return smartset.baseset(), None
721 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
734 match, pats, slowpath = _makematcher(repo, revs, pats, opts)
722 filematcher = None
735 filematcher = None
723 if follow:
736 if follow:
724 if slowpath or match.always():
737 if slowpath or match.always():
725 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
738 revs = dagop.revancestors(repo, revs, followfirst=followfirst)
726 else:
739 else:
727 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
740 revs, filematcher = _fileancestors(repo, revs, match, followfirst)
728 revs.reverse()
741 revs.reverse()
729 if filematcher is None:
742 if filematcher is None:
730 filematcher = _makenofollowfilematcher(repo, pats, opts)
743 filematcher = _makenofollowfilematcher(repo, pats, opts)
731 if filematcher is None:
744 if filematcher is None:
732 def filematcher(rev):
745 def filematcher(rev):
733 return match
746 return match
734
747
735 expr = _makerevset(repo, match, pats, slowpath, opts)
748 expr = _makerevset(repo, match, pats, slowpath, opts)
736 if opts.get('graph') and opts.get('rev'):
749 if opts.get('graph') and opts.get('rev'):
737 # User-specified revs might be unsorted, but don't sort before
750 # User-specified revs might be unsorted, but don't sort before
738 # _makerevset because it might depend on the order of revs
751 # _makerevset because it might depend on the order of revs
739 if not (revs.isdescending() or revs.istopo()):
752 if not (revs.isdescending() or revs.istopo()):
740 revs.sort(reverse=True)
753 revs.sort(reverse=True)
741 if expr:
754 if expr:
742 matcher = revset.match(None, expr)
755 matcher = revset.match(None, expr)
743 revs = matcher(repo, revs)
756 revs = matcher(repo, revs)
744 if limit is not None:
757 if limit is not None:
745 revs = revs.slice(0, limit)
758 revs = revs.slice(0, limit)
746 return revs, filematcher
759 return revs, filematcher
747
760
748 def _parselinerangeopt(repo, opts):
761 def _parselinerangeopt(repo, opts):
749 """Parse --line-range log option and return a list of tuples (filename,
762 """Parse --line-range log option and return a list of tuples (filename,
750 (fromline, toline)).
763 (fromline, toline)).
751 """
764 """
752 linerangebyfname = []
765 linerangebyfname = []
753 for pat in opts.get('line_range', []):
766 for pat in opts.get('line_range', []):
754 try:
767 try:
755 pat, linerange = pat.rsplit(',', 1)
768 pat, linerange = pat.rsplit(',', 1)
756 except ValueError:
769 except ValueError:
757 raise error.Abort(_('malformatted line-range pattern %s') % pat)
770 raise error.Abort(_('malformatted line-range pattern %s') % pat)
758 try:
771 try:
759 fromline, toline = map(int, linerange.split(':'))
772 fromline, toline = map(int, linerange.split(':'))
760 except ValueError:
773 except ValueError:
761 raise error.Abort(_("invalid line range for %s") % pat)
774 raise error.Abort(_("invalid line range for %s") % pat)
762 msg = _("line range pattern '%s' must match exactly one file") % pat
775 msg = _("line range pattern '%s' must match exactly one file") % pat
763 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
776 fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
764 linerangebyfname.append(
777 linerangebyfname.append(
765 (fname, util.processlinerange(fromline, toline)))
778 (fname, util.processlinerange(fromline, toline)))
766 return linerangebyfname
779 return linerangebyfname
767
780
768 def getlinerangerevs(repo, userrevs, opts):
781 def getlinerangerevs(repo, userrevs, opts):
769 """Return (revs, filematcher, hunksfilter).
782 """Return (revs, filematcher, hunksfilter).
770
783
771 "revs" are revisions obtained by processing "line-range" log options and
784 "revs" are revisions obtained by processing "line-range" log options and
772 walking block ancestors of each specified file/line-range.
785 walking block ancestors of each specified file/line-range.
773
786
774 "filematcher(rev) -> match" is a factory function returning a match object
787 "filematcher(rev) -> match" is a factory function returning a match object
775 for a given revision for file patterns specified in --line-range option.
788 for a given revision for file patterns specified in --line-range option.
776 If neither --stat nor --patch options are passed, "filematcher" is None.
789 If neither --stat nor --patch options are passed, "filematcher" is None.
777
790
778 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
791 "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
779 returning a hunks filtering function.
792 returning a hunks filtering function.
780 If neither --stat nor --patch options are passed, "filterhunks" is None.
793 If neither --stat nor --patch options are passed, "filterhunks" is None.
781 """
794 """
782 wctx = repo[None]
795 wctx = repo[None]
783
796
784 # Two-levels map of "rev -> file ctx -> [line range]".
797 # Two-levels map of "rev -> file ctx -> [line range]".
785 linerangesbyrev = {}
798 linerangesbyrev = {}
786 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
799 for fname, (fromline, toline) in _parselinerangeopt(repo, opts):
787 if fname not in wctx:
800 if fname not in wctx:
788 raise error.Abort(_('cannot follow file not in parent '
801 raise error.Abort(_('cannot follow file not in parent '
789 'revision: "%s"') % fname)
802 'revision: "%s"') % fname)
790 fctx = wctx.filectx(fname)
803 fctx = wctx.filectx(fname)
791 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
804 for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
792 rev = fctx.introrev()
805 rev = fctx.introrev()
793 if rev not in userrevs:
806 if rev not in userrevs:
794 continue
807 continue
795 linerangesbyrev.setdefault(
808 linerangesbyrev.setdefault(
796 rev, {}).setdefault(
809 rev, {}).setdefault(
797 fctx.path(), []).append(linerange)
810 fctx.path(), []).append(linerange)
798
811
799 filematcher = None
812 filematcher = None
800 hunksfilter = None
813 hunksfilter = None
801 if opts.get('patch') or opts.get('stat'):
814 if opts.get('patch') or opts.get('stat'):
802
815
803 def nofilterhunksfn(fctx, hunks):
816 def nofilterhunksfn(fctx, hunks):
804 return hunks
817 return hunks
805
818
806 def hunksfilter(rev):
819 def hunksfilter(rev):
807 fctxlineranges = linerangesbyrev.get(rev)
820 fctxlineranges = linerangesbyrev.get(rev)
808 if fctxlineranges is None:
821 if fctxlineranges is None:
809 return nofilterhunksfn
822 return nofilterhunksfn
810
823
811 def filterfn(fctx, hunks):
824 def filterfn(fctx, hunks):
812 lineranges = fctxlineranges.get(fctx.path())
825 lineranges = fctxlineranges.get(fctx.path())
813 if lineranges is not None:
826 if lineranges is not None:
814 for hr, lines in hunks:
827 for hr, lines in hunks:
815 if hr is None: # binary
828 if hr is None: # binary
816 yield hr, lines
829 yield hr, lines
817 continue
830 continue
818 if any(mdiff.hunkinrange(hr[2:], lr)
831 if any(mdiff.hunkinrange(hr[2:], lr)
819 for lr in lineranges):
832 for lr in lineranges):
820 yield hr, lines
833 yield hr, lines
821 else:
834 else:
822 for hunk in hunks:
835 for hunk in hunks:
823 yield hunk
836 yield hunk
824
837
825 return filterfn
838 return filterfn
826
839
827 def filematcher(rev):
840 def filematcher(rev):
828 files = list(linerangesbyrev.get(rev, []))
841 files = list(linerangesbyrev.get(rev, []))
829 return scmutil.matchfiles(repo, files)
842 return scmutil.matchfiles(repo, files)
830
843
831 revs = sorted(linerangesbyrev, reverse=True)
844 revs = sorted(linerangesbyrev, reverse=True)
832
845
833 return revs, filematcher, hunksfilter
846 return revs, filematcher, hunksfilter
834
847
835 def _graphnodeformatter(ui, displayer):
848 def _graphnodeformatter(ui, displayer):
836 spec = ui.config('ui', 'graphnodetemplate')
849 spec = ui.config('ui', 'graphnodetemplate')
837 if not spec:
850 if not spec:
838 return templatekw.showgraphnode # fast path for "{graphnode}"
851 return templatekw.showgraphnode # fast path for "{graphnode}"
839
852
840 spec = templater.unquotestring(spec)
853 spec = templater.unquotestring(spec)
841 tres = formatter.templateresources(ui)
854 tres = formatter.templateresources(ui)
842 if isinstance(displayer, changesettemplater):
855 if isinstance(displayer, changesettemplater):
843 tres['cache'] = displayer.cache # reuse cache of slow templates
856 tres['cache'] = displayer.cache # reuse cache of slow templates
844 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
857 templ = formatter.maketemplater(ui, spec, defaults=templatekw.keywords,
845 resources=tres)
858 resources=tres)
846 def formatnode(repo, ctx):
859 def formatnode(repo, ctx):
847 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
860 props = {'ctx': ctx, 'repo': repo, 'revcache': {}}
848 return templ.render(props)
861 return templ.render(props)
849 return formatnode
862 return formatnode
850
863
851 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
864 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
852 filematcher=None, props=None):
865 filematcher=None, props=None):
853 props = props or {}
866 props = props or {}
854 formatnode = _graphnodeformatter(ui, displayer)
867 formatnode = _graphnodeformatter(ui, displayer)
855 state = graphmod.asciistate()
868 state = graphmod.asciistate()
856 styles = state['styles']
869 styles = state['styles']
857
870
858 # only set graph styling if HGPLAIN is not set.
871 # only set graph styling if HGPLAIN is not set.
859 if ui.plain('graph'):
872 if ui.plain('graph'):
860 # set all edge styles to |, the default pre-3.8 behaviour
873 # set all edge styles to |, the default pre-3.8 behaviour
861 styles.update(dict.fromkeys(styles, '|'))
874 styles.update(dict.fromkeys(styles, '|'))
862 else:
875 else:
863 edgetypes = {
876 edgetypes = {
864 'parent': graphmod.PARENT,
877 'parent': graphmod.PARENT,
865 'grandparent': graphmod.GRANDPARENT,
878 'grandparent': graphmod.GRANDPARENT,
866 'missing': graphmod.MISSINGPARENT
879 'missing': graphmod.MISSINGPARENT
867 }
880 }
868 for name, key in edgetypes.items():
881 for name, key in edgetypes.items():
869 # experimental config: experimental.graphstyle.*
882 # experimental config: experimental.graphstyle.*
870 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
883 styles[key] = ui.config('experimental', 'graphstyle.%s' % name,
871 styles[key])
884 styles[key])
872 if not styles[key]:
885 if not styles[key]:
873 styles[key] = None
886 styles[key] = None
874
887
875 # experimental config: experimental.graphshorten
888 # experimental config: experimental.graphshorten
876 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
889 state['graphshorten'] = ui.configbool('experimental', 'graphshorten')
877
890
878 for rev, type, ctx, parents in dag:
891 for rev, type, ctx, parents in dag:
879 char = formatnode(repo, ctx)
892 char = formatnode(repo, ctx)
880 copies = None
893 copies = None
881 if getrenamed and ctx.rev():
894 if getrenamed and ctx.rev():
882 copies = []
895 copies = []
883 for fn in ctx.files():
896 for fn in ctx.files():
884 rename = getrenamed(fn, ctx.rev())
897 rename = getrenamed(fn, ctx.rev())
885 if rename:
898 if rename:
886 copies.append((fn, rename[0]))
899 copies.append((fn, rename[0]))
887 revmatchfn = None
900 revmatchfn = None
888 if filematcher is not None:
901 if filematcher is not None:
889 revmatchfn = filematcher(ctx.rev())
902 revmatchfn = filematcher(ctx.rev())
890 edges = edgefn(type, char, state, rev, parents)
903 edges = edgefn(type, char, state, rev, parents)
891 firstedge = next(edges)
904 firstedge = next(edges)
892 width = firstedge[2]
905 width = firstedge[2]
893 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
906 displayer.show(ctx, copies=copies, matchfn=revmatchfn,
894 _graphwidth=width, **pycompat.strkwargs(props))
907 _graphwidth=width, **pycompat.strkwargs(props))
895 lines = displayer.hunk.pop(rev).split('\n')
908 lines = displayer.hunk.pop(rev).split('\n')
896 if not lines[-1]:
909 if not lines[-1]:
897 del lines[-1]
910 del lines[-1]
898 displayer.flush(ctx)
911 displayer.flush(ctx)
899 for type, char, width, coldata in itertools.chain([firstedge], edges):
912 for type, char, width, coldata in itertools.chain([firstedge], edges):
900 graphmod.ascii(ui, state, type, char, lines, coldata)
913 graphmod.ascii(ui, state, type, char, lines, coldata)
901 lines = []
914 lines = []
902 displayer.close()
915 displayer.close()
903
916
904 def graphlog(ui, repo, revs, filematcher, opts):
917 def graphlog(ui, repo, revs, filematcher, opts):
905 # Parameters are identical to log command ones
918 # Parameters are identical to log command ones
906 revdag = graphmod.dagwalker(repo, revs)
919 revdag = graphmod.dagwalker(repo, revs)
907
920
908 getrenamed = None
921 getrenamed = None
909 if opts.get('copies'):
922 if opts.get('copies'):
910 endrev = None
923 endrev = None
911 if opts.get('rev'):
924 if opts.get('rev'):
912 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
925 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
913 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
926 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
914
927
915 ui.pager('log')
928 ui.pager('log')
916 displayer = changesetdisplayer(ui, repo, opts, buffered=True)
929 displayer = changesetdisplayer(ui, repo, opts, buffered=True)
917 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
930 displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges, getrenamed,
918 filematcher)
931 filematcher)
919
932
920 def checkunsupportedgraphflags(pats, opts):
933 def checkunsupportedgraphflags(pats, opts):
921 for op in ["newest_first"]:
934 for op in ["newest_first"]:
922 if op in opts and opts[op]:
935 if op in opts and opts[op]:
923 raise error.Abort(_("-G/--graph option is incompatible with --%s")
936 raise error.Abort(_("-G/--graph option is incompatible with --%s")
924 % op.replace("_", "-"))
937 % op.replace("_", "-"))
925
938
926 def graphrevs(repo, nodes, opts):
939 def graphrevs(repo, nodes, opts):
927 limit = getlimit(opts)
940 limit = getlimit(opts)
928 nodes.reverse()
941 nodes.reverse()
929 if limit is not None:
942 if limit is not None:
930 nodes = nodes[:limit]
943 nodes = nodes[:limit]
931 return graphmod.nodes(repo, nodes)
944 return graphmod.nodes(repo, nodes)
@@ -1,1836 +1,1847 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 configitems,
30 configitems,
31 encoding,
31 encoding,
32 error,
32 error,
33 formatter,
33 formatter,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40
40
41 urlreq = util.urlreq
41 urlreq = util.urlreq
42
42
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
45 if not c.isalnum())
45 if not c.isalnum())
46
46
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
47 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
48 tweakrc = b"""
48 tweakrc = b"""
49 [ui]
49 [ui]
50 # The rollback command is dangerous. As a rule, don't use it.
50 # The rollback command is dangerous. As a rule, don't use it.
51 rollback = False
51 rollback = False
52 # Make `hg status` report copy information
52 # Make `hg status` report copy information
53 statuscopies = yes
53 statuscopies = yes
54 # Prefer curses UIs when available. Revert to plain-text with `text`.
54 # Prefer curses UIs when available. Revert to plain-text with `text`.
55 interface = curses
55 interface = curses
56
56
57 [commands]
57 [commands]
58 # Make `hg status` emit cwd-relative paths by default.
58 # Make `hg status` emit cwd-relative paths by default.
59 status.relative = yes
59 status.relative = yes
60 # Refuse to perform an `hg update` that would cause a file content merge
60 # Refuse to perform an `hg update` that would cause a file content merge
61 update.check = noconflict
61 update.check = noconflict
62
62
63 [diff]
63 [diff]
64 git = 1
64 git = 1
65 showfunc = 1
65 showfunc = 1
66 """
66 """
67
67
68 samplehgrcs = {
68 samplehgrcs = {
69 'user':
69 'user':
70 b"""# example user config (see 'hg help config' for more info)
70 b"""# example user config (see 'hg help config' for more info)
71 [ui]
71 [ui]
72 # name and email, e.g.
72 # name and email, e.g.
73 # username = Jane Doe <jdoe@example.com>
73 # username = Jane Doe <jdoe@example.com>
74 username =
74 username =
75
75
76 # We recommend enabling tweakdefaults to get slight improvements to
76 # We recommend enabling tweakdefaults to get slight improvements to
77 # the UI over time. Make sure to set HGPLAIN in the environment when
77 # the UI over time. Make sure to set HGPLAIN in the environment when
78 # writing scripts!
78 # writing scripts!
79 # tweakdefaults = True
79 # tweakdefaults = True
80
80
81 # uncomment to disable color in command output
81 # uncomment to disable color in command output
82 # (see 'hg help color' for details)
82 # (see 'hg help color' for details)
83 # color = never
83 # color = never
84
84
85 # uncomment to disable command output pagination
85 # uncomment to disable command output pagination
86 # (see 'hg help pager' for details)
86 # (see 'hg help pager' for details)
87 # paginate = never
87 # paginate = never
88
88
89 [extensions]
89 [extensions]
90 # uncomment these lines to enable some popular extensions
90 # uncomment these lines to enable some popular extensions
91 # (see 'hg help extensions' for more info)
91 # (see 'hg help extensions' for more info)
92 #
92 #
93 # churn =
93 # churn =
94 """,
94 """,
95
95
96 'cloned':
96 'cloned':
97 b"""# example repository config (see 'hg help config' for more info)
97 b"""# example repository config (see 'hg help config' for more info)
98 [paths]
98 [paths]
99 default = %s
99 default = %s
100
100
101 # path aliases to other clones of this repo in URLs or filesystem paths
101 # path aliases to other clones of this repo in URLs or filesystem paths
102 # (see 'hg help config.paths' for more info)
102 # (see 'hg help config.paths' for more info)
103 #
103 #
104 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
104 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
105 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
105 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
106 # my-clone = /home/jdoe/jdoes-clone
106 # my-clone = /home/jdoe/jdoes-clone
107
107
108 [ui]
108 [ui]
109 # name and email (local to this repository, optional), e.g.
109 # name and email (local to this repository, optional), e.g.
110 # username = Jane Doe <jdoe@example.com>
110 # username = Jane Doe <jdoe@example.com>
111 """,
111 """,
112
112
113 'local':
113 'local':
114 b"""# example repository config (see 'hg help config' for more info)
114 b"""# example repository config (see 'hg help config' for more info)
115 [paths]
115 [paths]
116 # path aliases to other clones of this repo in URLs or filesystem paths
116 # path aliases to other clones of this repo in URLs or filesystem paths
117 # (see 'hg help config.paths' for more info)
117 # (see 'hg help config.paths' for more info)
118 #
118 #
119 # default = http://example.com/hg/example-repo
119 # default = http://example.com/hg/example-repo
120 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
120 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
121 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
121 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
122 # my-clone = /home/jdoe/jdoes-clone
122 # my-clone = /home/jdoe/jdoes-clone
123
123
124 [ui]
124 [ui]
125 # name and email (local to this repository, optional), e.g.
125 # name and email (local to this repository, optional), e.g.
126 # username = Jane Doe <jdoe@example.com>
126 # username = Jane Doe <jdoe@example.com>
127 """,
127 """,
128
128
129 'global':
129 'global':
130 b"""# example system-wide hg config (see 'hg help config' for more info)
130 b"""# example system-wide hg config (see 'hg help config' for more info)
131
131
132 [ui]
132 [ui]
133 # uncomment to disable color in command output
133 # uncomment to disable color in command output
134 # (see 'hg help color' for details)
134 # (see 'hg help color' for details)
135 # color = never
135 # color = never
136
136
137 # uncomment to disable command output pagination
137 # uncomment to disable command output pagination
138 # (see 'hg help pager' for details)
138 # (see 'hg help pager' for details)
139 # paginate = never
139 # paginate = never
140
140
141 [extensions]
141 [extensions]
142 # uncomment these lines to enable some popular extensions
142 # uncomment these lines to enable some popular extensions
143 # (see 'hg help extensions' for more info)
143 # (see 'hg help extensions' for more info)
144 #
144 #
145 # blackbox =
145 # blackbox =
146 # churn =
146 # churn =
147 """,
147 """,
148 }
148 }
149
149
150 def _maybestrurl(maybebytes):
150 def _maybestrurl(maybebytes):
151 return util.rapply(pycompat.strurl, maybebytes)
151 return util.rapply(pycompat.strurl, maybebytes)
152
152
153 def _maybebytesurl(maybestr):
153 def _maybebytesurl(maybestr):
154 return util.rapply(pycompat.bytesurl, maybestr)
154 return util.rapply(pycompat.bytesurl, maybestr)
155
155
156 class httppasswordmgrdbproxy(object):
156 class httppasswordmgrdbproxy(object):
157 """Delays loading urllib2 until it's needed."""
157 """Delays loading urllib2 until it's needed."""
158 def __init__(self):
158 def __init__(self):
159 self._mgr = None
159 self._mgr = None
160
160
161 def _get_mgr(self):
161 def _get_mgr(self):
162 if self._mgr is None:
162 if self._mgr is None:
163 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
163 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
164 return self._mgr
164 return self._mgr
165
165
166 def add_password(self, realm, uris, user, passwd):
166 def add_password(self, realm, uris, user, passwd):
167 return self._get_mgr().add_password(
167 return self._get_mgr().add_password(
168 _maybestrurl(realm), _maybestrurl(uris),
168 _maybestrurl(realm), _maybestrurl(uris),
169 _maybestrurl(user), _maybestrurl(passwd))
169 _maybestrurl(user), _maybestrurl(passwd))
170
170
171 def find_user_password(self, realm, uri):
171 def find_user_password(self, realm, uri):
172 mgr = self._get_mgr()
172 mgr = self._get_mgr()
173 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
173 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
174 _maybestrurl(uri)))
174 _maybestrurl(uri)))
175
175
176 def _catchterm(*args):
176 def _catchterm(*args):
177 raise error.SignalInterrupt
177 raise error.SignalInterrupt
178
178
179 # unique object used to detect no default value has been provided when
179 # unique object used to detect no default value has been provided when
180 # retrieving configuration value.
180 # retrieving configuration value.
181 _unset = object()
181 _unset = object()
182
182
183 # _reqexithandlers: callbacks run at the end of a request
183 # _reqexithandlers: callbacks run at the end of a request
184 _reqexithandlers = []
184 _reqexithandlers = []
185
185
186 class ui(object):
186 class ui(object):
187 def __init__(self, src=None):
187 def __init__(self, src=None):
188 """Create a fresh new ui object if no src given
188 """Create a fresh new ui object if no src given
189
189
190 Use uimod.ui.load() to create a ui which knows global and user configs.
190 Use uimod.ui.load() to create a ui which knows global and user configs.
191 In most cases, you should use ui.copy() to create a copy of an existing
191 In most cases, you should use ui.copy() to create a copy of an existing
192 ui object.
192 ui object.
193 """
193 """
194 # _buffers: used for temporary capture of output
194 # _buffers: used for temporary capture of output
195 self._buffers = []
195 self._buffers = []
196 # 3-tuple describing how each buffer in the stack behaves.
196 # 3-tuple describing how each buffer in the stack behaves.
197 # Values are (capture stderr, capture subprocesses, apply labels).
197 # Values are (capture stderr, capture subprocesses, apply labels).
198 self._bufferstates = []
198 self._bufferstates = []
199 # When a buffer is active, defines whether we are expanding labels.
199 # When a buffer is active, defines whether we are expanding labels.
200 # This exists to prevent an extra list lookup.
200 # This exists to prevent an extra list lookup.
201 self._bufferapplylabels = None
201 self._bufferapplylabels = None
202 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
202 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
203 self._reportuntrusted = True
203 self._reportuntrusted = True
204 self._knownconfig = configitems.coreitems
204 self._knownconfig = configitems.coreitems
205 self._ocfg = config.config() # overlay
205 self._ocfg = config.config() # overlay
206 self._tcfg = config.config() # trusted
206 self._tcfg = config.config() # trusted
207 self._ucfg = config.config() # untrusted
207 self._ucfg = config.config() # untrusted
208 self._trustusers = set()
208 self._trustusers = set()
209 self._trustgroups = set()
209 self._trustgroups = set()
210 self.callhooks = True
210 self.callhooks = True
211 # Insecure server connections requested.
211 # Insecure server connections requested.
212 self.insecureconnections = False
212 self.insecureconnections = False
213 # Blocked time
213 # Blocked time
214 self.logblockedtimes = False
214 self.logblockedtimes = False
215 # color mode: see mercurial/color.py for possible value
215 # color mode: see mercurial/color.py for possible value
216 self._colormode = None
216 self._colormode = None
217 self._terminfoparams = {}
217 self._terminfoparams = {}
218 self._styles = {}
218 self._styles = {}
219
219
220 if src:
220 if src:
221 self.fout = src.fout
221 self.fout = src.fout
222 self.ferr = src.ferr
222 self.ferr = src.ferr
223 self.fin = src.fin
223 self.fin = src.fin
224 self.pageractive = src.pageractive
224 self.pageractive = src.pageractive
225 self._disablepager = src._disablepager
225 self._disablepager = src._disablepager
226 self._tweaked = src._tweaked
226 self._tweaked = src._tweaked
227
227
228 self._tcfg = src._tcfg.copy()
228 self._tcfg = src._tcfg.copy()
229 self._ucfg = src._ucfg.copy()
229 self._ucfg = src._ucfg.copy()
230 self._ocfg = src._ocfg.copy()
230 self._ocfg = src._ocfg.copy()
231 self._trustusers = src._trustusers.copy()
231 self._trustusers = src._trustusers.copy()
232 self._trustgroups = src._trustgroups.copy()
232 self._trustgroups = src._trustgroups.copy()
233 self.environ = src.environ
233 self.environ = src.environ
234 self.callhooks = src.callhooks
234 self.callhooks = src.callhooks
235 self.insecureconnections = src.insecureconnections
235 self.insecureconnections = src.insecureconnections
236 self._colormode = src._colormode
236 self._colormode = src._colormode
237 self._terminfoparams = src._terminfoparams.copy()
237 self._terminfoparams = src._terminfoparams.copy()
238 self._styles = src._styles.copy()
238 self._styles = src._styles.copy()
239
239
240 self.fixconfig()
240 self.fixconfig()
241
241
242 self.httppasswordmgrdb = src.httppasswordmgrdb
242 self.httppasswordmgrdb = src.httppasswordmgrdb
243 self._blockedtimes = src._blockedtimes
243 self._blockedtimes = src._blockedtimes
244 else:
244 else:
245 self.fout = util.stdout
245 self.fout = util.stdout
246 self.ferr = util.stderr
246 self.ferr = util.stderr
247 self.fin = util.stdin
247 self.fin = util.stdin
248 self.pageractive = False
248 self.pageractive = False
249 self._disablepager = False
249 self._disablepager = False
250 self._tweaked = False
250 self._tweaked = False
251
251
252 # shared read-only environment
252 # shared read-only environment
253 self.environ = encoding.environ
253 self.environ = encoding.environ
254
254
255 self.httppasswordmgrdb = httppasswordmgrdbproxy()
255 self.httppasswordmgrdb = httppasswordmgrdbproxy()
256 self._blockedtimes = collections.defaultdict(int)
256 self._blockedtimes = collections.defaultdict(int)
257
257
258 allowed = self.configlist('experimental', 'exportableenviron')
258 allowed = self.configlist('experimental', 'exportableenviron')
259 if '*' in allowed:
259 if '*' in allowed:
260 self._exportableenviron = self.environ
260 self._exportableenviron = self.environ
261 else:
261 else:
262 self._exportableenviron = {}
262 self._exportableenviron = {}
263 for k in allowed:
263 for k in allowed:
264 if k in self.environ:
264 if k in self.environ:
265 self._exportableenviron[k] = self.environ[k]
265 self._exportableenviron[k] = self.environ[k]
266
266
267 @classmethod
267 @classmethod
268 def load(cls):
268 def load(cls):
269 """Create a ui and load global and user configs"""
269 """Create a ui and load global and user configs"""
270 u = cls()
270 u = cls()
271 # we always trust global config files and environment variables
271 # we always trust global config files and environment variables
272 for t, f in rcutil.rccomponents():
272 for t, f in rcutil.rccomponents():
273 if t == 'path':
273 if t == 'path':
274 u.readconfig(f, trust=True)
274 u.readconfig(f, trust=True)
275 elif t == 'items':
275 elif t == 'items':
276 sections = set()
276 sections = set()
277 for section, name, value, source in f:
277 for section, name, value, source in f:
278 # do not set u._ocfg
278 # do not set u._ocfg
279 # XXX clean this up once immutable config object is a thing
279 # XXX clean this up once immutable config object is a thing
280 u._tcfg.set(section, name, value, source)
280 u._tcfg.set(section, name, value, source)
281 u._ucfg.set(section, name, value, source)
281 u._ucfg.set(section, name, value, source)
282 sections.add(section)
282 sections.add(section)
283 for section in sections:
283 for section in sections:
284 u.fixconfig(section=section)
284 u.fixconfig(section=section)
285 else:
285 else:
286 raise error.ProgrammingError('unknown rctype: %s' % t)
286 raise error.ProgrammingError('unknown rctype: %s' % t)
287 u._maybetweakdefaults()
287 u._maybetweakdefaults()
288 return u
288 return u
289
289
290 def _maybetweakdefaults(self):
290 def _maybetweakdefaults(self):
291 if not self.configbool('ui', 'tweakdefaults'):
291 if not self.configbool('ui', 'tweakdefaults'):
292 return
292 return
293 if self._tweaked or self.plain('tweakdefaults'):
293 if self._tweaked or self.plain('tweakdefaults'):
294 return
294 return
295
295
296 # Note: it is SUPER IMPORTANT that you set self._tweaked to
296 # Note: it is SUPER IMPORTANT that you set self._tweaked to
297 # True *before* any calls to setconfig(), otherwise you'll get
297 # True *before* any calls to setconfig(), otherwise you'll get
298 # infinite recursion between setconfig and this method.
298 # infinite recursion between setconfig and this method.
299 #
299 #
300 # TODO: We should extract an inner method in setconfig() to
300 # TODO: We should extract an inner method in setconfig() to
301 # avoid this weirdness.
301 # avoid this weirdness.
302 self._tweaked = True
302 self._tweaked = True
303 tmpcfg = config.config()
303 tmpcfg = config.config()
304 tmpcfg.parse('<tweakdefaults>', tweakrc)
304 tmpcfg.parse('<tweakdefaults>', tweakrc)
305 for section in tmpcfg:
305 for section in tmpcfg:
306 for name, value in tmpcfg.items(section):
306 for name, value in tmpcfg.items(section):
307 if not self.hasconfig(section, name):
307 if not self.hasconfig(section, name):
308 self.setconfig(section, name, value, "<tweakdefaults>")
308 self.setconfig(section, name, value, "<tweakdefaults>")
309
309
310 def copy(self):
310 def copy(self):
311 return self.__class__(self)
311 return self.__class__(self)
312
312
313 def resetstate(self):
313 def resetstate(self):
314 """Clear internal state that shouldn't persist across commands"""
314 """Clear internal state that shouldn't persist across commands"""
315 if self._progbar:
315 if self._progbar:
316 self._progbar.resetstate() # reset last-print time of progress bar
316 self._progbar.resetstate() # reset last-print time of progress bar
317 self.httppasswordmgrdb = httppasswordmgrdbproxy()
317 self.httppasswordmgrdb = httppasswordmgrdbproxy()
318
318
319 @contextlib.contextmanager
319 @contextlib.contextmanager
320 def timeblockedsection(self, key):
320 def timeblockedsection(self, key):
321 # this is open-coded below - search for timeblockedsection to find them
321 # this is open-coded below - search for timeblockedsection to find them
322 starttime = util.timer()
322 starttime = util.timer()
323 try:
323 try:
324 yield
324 yield
325 finally:
325 finally:
326 self._blockedtimes[key + '_blocked'] += \
326 self._blockedtimes[key + '_blocked'] += \
327 (util.timer() - starttime) * 1000
327 (util.timer() - starttime) * 1000
328
328
329 def formatter(self, topic, opts):
329 def formatter(self, topic, opts):
330 return formatter.formatter(self, self, topic, opts)
330 return formatter.formatter(self, self, topic, opts)
331
331
332 def _trusted(self, fp, f):
332 def _trusted(self, fp, f):
333 st = util.fstat(fp)
333 st = util.fstat(fp)
334 if util.isowner(st):
334 if util.isowner(st):
335 return True
335 return True
336
336
337 tusers, tgroups = self._trustusers, self._trustgroups
337 tusers, tgroups = self._trustusers, self._trustgroups
338 if '*' in tusers or '*' in tgroups:
338 if '*' in tusers or '*' in tgroups:
339 return True
339 return True
340
340
341 user = util.username(st.st_uid)
341 user = util.username(st.st_uid)
342 group = util.groupname(st.st_gid)
342 group = util.groupname(st.st_gid)
343 if user in tusers or group in tgroups or user == util.username():
343 if user in tusers or group in tgroups or user == util.username():
344 return True
344 return True
345
345
346 if self._reportuntrusted:
346 if self._reportuntrusted:
347 self.warn(_('not trusting file %s from untrusted '
347 self.warn(_('not trusting file %s from untrusted '
348 'user %s, group %s\n') % (f, user, group))
348 'user %s, group %s\n') % (f, user, group))
349 return False
349 return False
350
350
351 def readconfig(self, filename, root=None, trust=False,
351 def readconfig(self, filename, root=None, trust=False,
352 sections=None, remap=None):
352 sections=None, remap=None):
353 try:
353 try:
354 fp = open(filename, u'rb')
354 fp = open(filename, u'rb')
355 except IOError:
355 except IOError:
356 if not sections: # ignore unless we were looking for something
356 if not sections: # ignore unless we were looking for something
357 return
357 return
358 raise
358 raise
359
359
360 cfg = config.config()
360 cfg = config.config()
361 trusted = sections or trust or self._trusted(fp, filename)
361 trusted = sections or trust or self._trusted(fp, filename)
362
362
363 try:
363 try:
364 cfg.read(filename, fp, sections=sections, remap=remap)
364 cfg.read(filename, fp, sections=sections, remap=remap)
365 fp.close()
365 fp.close()
366 except error.ConfigError as inst:
366 except error.ConfigError as inst:
367 if trusted:
367 if trusted:
368 raise
368 raise
369 self.warn(_("ignored: %s\n") % str(inst))
369 self.warn(_("ignored: %s\n") % str(inst))
370
370
371 if self.plain():
371 if self.plain():
372 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
372 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
373 'logtemplate', 'statuscopies', 'style',
373 'logtemplate', 'statuscopies', 'style',
374 'traceback', 'verbose'):
374 'traceback', 'verbose'):
375 if k in cfg['ui']:
375 if k in cfg['ui']:
376 del cfg['ui'][k]
376 del cfg['ui'][k]
377 for k, v in cfg.items('defaults'):
377 for k, v in cfg.items('defaults'):
378 del cfg['defaults'][k]
378 del cfg['defaults'][k]
379 for k, v in cfg.items('commands'):
379 for k, v in cfg.items('commands'):
380 del cfg['commands'][k]
380 del cfg['commands'][k]
381 # Don't remove aliases from the configuration if in the exceptionlist
381 # Don't remove aliases from the configuration if in the exceptionlist
382 if self.plain('alias'):
382 if self.plain('alias'):
383 for k, v in cfg.items('alias'):
383 for k, v in cfg.items('alias'):
384 del cfg['alias'][k]
384 del cfg['alias'][k]
385 if self.plain('revsetalias'):
385 if self.plain('revsetalias'):
386 for k, v in cfg.items('revsetalias'):
386 for k, v in cfg.items('revsetalias'):
387 del cfg['revsetalias'][k]
387 del cfg['revsetalias'][k]
388 if self.plain('templatealias'):
388 if self.plain('templatealias'):
389 for k, v in cfg.items('templatealias'):
389 for k, v in cfg.items('templatealias'):
390 del cfg['templatealias'][k]
390 del cfg['templatealias'][k]
391
391
392 if trusted:
392 if trusted:
393 self._tcfg.update(cfg)
393 self._tcfg.update(cfg)
394 self._tcfg.update(self._ocfg)
394 self._tcfg.update(self._ocfg)
395 self._ucfg.update(cfg)
395 self._ucfg.update(cfg)
396 self._ucfg.update(self._ocfg)
396 self._ucfg.update(self._ocfg)
397
397
398 if root is None:
398 if root is None:
399 root = os.path.expanduser('~')
399 root = os.path.expanduser('~')
400 self.fixconfig(root=root)
400 self.fixconfig(root=root)
401
401
402 def fixconfig(self, root=None, section=None):
402 def fixconfig(self, root=None, section=None):
403 if section in (None, 'paths'):
403 if section in (None, 'paths'):
404 # expand vars and ~
404 # expand vars and ~
405 # translate paths relative to root (or home) into absolute paths
405 # translate paths relative to root (or home) into absolute paths
406 root = root or pycompat.getcwd()
406 root = root or pycompat.getcwd()
407 for c in self._tcfg, self._ucfg, self._ocfg:
407 for c in self._tcfg, self._ucfg, self._ocfg:
408 for n, p in c.items('paths'):
408 for n, p in c.items('paths'):
409 # Ignore sub-options.
409 # Ignore sub-options.
410 if ':' in n:
410 if ':' in n:
411 continue
411 continue
412 if not p:
412 if not p:
413 continue
413 continue
414 if '%%' in p:
414 if '%%' in p:
415 s = self.configsource('paths', n) or 'none'
415 s = self.configsource('paths', n) or 'none'
416 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
416 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
417 % (n, p, s))
417 % (n, p, s))
418 p = p.replace('%%', '%')
418 p = p.replace('%%', '%')
419 p = util.expandpath(p)
419 p = util.expandpath(p)
420 if not util.hasscheme(p) and not os.path.isabs(p):
420 if not util.hasscheme(p) and not os.path.isabs(p):
421 p = os.path.normpath(os.path.join(root, p))
421 p = os.path.normpath(os.path.join(root, p))
422 c.set("paths", n, p)
422 c.set("paths", n, p)
423
423
424 if section in (None, 'ui'):
424 if section in (None, 'ui'):
425 # update ui options
425 # update ui options
426 self.debugflag = self.configbool('ui', 'debug')
426 self.debugflag = self.configbool('ui', 'debug')
427 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
427 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
428 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
428 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
429 if self.verbose and self.quiet:
429 if self.verbose and self.quiet:
430 self.quiet = self.verbose = False
430 self.quiet = self.verbose = False
431 self._reportuntrusted = self.debugflag or self.configbool("ui",
431 self._reportuntrusted = self.debugflag or self.configbool("ui",
432 "report_untrusted")
432 "report_untrusted")
433 self.tracebackflag = self.configbool('ui', 'traceback')
433 self.tracebackflag = self.configbool('ui', 'traceback')
434 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
434 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
435
435
436 if section in (None, 'trusted'):
436 if section in (None, 'trusted'):
437 # update trust information
437 # update trust information
438 self._trustusers.update(self.configlist('trusted', 'users'))
438 self._trustusers.update(self.configlist('trusted', 'users'))
439 self._trustgroups.update(self.configlist('trusted', 'groups'))
439 self._trustgroups.update(self.configlist('trusted', 'groups'))
440
440
441 def backupconfig(self, section, item):
441 def backupconfig(self, section, item):
442 return (self._ocfg.backup(section, item),
442 return (self._ocfg.backup(section, item),
443 self._tcfg.backup(section, item),
443 self._tcfg.backup(section, item),
444 self._ucfg.backup(section, item),)
444 self._ucfg.backup(section, item),)
445 def restoreconfig(self, data):
445 def restoreconfig(self, data):
446 self._ocfg.restore(data[0])
446 self._ocfg.restore(data[0])
447 self._tcfg.restore(data[1])
447 self._tcfg.restore(data[1])
448 self._ucfg.restore(data[2])
448 self._ucfg.restore(data[2])
449
449
450 def setconfig(self, section, name, value, source=''):
450 def setconfig(self, section, name, value, source=''):
451 for cfg in (self._ocfg, self._tcfg, self._ucfg):
451 for cfg in (self._ocfg, self._tcfg, self._ucfg):
452 cfg.set(section, name, value, source)
452 cfg.set(section, name, value, source)
453 self.fixconfig(section=section)
453 self.fixconfig(section=section)
454 self._maybetweakdefaults()
454 self._maybetweakdefaults()
455
455
456 def _data(self, untrusted):
456 def _data(self, untrusted):
457 return untrusted and self._ucfg or self._tcfg
457 return untrusted and self._ucfg or self._tcfg
458
458
459 def configsource(self, section, name, untrusted=False):
459 def configsource(self, section, name, untrusted=False):
460 return self._data(untrusted).source(section, name)
460 return self._data(untrusted).source(section, name)
461
461
462 def config(self, section, name, default=_unset, untrusted=False):
462 def config(self, section, name, default=_unset, untrusted=False):
463 """return the plain string version of a config"""
463 """return the plain string version of a config"""
464 value = self._config(section, name, default=default,
464 value = self._config(section, name, default=default,
465 untrusted=untrusted)
465 untrusted=untrusted)
466 if value is _unset:
466 if value is _unset:
467 return None
467 return None
468 return value
468 return value
469
469
470 def _config(self, section, name, default=_unset, untrusted=False):
470 def _config(self, section, name, default=_unset, untrusted=False):
471 value = itemdefault = default
471 value = itemdefault = default
472 item = self._knownconfig.get(section, {}).get(name)
472 item = self._knownconfig.get(section, {}).get(name)
473 alternates = [(section, name)]
473 alternates = [(section, name)]
474
474
475 if item is not None:
475 if item is not None:
476 alternates.extend(item.alias)
476 alternates.extend(item.alias)
477 if callable(item.default):
477 if callable(item.default):
478 itemdefault = item.default()
478 itemdefault = item.default()
479 else:
479 else:
480 itemdefault = item.default
480 itemdefault = item.default
481 else:
481 else:
482 msg = ("accessing unregistered config item: '%s.%s'")
482 msg = ("accessing unregistered config item: '%s.%s'")
483 msg %= (section, name)
483 msg %= (section, name)
484 self.develwarn(msg, 2, 'warn-config-unknown')
484 self.develwarn(msg, 2, 'warn-config-unknown')
485
485
486 if default is _unset:
486 if default is _unset:
487 if item is None:
487 if item is None:
488 value = default
488 value = default
489 elif item.default is configitems.dynamicdefault:
489 elif item.default is configitems.dynamicdefault:
490 value = None
490 value = None
491 msg = "config item requires an explicit default value: '%s.%s'"
491 msg = "config item requires an explicit default value: '%s.%s'"
492 msg %= (section, name)
492 msg %= (section, name)
493 self.develwarn(msg, 2, 'warn-config-default')
493 self.develwarn(msg, 2, 'warn-config-default')
494 else:
494 else:
495 value = itemdefault
495 value = itemdefault
496 elif (item is not None
496 elif (item is not None
497 and item.default is not configitems.dynamicdefault
497 and item.default is not configitems.dynamicdefault
498 and default != itemdefault):
498 and default != itemdefault):
499 msg = ("specifying a mismatched default value for a registered "
499 msg = ("specifying a mismatched default value for a registered "
500 "config item: '%s.%s' '%s'")
500 "config item: '%s.%s' '%s'")
501 msg %= (section, name, default)
501 msg %= (section, name, default)
502 self.develwarn(msg, 2, 'warn-config-default')
502 self.develwarn(msg, 2, 'warn-config-default')
503
503
504 for s, n in alternates:
504 for s, n in alternates:
505 candidate = self._data(untrusted).get(s, n, None)
505 candidate = self._data(untrusted).get(s, n, None)
506 if candidate is not None:
506 if candidate is not None:
507 value = candidate
507 value = candidate
508 section = s
508 section = s
509 name = n
509 name = n
510 break
510 break
511
511
512 if self.debugflag and not untrusted and self._reportuntrusted:
512 if self.debugflag and not untrusted and self._reportuntrusted:
513 for s, n in alternates:
513 for s, n in alternates:
514 uvalue = self._ucfg.get(s, n)
514 uvalue = self._ucfg.get(s, n)
515 if uvalue is not None and uvalue != value:
515 if uvalue is not None and uvalue != value:
516 self.debug("ignoring untrusted configuration option "
516 self.debug("ignoring untrusted configuration option "
517 "%s.%s = %s\n" % (s, n, uvalue))
517 "%s.%s = %s\n" % (s, n, uvalue))
518 return value
518 return value
519
519
520 def configsuboptions(self, section, name, default=_unset, untrusted=False):
520 def configsuboptions(self, section, name, default=_unset, untrusted=False):
521 """Get a config option and all sub-options.
521 """Get a config option and all sub-options.
522
522
523 Some config options have sub-options that are declared with the
523 Some config options have sub-options that are declared with the
524 format "key:opt = value". This method is used to return the main
524 format "key:opt = value". This method is used to return the main
525 option and all its declared sub-options.
525 option and all its declared sub-options.
526
526
527 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
527 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
528 is a dict of defined sub-options where keys and values are strings.
528 is a dict of defined sub-options where keys and values are strings.
529 """
529 """
530 main = self.config(section, name, default, untrusted=untrusted)
530 main = self.config(section, name, default, untrusted=untrusted)
531 data = self._data(untrusted)
531 data = self._data(untrusted)
532 sub = {}
532 sub = {}
533 prefix = '%s:' % name
533 prefix = '%s:' % name
534 for k, v in data.items(section):
534 for k, v in data.items(section):
535 if k.startswith(prefix):
535 if k.startswith(prefix):
536 sub[k[len(prefix):]] = v
536 sub[k[len(prefix):]] = v
537
537
538 if self.debugflag and not untrusted and self._reportuntrusted:
538 if self.debugflag and not untrusted and self._reportuntrusted:
539 for k, v in sub.items():
539 for k, v in sub.items():
540 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
540 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
541 if uvalue is not None and uvalue != v:
541 if uvalue is not None and uvalue != v:
542 self.debug('ignoring untrusted configuration option '
542 self.debug('ignoring untrusted configuration option '
543 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
543 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
544
544
545 return main, sub
545 return main, sub
546
546
547 def configpath(self, section, name, default=_unset, untrusted=False):
547 def configpath(self, section, name, default=_unset, untrusted=False):
548 'get a path config item, expanded relative to repo root or config file'
548 'get a path config item, expanded relative to repo root or config file'
549 v = self.config(section, name, default, untrusted)
549 v = self.config(section, name, default, untrusted)
550 if v is None:
550 if v is None:
551 return None
551 return None
552 if not os.path.isabs(v) or "://" not in v:
552 if not os.path.isabs(v) or "://" not in v:
553 src = self.configsource(section, name, untrusted)
553 src = self.configsource(section, name, untrusted)
554 if ':' in src:
554 if ':' in src:
555 base = os.path.dirname(src.rsplit(':')[0])
555 base = os.path.dirname(src.rsplit(':')[0])
556 v = os.path.join(base, os.path.expanduser(v))
556 v = os.path.join(base, os.path.expanduser(v))
557 return v
557 return v
558
558
559 def configbool(self, section, name, default=_unset, untrusted=False):
559 def configbool(self, section, name, default=_unset, untrusted=False):
560 """parse a configuration element as a boolean
560 """parse a configuration element as a boolean
561
561
562 >>> u = ui(); s = b'foo'
562 >>> u = ui(); s = b'foo'
563 >>> u.setconfig(s, b'true', b'yes')
563 >>> u.setconfig(s, b'true', b'yes')
564 >>> u.configbool(s, b'true')
564 >>> u.configbool(s, b'true')
565 True
565 True
566 >>> u.setconfig(s, b'false', b'no')
566 >>> u.setconfig(s, b'false', b'no')
567 >>> u.configbool(s, b'false')
567 >>> u.configbool(s, b'false')
568 False
568 False
569 >>> u.configbool(s, b'unknown')
569 >>> u.configbool(s, b'unknown')
570 False
570 False
571 >>> u.configbool(s, b'unknown', True)
571 >>> u.configbool(s, b'unknown', True)
572 True
572 True
573 >>> u.setconfig(s, b'invalid', b'somevalue')
573 >>> u.setconfig(s, b'invalid', b'somevalue')
574 >>> u.configbool(s, b'invalid')
574 >>> u.configbool(s, b'invalid')
575 Traceback (most recent call last):
575 Traceback (most recent call last):
576 ...
576 ...
577 ConfigError: foo.invalid is not a boolean ('somevalue')
577 ConfigError: foo.invalid is not a boolean ('somevalue')
578 """
578 """
579
579
580 v = self._config(section, name, default, untrusted=untrusted)
580 v = self._config(section, name, default, untrusted=untrusted)
581 if v is None:
581 if v is None:
582 return v
582 return v
583 if v is _unset:
583 if v is _unset:
584 if default is _unset:
584 if default is _unset:
585 return False
585 return False
586 return default
586 return default
587 if isinstance(v, bool):
587 if isinstance(v, bool):
588 return v
588 return v
589 b = util.parsebool(v)
589 b = util.parsebool(v)
590 if b is None:
590 if b is None:
591 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
591 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
592 % (section, name, v))
592 % (section, name, v))
593 return b
593 return b
594
594
595 def configwith(self, convert, section, name, default=_unset,
595 def configwith(self, convert, section, name, default=_unset,
596 desc=None, untrusted=False):
596 desc=None, untrusted=False):
597 """parse a configuration element with a conversion function
597 """parse a configuration element with a conversion function
598
598
599 >>> u = ui(); s = b'foo'
599 >>> u = ui(); s = b'foo'
600 >>> u.setconfig(s, b'float1', b'42')
600 >>> u.setconfig(s, b'float1', b'42')
601 >>> u.configwith(float, s, b'float1')
601 >>> u.configwith(float, s, b'float1')
602 42.0
602 42.0
603 >>> u.setconfig(s, b'float2', b'-4.25')
603 >>> u.setconfig(s, b'float2', b'-4.25')
604 >>> u.configwith(float, s, b'float2')
604 >>> u.configwith(float, s, b'float2')
605 -4.25
605 -4.25
606 >>> u.configwith(float, s, b'unknown', 7)
606 >>> u.configwith(float, s, b'unknown', 7)
607 7.0
607 7.0
608 >>> u.setconfig(s, b'invalid', b'somevalue')
608 >>> u.setconfig(s, b'invalid', b'somevalue')
609 >>> u.configwith(float, s, b'invalid')
609 >>> u.configwith(float, s, b'invalid')
610 Traceback (most recent call last):
610 Traceback (most recent call last):
611 ...
611 ...
612 ConfigError: foo.invalid is not a valid float ('somevalue')
612 ConfigError: foo.invalid is not a valid float ('somevalue')
613 >>> u.configwith(float, s, b'invalid', desc=b'womble')
613 >>> u.configwith(float, s, b'invalid', desc=b'womble')
614 Traceback (most recent call last):
614 Traceback (most recent call last):
615 ...
615 ...
616 ConfigError: foo.invalid is not a valid womble ('somevalue')
616 ConfigError: foo.invalid is not a valid womble ('somevalue')
617 """
617 """
618
618
619 v = self.config(section, name, default, untrusted)
619 v = self.config(section, name, default, untrusted)
620 if v is None:
620 if v is None:
621 return v # do not attempt to convert None
621 return v # do not attempt to convert None
622 try:
622 try:
623 return convert(v)
623 return convert(v)
624 except (ValueError, error.ParseError):
624 except (ValueError, error.ParseError):
625 if desc is None:
625 if desc is None:
626 desc = pycompat.sysbytes(convert.__name__)
626 desc = pycompat.sysbytes(convert.__name__)
627 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
627 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
628 % (section, name, desc, v))
628 % (section, name, desc, v))
629
629
630 def configint(self, section, name, default=_unset, untrusted=False):
630 def configint(self, section, name, default=_unset, untrusted=False):
631 """parse a configuration element as an integer
631 """parse a configuration element as an integer
632
632
633 >>> u = ui(); s = b'foo'
633 >>> u = ui(); s = b'foo'
634 >>> u.setconfig(s, b'int1', b'42')
634 >>> u.setconfig(s, b'int1', b'42')
635 >>> u.configint(s, b'int1')
635 >>> u.configint(s, b'int1')
636 42
636 42
637 >>> u.setconfig(s, b'int2', b'-42')
637 >>> u.setconfig(s, b'int2', b'-42')
638 >>> u.configint(s, b'int2')
638 >>> u.configint(s, b'int2')
639 -42
639 -42
640 >>> u.configint(s, b'unknown', 7)
640 >>> u.configint(s, b'unknown', 7)
641 7
641 7
642 >>> u.setconfig(s, b'invalid', b'somevalue')
642 >>> u.setconfig(s, b'invalid', b'somevalue')
643 >>> u.configint(s, b'invalid')
643 >>> u.configint(s, b'invalid')
644 Traceback (most recent call last):
644 Traceback (most recent call last):
645 ...
645 ...
646 ConfigError: foo.invalid is not a valid integer ('somevalue')
646 ConfigError: foo.invalid is not a valid integer ('somevalue')
647 """
647 """
648
648
649 return self.configwith(int, section, name, default, 'integer',
649 return self.configwith(int, section, name, default, 'integer',
650 untrusted)
650 untrusted)
651
651
652 def configbytes(self, section, name, default=_unset, untrusted=False):
652 def configbytes(self, section, name, default=_unset, untrusted=False):
653 """parse a configuration element as a quantity in bytes
653 """parse a configuration element as a quantity in bytes
654
654
655 Units can be specified as b (bytes), k or kb (kilobytes), m or
655 Units can be specified as b (bytes), k or kb (kilobytes), m or
656 mb (megabytes), g or gb (gigabytes).
656 mb (megabytes), g or gb (gigabytes).
657
657
658 >>> u = ui(); s = b'foo'
658 >>> u = ui(); s = b'foo'
659 >>> u.setconfig(s, b'val1', b'42')
659 >>> u.setconfig(s, b'val1', b'42')
660 >>> u.configbytes(s, b'val1')
660 >>> u.configbytes(s, b'val1')
661 42
661 42
662 >>> u.setconfig(s, b'val2', b'42.5 kb')
662 >>> u.setconfig(s, b'val2', b'42.5 kb')
663 >>> u.configbytes(s, b'val2')
663 >>> u.configbytes(s, b'val2')
664 43520
664 43520
665 >>> u.configbytes(s, b'unknown', b'7 MB')
665 >>> u.configbytes(s, b'unknown', b'7 MB')
666 7340032
666 7340032
667 >>> u.setconfig(s, b'invalid', b'somevalue')
667 >>> u.setconfig(s, b'invalid', b'somevalue')
668 >>> u.configbytes(s, b'invalid')
668 >>> u.configbytes(s, b'invalid')
669 Traceback (most recent call last):
669 Traceback (most recent call last):
670 ...
670 ...
671 ConfigError: foo.invalid is not a byte quantity ('somevalue')
671 ConfigError: foo.invalid is not a byte quantity ('somevalue')
672 """
672 """
673
673
674 value = self._config(section, name, default, untrusted)
674 value = self._config(section, name, default, untrusted)
675 if value is _unset:
675 if value is _unset:
676 if default is _unset:
676 if default is _unset:
677 default = 0
677 default = 0
678 value = default
678 value = default
679 if not isinstance(value, bytes):
679 if not isinstance(value, bytes):
680 return value
680 return value
681 try:
681 try:
682 return util.sizetoint(value)
682 return util.sizetoint(value)
683 except error.ParseError:
683 except error.ParseError:
684 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
684 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
685 % (section, name, value))
685 % (section, name, value))
686
686
687 def configlist(self, section, name, default=_unset, untrusted=False):
687 def configlist(self, section, name, default=_unset, untrusted=False):
688 """parse a configuration element as a list of comma/space separated
688 """parse a configuration element as a list of comma/space separated
689 strings
689 strings
690
690
691 >>> u = ui(); s = b'foo'
691 >>> u = ui(); s = b'foo'
692 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
692 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
693 >>> u.configlist(s, b'list1')
693 >>> u.configlist(s, b'list1')
694 ['this', 'is', 'a small', 'test']
694 ['this', 'is', 'a small', 'test']
695 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
695 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
696 >>> u.configlist(s, b'list2')
696 >>> u.configlist(s, b'list2')
697 ['this', 'is', 'a small', 'test']
697 ['this', 'is', 'a small', 'test']
698 """
698 """
699 # default is not always a list
699 # default is not always a list
700 v = self.configwith(config.parselist, section, name, default,
700 v = self.configwith(config.parselist, section, name, default,
701 'list', untrusted)
701 'list', untrusted)
702 if isinstance(v, bytes):
702 if isinstance(v, bytes):
703 return config.parselist(v)
703 return config.parselist(v)
704 elif v is None:
704 elif v is None:
705 return []
705 return []
706 return v
706 return v
707
707
708 def configdate(self, section, name, default=_unset, untrusted=False):
708 def configdate(self, section, name, default=_unset, untrusted=False):
709 """parse a configuration element as a tuple of ints
709 """parse a configuration element as a tuple of ints
710
710
711 >>> u = ui(); s = b'foo'
711 >>> u = ui(); s = b'foo'
712 >>> u.setconfig(s, b'date', b'0 0')
712 >>> u.setconfig(s, b'date', b'0 0')
713 >>> u.configdate(s, b'date')
713 >>> u.configdate(s, b'date')
714 (0, 0)
714 (0, 0)
715 """
715 """
716 if self.config(section, name, default, untrusted):
716 if self.config(section, name, default, untrusted):
717 return self.configwith(util.parsedate, section, name, default,
717 return self.configwith(util.parsedate, section, name, default,
718 'date', untrusted)
718 'date', untrusted)
719 if default is _unset:
719 if default is _unset:
720 return None
720 return None
721 return default
721 return default
722
722
723 def hasconfig(self, section, name, untrusted=False):
723 def hasconfig(self, section, name, untrusted=False):
724 return self._data(untrusted).hasitem(section, name)
724 return self._data(untrusted).hasitem(section, name)
725
725
726 def has_section(self, section, untrusted=False):
726 def has_section(self, section, untrusted=False):
727 '''tell whether section exists in config.'''
727 '''tell whether section exists in config.'''
728 return section in self._data(untrusted)
728 return section in self._data(untrusted)
729
729
730 def configitems(self, section, untrusted=False, ignoresub=False):
730 def configitems(self, section, untrusted=False, ignoresub=False):
731 items = self._data(untrusted).items(section)
731 items = self._data(untrusted).items(section)
732 if ignoresub:
732 if ignoresub:
733 newitems = {}
733 newitems = {}
734 for k, v in items:
734 for k, v in items:
735 if ':' not in k:
735 if ':' not in k:
736 newitems[k] = v
736 newitems[k] = v
737 items = newitems.items()
737 items = newitems.items()
738 if self.debugflag and not untrusted and self._reportuntrusted:
738 if self.debugflag and not untrusted and self._reportuntrusted:
739 for k, v in self._ucfg.items(section):
739 for k, v in self._ucfg.items(section):
740 if self._tcfg.get(section, k) != v:
740 if self._tcfg.get(section, k) != v:
741 self.debug("ignoring untrusted configuration option "
741 self.debug("ignoring untrusted configuration option "
742 "%s.%s = %s\n" % (section, k, v))
742 "%s.%s = %s\n" % (section, k, v))
743 return items
743 return items
744
744
745 def walkconfig(self, untrusted=False):
745 def walkconfig(self, untrusted=False):
746 cfg = self._data(untrusted)
746 cfg = self._data(untrusted)
747 for section in cfg.sections():
747 for section in cfg.sections():
748 for name, value in self.configitems(section, untrusted):
748 for name, value in self.configitems(section, untrusted):
749 yield section, name, value
749 yield section, name, value
750
750
751 def plain(self, feature=None):
751 def plain(self, feature=None):
752 '''is plain mode active?
752 '''is plain mode active?
753
753
754 Plain mode means that all configuration variables which affect
754 Plain mode means that all configuration variables which affect
755 the behavior and output of Mercurial should be
755 the behavior and output of Mercurial should be
756 ignored. Additionally, the output should be stable,
756 ignored. Additionally, the output should be stable,
757 reproducible and suitable for use in scripts or applications.
757 reproducible and suitable for use in scripts or applications.
758
758
759 The only way to trigger plain mode is by setting either the
759 The only way to trigger plain mode is by setting either the
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
760 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
761
761
762 The return value can either be
762 The return value can either be
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
763 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
764 - False if feature is disabled by default and not included in HGPLAIN
764 - False if feature is disabled by default and not included in HGPLAIN
765 - True otherwise
765 - True otherwise
766 '''
766 '''
767 if ('HGPLAIN' not in encoding.environ and
767 if ('HGPLAIN' not in encoding.environ and
768 'HGPLAINEXCEPT' not in encoding.environ):
768 'HGPLAINEXCEPT' not in encoding.environ):
769 return False
769 return False
770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
770 exceptions = encoding.environ.get('HGPLAINEXCEPT',
771 '').strip().split(',')
771 '').strip().split(',')
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
772 # TODO: add support for HGPLAIN=+feature,-feature syntax
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
773 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
774 exceptions.append('strictflags')
774 exceptions.append('strictflags')
775 if feature and exceptions:
775 if feature and exceptions:
776 return feature not in exceptions
776 return feature not in exceptions
777 return True
777 return True
778
778
779 def username(self, acceptempty=False):
779 def username(self, acceptempty=False):
780 """Return default username to be used in commits.
780 """Return default username to be used in commits.
781
781
782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
782 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
783 and stop searching if one of these is set.
783 and stop searching if one of these is set.
784 If not found and acceptempty is True, returns None.
784 If not found and acceptempty is True, returns None.
785 If not found and ui.askusername is True, ask the user, else use
785 If not found and ui.askusername is True, ask the user, else use
786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
786 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
787 If no username could be found, raise an Abort error.
787 If no username could be found, raise an Abort error.
788 """
788 """
789 user = encoding.environ.get("HGUSER")
789 user = encoding.environ.get("HGUSER")
790 if user is None:
790 if user is None:
791 user = self.config("ui", "username")
791 user = self.config("ui", "username")
792 if user is not None:
792 if user is not None:
793 user = os.path.expandvars(user)
793 user = os.path.expandvars(user)
794 if user is None:
794 if user is None:
795 user = encoding.environ.get("EMAIL")
795 user = encoding.environ.get("EMAIL")
796 if user is None and acceptempty:
796 if user is None and acceptempty:
797 return user
797 return user
798 if user is None and self.configbool("ui", "askusername"):
798 if user is None and self.configbool("ui", "askusername"):
799 user = self.prompt(_("enter a commit username:"), default=None)
799 user = self.prompt(_("enter a commit username:"), default=None)
800 if user is None and not self.interactive():
800 if user is None and not self.interactive():
801 try:
801 try:
802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
802 user = '%s@%s' % (util.getuser(), socket.getfqdn())
803 self.warn(_("no username found, using '%s' instead\n") % user)
803 self.warn(_("no username found, using '%s' instead\n") % user)
804 except KeyError:
804 except KeyError:
805 pass
805 pass
806 if not user:
806 if not user:
807 raise error.Abort(_('no username supplied'),
807 raise error.Abort(_('no username supplied'),
808 hint=_("use 'hg config --edit' "
808 hint=_("use 'hg config --edit' "
809 'to set your username'))
809 'to set your username'))
810 if "\n" in user:
810 if "\n" in user:
811 raise error.Abort(_("username %s contains a newline\n")
811 raise error.Abort(_("username %s contains a newline\n")
812 % repr(user))
812 % repr(user))
813 return user
813 return user
814
814
815 def shortuser(self, user):
815 def shortuser(self, user):
816 """Return a short representation of a user name or email address."""
816 """Return a short representation of a user name or email address."""
817 if not self.verbose:
817 if not self.verbose:
818 user = util.shortuser(user)
818 user = util.shortuser(user)
819 return user
819 return user
820
820
821 def expandpath(self, loc, default=None):
821 def expandpath(self, loc, default=None):
822 """Return repository location relative to cwd or from [paths]"""
822 """Return repository location relative to cwd or from [paths]"""
823 try:
823 try:
824 p = self.paths.getpath(loc)
824 p = self.paths.getpath(loc)
825 if p:
825 if p:
826 return p.rawloc
826 return p.rawloc
827 except error.RepoError:
827 except error.RepoError:
828 pass
828 pass
829
829
830 if default:
830 if default:
831 try:
831 try:
832 p = self.paths.getpath(default)
832 p = self.paths.getpath(default)
833 if p:
833 if p:
834 return p.rawloc
834 return p.rawloc
835 except error.RepoError:
835 except error.RepoError:
836 pass
836 pass
837
837
838 return loc
838 return loc
839
839
840 @util.propertycache
840 @util.propertycache
841 def paths(self):
841 def paths(self):
842 return paths(self)
842 return paths(self)
843
843
844 def pushbuffer(self, error=False, subproc=False, labeled=False):
844 def pushbuffer(self, error=False, subproc=False, labeled=False):
845 """install a buffer to capture standard output of the ui object
845 """install a buffer to capture standard output of the ui object
846
846
847 If error is True, the error output will be captured too.
847 If error is True, the error output will be captured too.
848
848
849 If subproc is True, output from subprocesses (typically hooks) will be
849 If subproc is True, output from subprocesses (typically hooks) will be
850 captured too.
850 captured too.
851
851
852 If labeled is True, any labels associated with buffered
852 If labeled is True, any labels associated with buffered
853 output will be handled. By default, this has no effect
853 output will be handled. By default, this has no effect
854 on the output returned, but extensions and GUI tools may
854 on the output returned, but extensions and GUI tools may
855 handle this argument and returned styled output. If output
855 handle this argument and returned styled output. If output
856 is being buffered so it can be captured and parsed or
856 is being buffered so it can be captured and parsed or
857 processed, labeled should not be set to True.
857 processed, labeled should not be set to True.
858 """
858 """
859 self._buffers.append([])
859 self._buffers.append([])
860 self._bufferstates.append((error, subproc, labeled))
860 self._bufferstates.append((error, subproc, labeled))
861 self._bufferapplylabels = labeled
861 self._bufferapplylabels = labeled
862
862
863 def popbuffer(self):
863 def popbuffer(self):
864 '''pop the last buffer and return the buffered output'''
864 '''pop the last buffer and return the buffered output'''
865 self._bufferstates.pop()
865 self._bufferstates.pop()
866 if self._bufferstates:
866 if self._bufferstates:
867 self._bufferapplylabels = self._bufferstates[-1][2]
867 self._bufferapplylabels = self._bufferstates[-1][2]
868 else:
868 else:
869 self._bufferapplylabels = None
869 self._bufferapplylabels = None
870
870
871 return "".join(self._buffers.pop())
871 return "".join(self._buffers.pop())
872
872
873 def canwritewithoutlabels(self):
874 '''check if write skips the label'''
875 if self._buffers and not self._bufferapplylabels:
876 return True
877 return self._colormode is None
878
879 def canbatchlabeledwrites(self):
880 '''check if write calls with labels are batchable'''
881 # Windows color printing is special, see ``write``.
882 return self._colormode != 'win32'
883
873 def write(self, *args, **opts):
884 def write(self, *args, **opts):
874 '''write args to output
885 '''write args to output
875
886
876 By default, this method simply writes to the buffer or stdout.
887 By default, this method simply writes to the buffer or stdout.
877 Color mode can be set on the UI class to have the output decorated
888 Color mode can be set on the UI class to have the output decorated
878 with color modifier before being written to stdout.
889 with color modifier before being written to stdout.
879
890
880 The color used is controlled by an optional keyword argument, "label".
891 The color used is controlled by an optional keyword argument, "label".
881 This should be a string containing label names separated by space.
892 This should be a string containing label names separated by space.
882 Label names take the form of "topic.type". For example, ui.debug()
893 Label names take the form of "topic.type". For example, ui.debug()
883 issues a label of "ui.debug".
894 issues a label of "ui.debug".
884
895
885 When labeling output for a specific command, a label of
896 When labeling output for a specific command, a label of
886 "cmdname.type" is recommended. For example, status issues
897 "cmdname.type" is recommended. For example, status issues
887 a label of "status.modified" for modified files.
898 a label of "status.modified" for modified files.
888 '''
899 '''
889 if self._buffers:
900 if self._buffers:
890 if self._bufferapplylabels:
901 if self._bufferapplylabels:
891 label = opts.get(r'label', '')
902 label = opts.get(r'label', '')
892 self._buffers[-1].extend(self.label(a, label) for a in args)
903 self._buffers[-1].extend(self.label(a, label) for a in args)
893 else:
904 else:
894 self._buffers[-1].extend(args)
905 self._buffers[-1].extend(args)
895 else:
906 else:
896 self._writenobuf(*args, **opts)
907 self._writenobuf(*args, **opts)
897
908
898 def _writenobuf(self, *args, **opts):
909 def _writenobuf(self, *args, **opts):
899 if self._colormode == 'win32':
910 if self._colormode == 'win32':
900 # windows color printing is its own can of crab, defer to
911 # windows color printing is its own can of crab, defer to
901 # the color module and that is it.
912 # the color module and that is it.
902 color.win32print(self, self._write, *args, **opts)
913 color.win32print(self, self._write, *args, **opts)
903 else:
914 else:
904 msgs = args
915 msgs = args
905 if self._colormode is not None:
916 if self._colormode is not None:
906 label = opts.get(r'label', '')
917 label = opts.get(r'label', '')
907 msgs = [self.label(a, label) for a in args]
918 msgs = [self.label(a, label) for a in args]
908 self._write(*msgs, **opts)
919 self._write(*msgs, **opts)
909
920
910 def _write(self, *msgs, **opts):
921 def _write(self, *msgs, **opts):
911 self._progclear()
922 self._progclear()
912 # opencode timeblockedsection because this is a critical path
923 # opencode timeblockedsection because this is a critical path
913 starttime = util.timer()
924 starttime = util.timer()
914 try:
925 try:
915 self.fout.write(''.join(msgs))
926 self.fout.write(''.join(msgs))
916 except IOError as err:
927 except IOError as err:
917 raise error.StdioError(err)
928 raise error.StdioError(err)
918 finally:
929 finally:
919 self._blockedtimes['stdio_blocked'] += \
930 self._blockedtimes['stdio_blocked'] += \
920 (util.timer() - starttime) * 1000
931 (util.timer() - starttime) * 1000
921
932
922 def write_err(self, *args, **opts):
933 def write_err(self, *args, **opts):
923 self._progclear()
934 self._progclear()
924 if self._bufferstates and self._bufferstates[-1][0]:
935 if self._bufferstates and self._bufferstates[-1][0]:
925 self.write(*args, **opts)
936 self.write(*args, **opts)
926 elif self._colormode == 'win32':
937 elif self._colormode == 'win32':
927 # windows color printing is its own can of crab, defer to
938 # windows color printing is its own can of crab, defer to
928 # the color module and that is it.
939 # the color module and that is it.
929 color.win32print(self, self._write_err, *args, **opts)
940 color.win32print(self, self._write_err, *args, **opts)
930 else:
941 else:
931 msgs = args
942 msgs = args
932 if self._colormode is not None:
943 if self._colormode is not None:
933 label = opts.get(r'label', '')
944 label = opts.get(r'label', '')
934 msgs = [self.label(a, label) for a in args]
945 msgs = [self.label(a, label) for a in args]
935 self._write_err(*msgs, **opts)
946 self._write_err(*msgs, **opts)
936
947
937 def _write_err(self, *msgs, **opts):
948 def _write_err(self, *msgs, **opts):
938 try:
949 try:
939 with self.timeblockedsection('stdio'):
950 with self.timeblockedsection('stdio'):
940 if not getattr(self.fout, 'closed', False):
951 if not getattr(self.fout, 'closed', False):
941 self.fout.flush()
952 self.fout.flush()
942 for a in msgs:
953 for a in msgs:
943 self.ferr.write(a)
954 self.ferr.write(a)
944 # stderr may be buffered under win32 when redirected to files,
955 # stderr may be buffered under win32 when redirected to files,
945 # including stdout.
956 # including stdout.
946 if not getattr(self.ferr, 'closed', False):
957 if not getattr(self.ferr, 'closed', False):
947 self.ferr.flush()
958 self.ferr.flush()
948 except IOError as inst:
959 except IOError as inst:
949 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
960 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
950 raise error.StdioError(inst)
961 raise error.StdioError(inst)
951
962
952 def flush(self):
963 def flush(self):
953 # opencode timeblockedsection because this is a critical path
964 # opencode timeblockedsection because this is a critical path
954 starttime = util.timer()
965 starttime = util.timer()
955 try:
966 try:
956 try:
967 try:
957 self.fout.flush()
968 self.fout.flush()
958 except IOError as err:
969 except IOError as err:
959 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
970 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
960 raise error.StdioError(err)
971 raise error.StdioError(err)
961 finally:
972 finally:
962 try:
973 try:
963 self.ferr.flush()
974 self.ferr.flush()
964 except IOError as err:
975 except IOError as err:
965 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
976 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
966 raise error.StdioError(err)
977 raise error.StdioError(err)
967 finally:
978 finally:
968 self._blockedtimes['stdio_blocked'] += \
979 self._blockedtimes['stdio_blocked'] += \
969 (util.timer() - starttime) * 1000
980 (util.timer() - starttime) * 1000
970
981
971 def _isatty(self, fh):
982 def _isatty(self, fh):
972 if self.configbool('ui', 'nontty'):
983 if self.configbool('ui', 'nontty'):
973 return False
984 return False
974 return util.isatty(fh)
985 return util.isatty(fh)
975
986
976 def disablepager(self):
987 def disablepager(self):
977 self._disablepager = True
988 self._disablepager = True
978
989
979 def pager(self, command):
990 def pager(self, command):
980 """Start a pager for subsequent command output.
991 """Start a pager for subsequent command output.
981
992
982 Commands which produce a long stream of output should call
993 Commands which produce a long stream of output should call
983 this function to activate the user's preferred pagination
994 this function to activate the user's preferred pagination
984 mechanism (which may be no pager). Calling this function
995 mechanism (which may be no pager). Calling this function
985 precludes any future use of interactive functionality, such as
996 precludes any future use of interactive functionality, such as
986 prompting the user or activating curses.
997 prompting the user or activating curses.
987
998
988 Args:
999 Args:
989 command: The full, non-aliased name of the command. That is, "log"
1000 command: The full, non-aliased name of the command. That is, "log"
990 not "history, "summary" not "summ", etc.
1001 not "history, "summary" not "summ", etc.
991 """
1002 """
992 if (self._disablepager
1003 if (self._disablepager
993 or self.pageractive):
1004 or self.pageractive):
994 # how pager should do is already determined
1005 # how pager should do is already determined
995 return
1006 return
996
1007
997 if not command.startswith('internal-always-') and (
1008 if not command.startswith('internal-always-') and (
998 # explicit --pager=on (= 'internal-always-' prefix) should
1009 # explicit --pager=on (= 'internal-always-' prefix) should
999 # take precedence over disabling factors below
1010 # take precedence over disabling factors below
1000 command in self.configlist('pager', 'ignore')
1011 command in self.configlist('pager', 'ignore')
1001 or not self.configbool('ui', 'paginate')
1012 or not self.configbool('ui', 'paginate')
1002 or not self.configbool('pager', 'attend-' + command, True)
1013 or not self.configbool('pager', 'attend-' + command, True)
1003 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1014 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1004 # formatted() will need some adjustment.
1015 # formatted() will need some adjustment.
1005 or not self.formatted()
1016 or not self.formatted()
1006 or self.plain()
1017 or self.plain()
1007 or self._buffers
1018 or self._buffers
1008 # TODO: expose debugger-enabled on the UI object
1019 # TODO: expose debugger-enabled on the UI object
1009 or '--debugger' in pycompat.sysargv):
1020 or '--debugger' in pycompat.sysargv):
1010 # We only want to paginate if the ui appears to be
1021 # We only want to paginate if the ui appears to be
1011 # interactive, the user didn't say HGPLAIN or
1022 # interactive, the user didn't say HGPLAIN or
1012 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1023 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1013 return
1024 return
1014
1025
1015 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1026 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1016 if not pagercmd:
1027 if not pagercmd:
1017 return
1028 return
1018
1029
1019 pagerenv = {}
1030 pagerenv = {}
1020 for name, value in rcutil.defaultpagerenv().items():
1031 for name, value in rcutil.defaultpagerenv().items():
1021 if name not in encoding.environ:
1032 if name not in encoding.environ:
1022 pagerenv[name] = value
1033 pagerenv[name] = value
1023
1034
1024 self.debug('starting pager for command %r\n' % command)
1035 self.debug('starting pager for command %r\n' % command)
1025 self.flush()
1036 self.flush()
1026
1037
1027 wasformatted = self.formatted()
1038 wasformatted = self.formatted()
1028 if util.safehasattr(signal, "SIGPIPE"):
1039 if util.safehasattr(signal, "SIGPIPE"):
1029 signal.signal(signal.SIGPIPE, _catchterm)
1040 signal.signal(signal.SIGPIPE, _catchterm)
1030 if self._runpager(pagercmd, pagerenv):
1041 if self._runpager(pagercmd, pagerenv):
1031 self.pageractive = True
1042 self.pageractive = True
1032 # Preserve the formatted-ness of the UI. This is important
1043 # Preserve the formatted-ness of the UI. This is important
1033 # because we mess with stdout, which might confuse
1044 # because we mess with stdout, which might confuse
1034 # auto-detection of things being formatted.
1045 # auto-detection of things being formatted.
1035 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1046 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1036 self.setconfig('ui', 'interactive', False, 'pager')
1047 self.setconfig('ui', 'interactive', False, 'pager')
1037
1048
1038 # If pagermode differs from color.mode, reconfigure color now that
1049 # If pagermode differs from color.mode, reconfigure color now that
1039 # pageractive is set.
1050 # pageractive is set.
1040 cm = self._colormode
1051 cm = self._colormode
1041 if cm != self.config('color', 'pagermode', cm):
1052 if cm != self.config('color', 'pagermode', cm):
1042 color.setup(self)
1053 color.setup(self)
1043 else:
1054 else:
1044 # If the pager can't be spawned in dispatch when --pager=on is
1055 # If the pager can't be spawned in dispatch when --pager=on is
1045 # given, don't try again when the command runs, to avoid a duplicate
1056 # given, don't try again when the command runs, to avoid a duplicate
1046 # warning about a missing pager command.
1057 # warning about a missing pager command.
1047 self.disablepager()
1058 self.disablepager()
1048
1059
1049 def _runpager(self, command, env=None):
1060 def _runpager(self, command, env=None):
1050 """Actually start the pager and set up file descriptors.
1061 """Actually start the pager and set up file descriptors.
1051
1062
1052 This is separate in part so that extensions (like chg) can
1063 This is separate in part so that extensions (like chg) can
1053 override how a pager is invoked.
1064 override how a pager is invoked.
1054 """
1065 """
1055 if command == 'cat':
1066 if command == 'cat':
1056 # Save ourselves some work.
1067 # Save ourselves some work.
1057 return False
1068 return False
1058 # If the command doesn't contain any of these characters, we
1069 # If the command doesn't contain any of these characters, we
1059 # assume it's a binary and exec it directly. This means for
1070 # assume it's a binary and exec it directly. This means for
1060 # simple pager command configurations, we can degrade
1071 # simple pager command configurations, we can degrade
1061 # gracefully and tell the user about their broken pager.
1072 # gracefully and tell the user about their broken pager.
1062 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1073 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1063
1074
1064 if pycompat.iswindows and not shell:
1075 if pycompat.iswindows and not shell:
1065 # Window's built-in `more` cannot be invoked with shell=False, but
1076 # Window's built-in `more` cannot be invoked with shell=False, but
1066 # its `more.com` can. Hide this implementation detail from the
1077 # its `more.com` can. Hide this implementation detail from the
1067 # user so we can also get sane bad PAGER behavior. MSYS has
1078 # user so we can also get sane bad PAGER behavior. MSYS has
1068 # `more.exe`, so do a cmd.exe style resolution of the executable to
1079 # `more.exe`, so do a cmd.exe style resolution of the executable to
1069 # determine which one to use.
1080 # determine which one to use.
1070 fullcmd = util.findexe(command)
1081 fullcmd = util.findexe(command)
1071 if not fullcmd:
1082 if not fullcmd:
1072 self.warn(_("missing pager command '%s', skipping pager\n")
1083 self.warn(_("missing pager command '%s', skipping pager\n")
1073 % command)
1084 % command)
1074 return False
1085 return False
1075
1086
1076 command = fullcmd
1087 command = fullcmd
1077
1088
1078 try:
1089 try:
1079 pager = subprocess.Popen(
1090 pager = subprocess.Popen(
1080 command, shell=shell, bufsize=-1,
1091 command, shell=shell, bufsize=-1,
1081 close_fds=util.closefds, stdin=subprocess.PIPE,
1092 close_fds=util.closefds, stdin=subprocess.PIPE,
1082 stdout=util.stdout, stderr=util.stderr,
1093 stdout=util.stdout, stderr=util.stderr,
1083 env=util.shellenviron(env))
1094 env=util.shellenviron(env))
1084 except OSError as e:
1095 except OSError as e:
1085 if e.errno == errno.ENOENT and not shell:
1096 if e.errno == errno.ENOENT and not shell:
1086 self.warn(_("missing pager command '%s', skipping pager\n")
1097 self.warn(_("missing pager command '%s', skipping pager\n")
1087 % command)
1098 % command)
1088 return False
1099 return False
1089 raise
1100 raise
1090
1101
1091 # back up original file descriptors
1102 # back up original file descriptors
1092 stdoutfd = os.dup(util.stdout.fileno())
1103 stdoutfd = os.dup(util.stdout.fileno())
1093 stderrfd = os.dup(util.stderr.fileno())
1104 stderrfd = os.dup(util.stderr.fileno())
1094
1105
1095 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1106 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
1096 if self._isatty(util.stderr):
1107 if self._isatty(util.stderr):
1097 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1108 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
1098
1109
1099 @self.atexit
1110 @self.atexit
1100 def killpager():
1111 def killpager():
1101 if util.safehasattr(signal, "SIGINT"):
1112 if util.safehasattr(signal, "SIGINT"):
1102 signal.signal(signal.SIGINT, signal.SIG_IGN)
1113 signal.signal(signal.SIGINT, signal.SIG_IGN)
1103 # restore original fds, closing pager.stdin copies in the process
1114 # restore original fds, closing pager.stdin copies in the process
1104 os.dup2(stdoutfd, util.stdout.fileno())
1115 os.dup2(stdoutfd, util.stdout.fileno())
1105 os.dup2(stderrfd, util.stderr.fileno())
1116 os.dup2(stderrfd, util.stderr.fileno())
1106 pager.stdin.close()
1117 pager.stdin.close()
1107 pager.wait()
1118 pager.wait()
1108
1119
1109 return True
1120 return True
1110
1121
1111 @property
1122 @property
1112 def _exithandlers(self):
1123 def _exithandlers(self):
1113 return _reqexithandlers
1124 return _reqexithandlers
1114
1125
1115 def atexit(self, func, *args, **kwargs):
1126 def atexit(self, func, *args, **kwargs):
1116 '''register a function to run after dispatching a request
1127 '''register a function to run after dispatching a request
1117
1128
1118 Handlers do not stay registered across request boundaries.'''
1129 Handlers do not stay registered across request boundaries.'''
1119 self._exithandlers.append((func, args, kwargs))
1130 self._exithandlers.append((func, args, kwargs))
1120 return func
1131 return func
1121
1132
1122 def interface(self, feature):
1133 def interface(self, feature):
1123 """what interface to use for interactive console features?
1134 """what interface to use for interactive console features?
1124
1135
1125 The interface is controlled by the value of `ui.interface` but also by
1136 The interface is controlled by the value of `ui.interface` but also by
1126 the value of feature-specific configuration. For example:
1137 the value of feature-specific configuration. For example:
1127
1138
1128 ui.interface.histedit = text
1139 ui.interface.histedit = text
1129 ui.interface.chunkselector = curses
1140 ui.interface.chunkselector = curses
1130
1141
1131 Here the features are "histedit" and "chunkselector".
1142 Here the features are "histedit" and "chunkselector".
1132
1143
1133 The configuration above means that the default interfaces for commands
1144 The configuration above means that the default interfaces for commands
1134 is curses, the interface for histedit is text and the interface for
1145 is curses, the interface for histedit is text and the interface for
1135 selecting chunk is crecord (the best curses interface available).
1146 selecting chunk is crecord (the best curses interface available).
1136
1147
1137 Consider the following example:
1148 Consider the following example:
1138 ui.interface = curses
1149 ui.interface = curses
1139 ui.interface.histedit = text
1150 ui.interface.histedit = text
1140
1151
1141 Then histedit will use the text interface and chunkselector will use
1152 Then histedit will use the text interface and chunkselector will use
1142 the default curses interface (crecord at the moment).
1153 the default curses interface (crecord at the moment).
1143 """
1154 """
1144 alldefaults = frozenset(["text", "curses"])
1155 alldefaults = frozenset(["text", "curses"])
1145
1156
1146 featureinterfaces = {
1157 featureinterfaces = {
1147 "chunkselector": [
1158 "chunkselector": [
1148 "text",
1159 "text",
1149 "curses",
1160 "curses",
1150 ]
1161 ]
1151 }
1162 }
1152
1163
1153 # Feature-specific interface
1164 # Feature-specific interface
1154 if feature not in featureinterfaces.keys():
1165 if feature not in featureinterfaces.keys():
1155 # Programming error, not user error
1166 # Programming error, not user error
1156 raise ValueError("Unknown feature requested %s" % feature)
1167 raise ValueError("Unknown feature requested %s" % feature)
1157
1168
1158 availableinterfaces = frozenset(featureinterfaces[feature])
1169 availableinterfaces = frozenset(featureinterfaces[feature])
1159 if alldefaults > availableinterfaces:
1170 if alldefaults > availableinterfaces:
1160 # Programming error, not user error. We need a use case to
1171 # Programming error, not user error. We need a use case to
1161 # define the right thing to do here.
1172 # define the right thing to do here.
1162 raise ValueError(
1173 raise ValueError(
1163 "Feature %s does not handle all default interfaces" %
1174 "Feature %s does not handle all default interfaces" %
1164 feature)
1175 feature)
1165
1176
1166 if self.plain():
1177 if self.plain():
1167 return "text"
1178 return "text"
1168
1179
1169 # Default interface for all the features
1180 # Default interface for all the features
1170 defaultinterface = "text"
1181 defaultinterface = "text"
1171 i = self.config("ui", "interface")
1182 i = self.config("ui", "interface")
1172 if i in alldefaults:
1183 if i in alldefaults:
1173 defaultinterface = i
1184 defaultinterface = i
1174
1185
1175 choseninterface = defaultinterface
1186 choseninterface = defaultinterface
1176 f = self.config("ui", "interface.%s" % feature)
1187 f = self.config("ui", "interface.%s" % feature)
1177 if f in availableinterfaces:
1188 if f in availableinterfaces:
1178 choseninterface = f
1189 choseninterface = f
1179
1190
1180 if i is not None and defaultinterface != i:
1191 if i is not None and defaultinterface != i:
1181 if f is not None:
1192 if f is not None:
1182 self.warn(_("invalid value for ui.interface: %s\n") %
1193 self.warn(_("invalid value for ui.interface: %s\n") %
1183 (i,))
1194 (i,))
1184 else:
1195 else:
1185 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1196 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1186 (i, choseninterface))
1197 (i, choseninterface))
1187 if f is not None and choseninterface != f:
1198 if f is not None and choseninterface != f:
1188 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1199 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1189 (feature, f, choseninterface))
1200 (feature, f, choseninterface))
1190
1201
1191 return choseninterface
1202 return choseninterface
1192
1203
1193 def interactive(self):
1204 def interactive(self):
1194 '''is interactive input allowed?
1205 '''is interactive input allowed?
1195
1206
1196 An interactive session is a session where input can be reasonably read
1207 An interactive session is a session where input can be reasonably read
1197 from `sys.stdin'. If this function returns false, any attempt to read
1208 from `sys.stdin'. If this function returns false, any attempt to read
1198 from stdin should fail with an error, unless a sensible default has been
1209 from stdin should fail with an error, unless a sensible default has been
1199 specified.
1210 specified.
1200
1211
1201 Interactiveness is triggered by the value of the `ui.interactive'
1212 Interactiveness is triggered by the value of the `ui.interactive'
1202 configuration variable or - if it is unset - when `sys.stdin' points
1213 configuration variable or - if it is unset - when `sys.stdin' points
1203 to a terminal device.
1214 to a terminal device.
1204
1215
1205 This function refers to input only; for output, see `ui.formatted()'.
1216 This function refers to input only; for output, see `ui.formatted()'.
1206 '''
1217 '''
1207 i = self.configbool("ui", "interactive")
1218 i = self.configbool("ui", "interactive")
1208 if i is None:
1219 if i is None:
1209 # some environments replace stdin without implementing isatty
1220 # some environments replace stdin without implementing isatty
1210 # usually those are non-interactive
1221 # usually those are non-interactive
1211 return self._isatty(self.fin)
1222 return self._isatty(self.fin)
1212
1223
1213 return i
1224 return i
1214
1225
1215 def termwidth(self):
1226 def termwidth(self):
1216 '''how wide is the terminal in columns?
1227 '''how wide is the terminal in columns?
1217 '''
1228 '''
1218 if 'COLUMNS' in encoding.environ:
1229 if 'COLUMNS' in encoding.environ:
1219 try:
1230 try:
1220 return int(encoding.environ['COLUMNS'])
1231 return int(encoding.environ['COLUMNS'])
1221 except ValueError:
1232 except ValueError:
1222 pass
1233 pass
1223 return scmutil.termsize(self)[0]
1234 return scmutil.termsize(self)[0]
1224
1235
1225 def formatted(self):
1236 def formatted(self):
1226 '''should formatted output be used?
1237 '''should formatted output be used?
1227
1238
1228 It is often desirable to format the output to suite the output medium.
1239 It is often desirable to format the output to suite the output medium.
1229 Examples of this are truncating long lines or colorizing messages.
1240 Examples of this are truncating long lines or colorizing messages.
1230 However, this is not often not desirable when piping output into other
1241 However, this is not often not desirable when piping output into other
1231 utilities, e.g. `grep'.
1242 utilities, e.g. `grep'.
1232
1243
1233 Formatted output is triggered by the value of the `ui.formatted'
1244 Formatted output is triggered by the value of the `ui.formatted'
1234 configuration variable or - if it is unset - when `sys.stdout' points
1245 configuration variable or - if it is unset - when `sys.stdout' points
1235 to a terminal device. Please note that `ui.formatted' should be
1246 to a terminal device. Please note that `ui.formatted' should be
1236 considered an implementation detail; it is not intended for use outside
1247 considered an implementation detail; it is not intended for use outside
1237 Mercurial or its extensions.
1248 Mercurial or its extensions.
1238
1249
1239 This function refers to output only; for input, see `ui.interactive()'.
1250 This function refers to output only; for input, see `ui.interactive()'.
1240 This function always returns false when in plain mode, see `ui.plain()'.
1251 This function always returns false when in plain mode, see `ui.plain()'.
1241 '''
1252 '''
1242 if self.plain():
1253 if self.plain():
1243 return False
1254 return False
1244
1255
1245 i = self.configbool("ui", "formatted")
1256 i = self.configbool("ui", "formatted")
1246 if i is None:
1257 if i is None:
1247 # some environments replace stdout without implementing isatty
1258 # some environments replace stdout without implementing isatty
1248 # usually those are non-interactive
1259 # usually those are non-interactive
1249 return self._isatty(self.fout)
1260 return self._isatty(self.fout)
1250
1261
1251 return i
1262 return i
1252
1263
1253 def _readline(self):
1264 def _readline(self):
1254 if self._isatty(self.fin):
1265 if self._isatty(self.fin):
1255 try:
1266 try:
1256 # magically add command line editing support, where
1267 # magically add command line editing support, where
1257 # available
1268 # available
1258 import readline
1269 import readline
1259 # force demandimport to really load the module
1270 # force demandimport to really load the module
1260 readline.read_history_file
1271 readline.read_history_file
1261 # windows sometimes raises something other than ImportError
1272 # windows sometimes raises something other than ImportError
1262 except Exception:
1273 except Exception:
1263 pass
1274 pass
1264
1275
1265 # prompt ' ' must exist; otherwise readline may delete entire line
1276 # prompt ' ' must exist; otherwise readline may delete entire line
1266 # - http://bugs.python.org/issue12833
1277 # - http://bugs.python.org/issue12833
1267 with self.timeblockedsection('stdio'):
1278 with self.timeblockedsection('stdio'):
1268 line = util.bytesinput(self.fin, self.fout, r' ')
1279 line = util.bytesinput(self.fin, self.fout, r' ')
1269
1280
1270 # When stdin is in binary mode on Windows, it can cause
1281 # When stdin is in binary mode on Windows, it can cause
1271 # raw_input() to emit an extra trailing carriage return
1282 # raw_input() to emit an extra trailing carriage return
1272 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1283 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1273 line = line[:-1]
1284 line = line[:-1]
1274 return line
1285 return line
1275
1286
1276 def prompt(self, msg, default="y"):
1287 def prompt(self, msg, default="y"):
1277 """Prompt user with msg, read response.
1288 """Prompt user with msg, read response.
1278 If ui is not interactive, the default is returned.
1289 If ui is not interactive, the default is returned.
1279 """
1290 """
1280 if not self.interactive():
1291 if not self.interactive():
1281 self.write(msg, ' ', default or '', "\n")
1292 self.write(msg, ' ', default or '', "\n")
1282 return default
1293 return default
1283 self._writenobuf(msg, label='ui.prompt')
1294 self._writenobuf(msg, label='ui.prompt')
1284 self.flush()
1295 self.flush()
1285 try:
1296 try:
1286 r = self._readline()
1297 r = self._readline()
1287 if not r:
1298 if not r:
1288 r = default
1299 r = default
1289 if self.configbool('ui', 'promptecho'):
1300 if self.configbool('ui', 'promptecho'):
1290 self.write(r, "\n")
1301 self.write(r, "\n")
1291 return r
1302 return r
1292 except EOFError:
1303 except EOFError:
1293 raise error.ResponseExpected()
1304 raise error.ResponseExpected()
1294
1305
1295 @staticmethod
1306 @staticmethod
1296 def extractchoices(prompt):
1307 def extractchoices(prompt):
1297 """Extract prompt message and list of choices from specified prompt.
1308 """Extract prompt message and list of choices from specified prompt.
1298
1309
1299 This returns tuple "(message, choices)", and "choices" is the
1310 This returns tuple "(message, choices)", and "choices" is the
1300 list of tuple "(response character, text without &)".
1311 list of tuple "(response character, text without &)".
1301
1312
1302 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1313 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1303 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1314 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1304 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1315 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1305 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1316 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1306 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1317 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1307 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1318 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1308 """
1319 """
1309
1320
1310 # Sadly, the prompt string may have been built with a filename
1321 # Sadly, the prompt string may have been built with a filename
1311 # containing "$$" so let's try to find the first valid-looking
1322 # containing "$$" so let's try to find the first valid-looking
1312 # prompt to start parsing. Sadly, we also can't rely on
1323 # prompt to start parsing. Sadly, we also can't rely on
1313 # choices containing spaces, ASCII, or basically anything
1324 # choices containing spaces, ASCII, or basically anything
1314 # except an ampersand followed by a character.
1325 # except an ampersand followed by a character.
1315 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1326 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1316 msg = m.group(1)
1327 msg = m.group(1)
1317 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1328 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1318 def choicetuple(s):
1329 def choicetuple(s):
1319 ampidx = s.index('&')
1330 ampidx = s.index('&')
1320 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1331 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1321 return (msg, [choicetuple(s) for s in choices])
1332 return (msg, [choicetuple(s) for s in choices])
1322
1333
1323 def promptchoice(self, prompt, default=0):
1334 def promptchoice(self, prompt, default=0):
1324 """Prompt user with a message, read response, and ensure it matches
1335 """Prompt user with a message, read response, and ensure it matches
1325 one of the provided choices. The prompt is formatted as follows:
1336 one of the provided choices. The prompt is formatted as follows:
1326
1337
1327 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1338 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1328
1339
1329 The index of the choice is returned. Responses are case
1340 The index of the choice is returned. Responses are case
1330 insensitive. If ui is not interactive, the default is
1341 insensitive. If ui is not interactive, the default is
1331 returned.
1342 returned.
1332 """
1343 """
1333
1344
1334 msg, choices = self.extractchoices(prompt)
1345 msg, choices = self.extractchoices(prompt)
1335 resps = [r for r, t in choices]
1346 resps = [r for r, t in choices]
1336 while True:
1347 while True:
1337 r = self.prompt(msg, resps[default])
1348 r = self.prompt(msg, resps[default])
1338 if r.lower() in resps:
1349 if r.lower() in resps:
1339 return resps.index(r.lower())
1350 return resps.index(r.lower())
1340 self.write(_("unrecognized response\n"))
1351 self.write(_("unrecognized response\n"))
1341
1352
1342 def getpass(self, prompt=None, default=None):
1353 def getpass(self, prompt=None, default=None):
1343 if not self.interactive():
1354 if not self.interactive():
1344 return default
1355 return default
1345 try:
1356 try:
1346 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1357 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1347 # disable getpass() only if explicitly specified. it's still valid
1358 # disable getpass() only if explicitly specified. it's still valid
1348 # to interact with tty even if fin is not a tty.
1359 # to interact with tty even if fin is not a tty.
1349 with self.timeblockedsection('stdio'):
1360 with self.timeblockedsection('stdio'):
1350 if self.configbool('ui', 'nontty'):
1361 if self.configbool('ui', 'nontty'):
1351 l = self.fin.readline()
1362 l = self.fin.readline()
1352 if not l:
1363 if not l:
1353 raise EOFError
1364 raise EOFError
1354 return l.rstrip('\n')
1365 return l.rstrip('\n')
1355 else:
1366 else:
1356 return getpass.getpass('')
1367 return getpass.getpass('')
1357 except EOFError:
1368 except EOFError:
1358 raise error.ResponseExpected()
1369 raise error.ResponseExpected()
1359 def status(self, *msg, **opts):
1370 def status(self, *msg, **opts):
1360 '''write status message to output (if ui.quiet is False)
1371 '''write status message to output (if ui.quiet is False)
1361
1372
1362 This adds an output label of "ui.status".
1373 This adds an output label of "ui.status".
1363 '''
1374 '''
1364 if not self.quiet:
1375 if not self.quiet:
1365 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1376 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1366 self.write(*msg, **opts)
1377 self.write(*msg, **opts)
1367 def warn(self, *msg, **opts):
1378 def warn(self, *msg, **opts):
1368 '''write warning message to output (stderr)
1379 '''write warning message to output (stderr)
1369
1380
1370 This adds an output label of "ui.warning".
1381 This adds an output label of "ui.warning".
1371 '''
1382 '''
1372 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1383 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1373 self.write_err(*msg, **opts)
1384 self.write_err(*msg, **opts)
1374 def note(self, *msg, **opts):
1385 def note(self, *msg, **opts):
1375 '''write note to output (if ui.verbose is True)
1386 '''write note to output (if ui.verbose is True)
1376
1387
1377 This adds an output label of "ui.note".
1388 This adds an output label of "ui.note".
1378 '''
1389 '''
1379 if self.verbose:
1390 if self.verbose:
1380 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1391 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1381 self.write(*msg, **opts)
1392 self.write(*msg, **opts)
1382 def debug(self, *msg, **opts):
1393 def debug(self, *msg, **opts):
1383 '''write debug message to output (if ui.debugflag is True)
1394 '''write debug message to output (if ui.debugflag is True)
1384
1395
1385 This adds an output label of "ui.debug".
1396 This adds an output label of "ui.debug".
1386 '''
1397 '''
1387 if self.debugflag:
1398 if self.debugflag:
1388 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1399 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1389 self.write(*msg, **opts)
1400 self.write(*msg, **opts)
1390
1401
1391 def edit(self, text, user, extra=None, editform=None, pending=None,
1402 def edit(self, text, user, extra=None, editform=None, pending=None,
1392 repopath=None, action=None):
1403 repopath=None, action=None):
1393 if action is None:
1404 if action is None:
1394 self.develwarn('action is None but will soon be a required '
1405 self.develwarn('action is None but will soon be a required '
1395 'parameter to ui.edit()')
1406 'parameter to ui.edit()')
1396 extra_defaults = {
1407 extra_defaults = {
1397 'prefix': 'editor',
1408 'prefix': 'editor',
1398 'suffix': '.txt',
1409 'suffix': '.txt',
1399 }
1410 }
1400 if extra is not None:
1411 if extra is not None:
1401 if extra.get('suffix') is not None:
1412 if extra.get('suffix') is not None:
1402 self.develwarn('extra.suffix is not None but will soon be '
1413 self.develwarn('extra.suffix is not None but will soon be '
1403 'ignored by ui.edit()')
1414 'ignored by ui.edit()')
1404 extra_defaults.update(extra)
1415 extra_defaults.update(extra)
1405 extra = extra_defaults
1416 extra = extra_defaults
1406
1417
1407 if action == 'diff':
1418 if action == 'diff':
1408 suffix = '.diff'
1419 suffix = '.diff'
1409 elif action:
1420 elif action:
1410 suffix = '.%s.hg.txt' % action
1421 suffix = '.%s.hg.txt' % action
1411 else:
1422 else:
1412 suffix = extra['suffix']
1423 suffix = extra['suffix']
1413
1424
1414 rdir = None
1425 rdir = None
1415 if self.configbool('experimental', 'editortmpinhg'):
1426 if self.configbool('experimental', 'editortmpinhg'):
1416 rdir = repopath
1427 rdir = repopath
1417 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1428 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1418 suffix=suffix,
1429 suffix=suffix,
1419 dir=rdir)
1430 dir=rdir)
1420 try:
1431 try:
1421 f = os.fdopen(fd, r'wb')
1432 f = os.fdopen(fd, r'wb')
1422 f.write(util.tonativeeol(text))
1433 f.write(util.tonativeeol(text))
1423 f.close()
1434 f.close()
1424
1435
1425 environ = {'HGUSER': user}
1436 environ = {'HGUSER': user}
1426 if 'transplant_source' in extra:
1437 if 'transplant_source' in extra:
1427 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1438 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1428 for label in ('intermediate-source', 'source', 'rebase_source'):
1439 for label in ('intermediate-source', 'source', 'rebase_source'):
1429 if label in extra:
1440 if label in extra:
1430 environ.update({'HGREVISION': extra[label]})
1441 environ.update({'HGREVISION': extra[label]})
1431 break
1442 break
1432 if editform:
1443 if editform:
1433 environ.update({'HGEDITFORM': editform})
1444 environ.update({'HGEDITFORM': editform})
1434 if pending:
1445 if pending:
1435 environ.update({'HG_PENDING': pending})
1446 environ.update({'HG_PENDING': pending})
1436
1447
1437 editor = self.geteditor()
1448 editor = self.geteditor()
1438
1449
1439 self.system("%s \"%s\"" % (editor, name),
1450 self.system("%s \"%s\"" % (editor, name),
1440 environ=environ,
1451 environ=environ,
1441 onerr=error.Abort, errprefix=_("edit failed"),
1452 onerr=error.Abort, errprefix=_("edit failed"),
1442 blockedtag='editor')
1453 blockedtag='editor')
1443
1454
1444 f = open(name, r'rb')
1455 f = open(name, r'rb')
1445 t = util.fromnativeeol(f.read())
1456 t = util.fromnativeeol(f.read())
1446 f.close()
1457 f.close()
1447 finally:
1458 finally:
1448 os.unlink(name)
1459 os.unlink(name)
1449
1460
1450 return t
1461 return t
1451
1462
1452 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1463 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1453 blockedtag=None):
1464 blockedtag=None):
1454 '''execute shell command with appropriate output stream. command
1465 '''execute shell command with appropriate output stream. command
1455 output will be redirected if fout is not stdout.
1466 output will be redirected if fout is not stdout.
1456
1467
1457 if command fails and onerr is None, return status, else raise onerr
1468 if command fails and onerr is None, return status, else raise onerr
1458 object as exception.
1469 object as exception.
1459 '''
1470 '''
1460 if blockedtag is None:
1471 if blockedtag is None:
1461 # Long cmds tend to be because of an absolute path on cmd. Keep
1472 # Long cmds tend to be because of an absolute path on cmd. Keep
1462 # the tail end instead
1473 # the tail end instead
1463 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1474 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1464 blockedtag = 'unknown_system_' + cmdsuffix
1475 blockedtag = 'unknown_system_' + cmdsuffix
1465 out = self.fout
1476 out = self.fout
1466 if any(s[1] for s in self._bufferstates):
1477 if any(s[1] for s in self._bufferstates):
1467 out = self
1478 out = self
1468 with self.timeblockedsection(blockedtag):
1479 with self.timeblockedsection(blockedtag):
1469 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1480 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1470 if rc and onerr:
1481 if rc and onerr:
1471 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1482 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1472 util.explainexit(rc)[0])
1483 util.explainexit(rc)[0])
1473 if errprefix:
1484 if errprefix:
1474 errmsg = '%s: %s' % (errprefix, errmsg)
1485 errmsg = '%s: %s' % (errprefix, errmsg)
1475 raise onerr(errmsg)
1486 raise onerr(errmsg)
1476 return rc
1487 return rc
1477
1488
1478 def _runsystem(self, cmd, environ, cwd, out):
1489 def _runsystem(self, cmd, environ, cwd, out):
1479 """actually execute the given shell command (can be overridden by
1490 """actually execute the given shell command (can be overridden by
1480 extensions like chg)"""
1491 extensions like chg)"""
1481 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1492 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1482
1493
1483 def traceback(self, exc=None, force=False):
1494 def traceback(self, exc=None, force=False):
1484 '''print exception traceback if traceback printing enabled or forced.
1495 '''print exception traceback if traceback printing enabled or forced.
1485 only to call in exception handler. returns true if traceback
1496 only to call in exception handler. returns true if traceback
1486 printed.'''
1497 printed.'''
1487 if self.tracebackflag or force:
1498 if self.tracebackflag or force:
1488 if exc is None:
1499 if exc is None:
1489 exc = sys.exc_info()
1500 exc = sys.exc_info()
1490 cause = getattr(exc[1], 'cause', None)
1501 cause = getattr(exc[1], 'cause', None)
1491
1502
1492 if cause is not None:
1503 if cause is not None:
1493 causetb = traceback.format_tb(cause[2])
1504 causetb = traceback.format_tb(cause[2])
1494 exctb = traceback.format_tb(exc[2])
1505 exctb = traceback.format_tb(exc[2])
1495 exconly = traceback.format_exception_only(cause[0], cause[1])
1506 exconly = traceback.format_exception_only(cause[0], cause[1])
1496
1507
1497 # exclude frame where 'exc' was chained and rethrown from exctb
1508 # exclude frame where 'exc' was chained and rethrown from exctb
1498 self.write_err('Traceback (most recent call last):\n',
1509 self.write_err('Traceback (most recent call last):\n',
1499 ''.join(exctb[:-1]),
1510 ''.join(exctb[:-1]),
1500 ''.join(causetb),
1511 ''.join(causetb),
1501 ''.join(exconly))
1512 ''.join(exconly))
1502 else:
1513 else:
1503 output = traceback.format_exception(exc[0], exc[1], exc[2])
1514 output = traceback.format_exception(exc[0], exc[1], exc[2])
1504 self.write_err(encoding.strtolocal(r''.join(output)))
1515 self.write_err(encoding.strtolocal(r''.join(output)))
1505 return self.tracebackflag or force
1516 return self.tracebackflag or force
1506
1517
1507 def geteditor(self):
1518 def geteditor(self):
1508 '''return editor to use'''
1519 '''return editor to use'''
1509 if pycompat.sysplatform == 'plan9':
1520 if pycompat.sysplatform == 'plan9':
1510 # vi is the MIPS instruction simulator on Plan 9. We
1521 # vi is the MIPS instruction simulator on Plan 9. We
1511 # instead default to E to plumb commit messages to
1522 # instead default to E to plumb commit messages to
1512 # avoid confusion.
1523 # avoid confusion.
1513 editor = 'E'
1524 editor = 'E'
1514 else:
1525 else:
1515 editor = 'vi'
1526 editor = 'vi'
1516 return (encoding.environ.get("HGEDITOR") or
1527 return (encoding.environ.get("HGEDITOR") or
1517 self.config("ui", "editor", editor))
1528 self.config("ui", "editor", editor))
1518
1529
1519 @util.propertycache
1530 @util.propertycache
1520 def _progbar(self):
1531 def _progbar(self):
1521 """setup the progbar singleton to the ui object"""
1532 """setup the progbar singleton to the ui object"""
1522 if (self.quiet or self.debugflag
1533 if (self.quiet or self.debugflag
1523 or self.configbool('progress', 'disable')
1534 or self.configbool('progress', 'disable')
1524 or not progress.shouldprint(self)):
1535 or not progress.shouldprint(self)):
1525 return None
1536 return None
1526 return getprogbar(self)
1537 return getprogbar(self)
1527
1538
1528 def _progclear(self):
1539 def _progclear(self):
1529 """clear progress bar output if any. use it before any output"""
1540 """clear progress bar output if any. use it before any output"""
1530 if not haveprogbar(): # nothing loaded yet
1541 if not haveprogbar(): # nothing loaded yet
1531 return
1542 return
1532 if self._progbar is not None and self._progbar.printed:
1543 if self._progbar is not None and self._progbar.printed:
1533 self._progbar.clear()
1544 self._progbar.clear()
1534
1545
1535 def progress(self, topic, pos, item="", unit="", total=None):
1546 def progress(self, topic, pos, item="", unit="", total=None):
1536 '''show a progress message
1547 '''show a progress message
1537
1548
1538 By default a textual progress bar will be displayed if an operation
1549 By default a textual progress bar will be displayed if an operation
1539 takes too long. 'topic' is the current operation, 'item' is a
1550 takes too long. 'topic' is the current operation, 'item' is a
1540 non-numeric marker of the current position (i.e. the currently
1551 non-numeric marker of the current position (i.e. the currently
1541 in-process file), 'pos' is the current numeric position (i.e.
1552 in-process file), 'pos' is the current numeric position (i.e.
1542 revision, bytes, etc.), unit is a corresponding unit label,
1553 revision, bytes, etc.), unit is a corresponding unit label,
1543 and total is the highest expected pos.
1554 and total is the highest expected pos.
1544
1555
1545 Multiple nested topics may be active at a time.
1556 Multiple nested topics may be active at a time.
1546
1557
1547 All topics should be marked closed by setting pos to None at
1558 All topics should be marked closed by setting pos to None at
1548 termination.
1559 termination.
1549 '''
1560 '''
1550 if self._progbar is not None:
1561 if self._progbar is not None:
1551 self._progbar.progress(topic, pos, item=item, unit=unit,
1562 self._progbar.progress(topic, pos, item=item, unit=unit,
1552 total=total)
1563 total=total)
1553 if pos is None or not self.configbool('progress', 'debug'):
1564 if pos is None or not self.configbool('progress', 'debug'):
1554 return
1565 return
1555
1566
1556 if unit:
1567 if unit:
1557 unit = ' ' + unit
1568 unit = ' ' + unit
1558 if item:
1569 if item:
1559 item = ' ' + item
1570 item = ' ' + item
1560
1571
1561 if total:
1572 if total:
1562 pct = 100.0 * pos / total
1573 pct = 100.0 * pos / total
1563 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1574 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1564 % (topic, item, pos, total, unit, pct))
1575 % (topic, item, pos, total, unit, pct))
1565 else:
1576 else:
1566 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1577 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1567
1578
1568 def log(self, service, *msg, **opts):
1579 def log(self, service, *msg, **opts):
1569 '''hook for logging facility extensions
1580 '''hook for logging facility extensions
1570
1581
1571 service should be a readily-identifiable subsystem, which will
1582 service should be a readily-identifiable subsystem, which will
1572 allow filtering.
1583 allow filtering.
1573
1584
1574 *msg should be a newline-terminated format string to log, and
1585 *msg should be a newline-terminated format string to log, and
1575 then any values to %-format into that format string.
1586 then any values to %-format into that format string.
1576
1587
1577 **opts currently has no defined meanings.
1588 **opts currently has no defined meanings.
1578 '''
1589 '''
1579
1590
1580 def label(self, msg, label):
1591 def label(self, msg, label):
1581 '''style msg based on supplied label
1592 '''style msg based on supplied label
1582
1593
1583 If some color mode is enabled, this will add the necessary control
1594 If some color mode is enabled, this will add the necessary control
1584 characters to apply such color. In addition, 'debug' color mode adds
1595 characters to apply such color. In addition, 'debug' color mode adds
1585 markup showing which label affects a piece of text.
1596 markup showing which label affects a piece of text.
1586
1597
1587 ui.write(s, 'label') is equivalent to
1598 ui.write(s, 'label') is equivalent to
1588 ui.write(ui.label(s, 'label')).
1599 ui.write(ui.label(s, 'label')).
1589 '''
1600 '''
1590 if self._colormode is not None:
1601 if self._colormode is not None:
1591 return color.colorlabel(self, msg, label)
1602 return color.colorlabel(self, msg, label)
1592 return msg
1603 return msg
1593
1604
1594 def develwarn(self, msg, stacklevel=1, config=None):
1605 def develwarn(self, msg, stacklevel=1, config=None):
1595 """issue a developer warning message
1606 """issue a developer warning message
1596
1607
1597 Use 'stacklevel' to report the offender some layers further up in the
1608 Use 'stacklevel' to report the offender some layers further up in the
1598 stack.
1609 stack.
1599 """
1610 """
1600 if not self.configbool('devel', 'all-warnings'):
1611 if not self.configbool('devel', 'all-warnings'):
1601 if config is None or not self.configbool('devel', config):
1612 if config is None or not self.configbool('devel', config):
1602 return
1613 return
1603 msg = 'devel-warn: ' + msg
1614 msg = 'devel-warn: ' + msg
1604 stacklevel += 1 # get in develwarn
1615 stacklevel += 1 # get in develwarn
1605 if self.tracebackflag:
1616 if self.tracebackflag:
1606 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1617 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1607 self.log('develwarn', '%s at:\n%s' %
1618 self.log('develwarn', '%s at:\n%s' %
1608 (msg, ''.join(util.getstackframes(stacklevel))))
1619 (msg, ''.join(util.getstackframes(stacklevel))))
1609 else:
1620 else:
1610 curframe = inspect.currentframe()
1621 curframe = inspect.currentframe()
1611 calframe = inspect.getouterframes(curframe, 2)
1622 calframe = inspect.getouterframes(curframe, 2)
1612 self.write_err('%s at: %s:%s (%s)\n'
1623 self.write_err('%s at: %s:%s (%s)\n'
1613 % ((msg,) + calframe[stacklevel][1:4]))
1624 % ((msg,) + calframe[stacklevel][1:4]))
1614 self.log('develwarn', '%s at: %s:%s (%s)\n',
1625 self.log('develwarn', '%s at: %s:%s (%s)\n',
1615 msg, *calframe[stacklevel][1:4])
1626 msg, *calframe[stacklevel][1:4])
1616 curframe = calframe = None # avoid cycles
1627 curframe = calframe = None # avoid cycles
1617
1628
1618 def deprecwarn(self, msg, version, stacklevel=2):
1629 def deprecwarn(self, msg, version, stacklevel=2):
1619 """issue a deprecation warning
1630 """issue a deprecation warning
1620
1631
1621 - msg: message explaining what is deprecated and how to upgrade,
1632 - msg: message explaining what is deprecated and how to upgrade,
1622 - version: last version where the API will be supported,
1633 - version: last version where the API will be supported,
1623 """
1634 """
1624 if not (self.configbool('devel', 'all-warnings')
1635 if not (self.configbool('devel', 'all-warnings')
1625 or self.configbool('devel', 'deprec-warn')):
1636 or self.configbool('devel', 'deprec-warn')):
1626 return
1637 return
1627 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1638 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1628 " update your code.)") % version
1639 " update your code.)") % version
1629 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1640 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1630
1641
1631 def exportableenviron(self):
1642 def exportableenviron(self):
1632 """The environment variables that are safe to export, e.g. through
1643 """The environment variables that are safe to export, e.g. through
1633 hgweb.
1644 hgweb.
1634 """
1645 """
1635 return self._exportableenviron
1646 return self._exportableenviron
1636
1647
1637 @contextlib.contextmanager
1648 @contextlib.contextmanager
1638 def configoverride(self, overrides, source=""):
1649 def configoverride(self, overrides, source=""):
1639 """Context manager for temporary config overrides
1650 """Context manager for temporary config overrides
1640 `overrides` must be a dict of the following structure:
1651 `overrides` must be a dict of the following structure:
1641 {(section, name) : value}"""
1652 {(section, name) : value}"""
1642 backups = {}
1653 backups = {}
1643 try:
1654 try:
1644 for (section, name), value in overrides.items():
1655 for (section, name), value in overrides.items():
1645 backups[(section, name)] = self.backupconfig(section, name)
1656 backups[(section, name)] = self.backupconfig(section, name)
1646 self.setconfig(section, name, value, source)
1657 self.setconfig(section, name, value, source)
1647 yield
1658 yield
1648 finally:
1659 finally:
1649 for __, backup in backups.items():
1660 for __, backup in backups.items():
1650 self.restoreconfig(backup)
1661 self.restoreconfig(backup)
1651 # just restoring ui.quiet config to the previous value is not enough
1662 # just restoring ui.quiet config to the previous value is not enough
1652 # as it does not update ui.quiet class member
1663 # as it does not update ui.quiet class member
1653 if ('ui', 'quiet') in overrides:
1664 if ('ui', 'quiet') in overrides:
1654 self.fixconfig(section='ui')
1665 self.fixconfig(section='ui')
1655
1666
1656 class paths(dict):
1667 class paths(dict):
1657 """Represents a collection of paths and their configs.
1668 """Represents a collection of paths and their configs.
1658
1669
1659 Data is initially derived from ui instances and the config files they have
1670 Data is initially derived from ui instances and the config files they have
1660 loaded.
1671 loaded.
1661 """
1672 """
1662 def __init__(self, ui):
1673 def __init__(self, ui):
1663 dict.__init__(self)
1674 dict.__init__(self)
1664
1675
1665 for name, loc in ui.configitems('paths', ignoresub=True):
1676 for name, loc in ui.configitems('paths', ignoresub=True):
1666 # No location is the same as not existing.
1677 # No location is the same as not existing.
1667 if not loc:
1678 if not loc:
1668 continue
1679 continue
1669 loc, sub = ui.configsuboptions('paths', name)
1680 loc, sub = ui.configsuboptions('paths', name)
1670 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1681 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1671
1682
1672 def getpath(self, name, default=None):
1683 def getpath(self, name, default=None):
1673 """Return a ``path`` from a string, falling back to default.
1684 """Return a ``path`` from a string, falling back to default.
1674
1685
1675 ``name`` can be a named path or locations. Locations are filesystem
1686 ``name`` can be a named path or locations. Locations are filesystem
1676 paths or URIs.
1687 paths or URIs.
1677
1688
1678 Returns None if ``name`` is not a registered path, a URI, or a local
1689 Returns None if ``name`` is not a registered path, a URI, or a local
1679 path to a repo.
1690 path to a repo.
1680 """
1691 """
1681 # Only fall back to default if no path was requested.
1692 # Only fall back to default if no path was requested.
1682 if name is None:
1693 if name is None:
1683 if not default:
1694 if not default:
1684 default = ()
1695 default = ()
1685 elif not isinstance(default, (tuple, list)):
1696 elif not isinstance(default, (tuple, list)):
1686 default = (default,)
1697 default = (default,)
1687 for k in default:
1698 for k in default:
1688 try:
1699 try:
1689 return self[k]
1700 return self[k]
1690 except KeyError:
1701 except KeyError:
1691 continue
1702 continue
1692 return None
1703 return None
1693
1704
1694 # Most likely empty string.
1705 # Most likely empty string.
1695 # This may need to raise in the future.
1706 # This may need to raise in the future.
1696 if not name:
1707 if not name:
1697 return None
1708 return None
1698
1709
1699 try:
1710 try:
1700 return self[name]
1711 return self[name]
1701 except KeyError:
1712 except KeyError:
1702 # Try to resolve as a local path or URI.
1713 # Try to resolve as a local path or URI.
1703 try:
1714 try:
1704 # We don't pass sub-options in, so no need to pass ui instance.
1715 # We don't pass sub-options in, so no need to pass ui instance.
1705 return path(None, None, rawloc=name)
1716 return path(None, None, rawloc=name)
1706 except ValueError:
1717 except ValueError:
1707 raise error.RepoError(_('repository %s does not exist') %
1718 raise error.RepoError(_('repository %s does not exist') %
1708 name)
1719 name)
1709
1720
1710 _pathsuboptions = {}
1721 _pathsuboptions = {}
1711
1722
1712 def pathsuboption(option, attr):
1723 def pathsuboption(option, attr):
1713 """Decorator used to declare a path sub-option.
1724 """Decorator used to declare a path sub-option.
1714
1725
1715 Arguments are the sub-option name and the attribute it should set on
1726 Arguments are the sub-option name and the attribute it should set on
1716 ``path`` instances.
1727 ``path`` instances.
1717
1728
1718 The decorated function will receive as arguments a ``ui`` instance,
1729 The decorated function will receive as arguments a ``ui`` instance,
1719 ``path`` instance, and the string value of this option from the config.
1730 ``path`` instance, and the string value of this option from the config.
1720 The function should return the value that will be set on the ``path``
1731 The function should return the value that will be set on the ``path``
1721 instance.
1732 instance.
1722
1733
1723 This decorator can be used to perform additional verification of
1734 This decorator can be used to perform additional verification of
1724 sub-options and to change the type of sub-options.
1735 sub-options and to change the type of sub-options.
1725 """
1736 """
1726 def register(func):
1737 def register(func):
1727 _pathsuboptions[option] = (attr, func)
1738 _pathsuboptions[option] = (attr, func)
1728 return func
1739 return func
1729 return register
1740 return register
1730
1741
1731 @pathsuboption('pushurl', 'pushloc')
1742 @pathsuboption('pushurl', 'pushloc')
1732 def pushurlpathoption(ui, path, value):
1743 def pushurlpathoption(ui, path, value):
1733 u = util.url(value)
1744 u = util.url(value)
1734 # Actually require a URL.
1745 # Actually require a URL.
1735 if not u.scheme:
1746 if not u.scheme:
1736 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1747 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1737 return None
1748 return None
1738
1749
1739 # Don't support the #foo syntax in the push URL to declare branch to
1750 # Don't support the #foo syntax in the push URL to declare branch to
1740 # push.
1751 # push.
1741 if u.fragment:
1752 if u.fragment:
1742 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1753 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1743 'ignoring)\n') % path.name)
1754 'ignoring)\n') % path.name)
1744 u.fragment = None
1755 u.fragment = None
1745
1756
1746 return str(u)
1757 return str(u)
1747
1758
1748 @pathsuboption('pushrev', 'pushrev')
1759 @pathsuboption('pushrev', 'pushrev')
1749 def pushrevpathoption(ui, path, value):
1760 def pushrevpathoption(ui, path, value):
1750 return value
1761 return value
1751
1762
1752 class path(object):
1763 class path(object):
1753 """Represents an individual path and its configuration."""
1764 """Represents an individual path and its configuration."""
1754
1765
1755 def __init__(self, ui, name, rawloc=None, suboptions=None):
1766 def __init__(self, ui, name, rawloc=None, suboptions=None):
1756 """Construct a path from its config options.
1767 """Construct a path from its config options.
1757
1768
1758 ``ui`` is the ``ui`` instance the path is coming from.
1769 ``ui`` is the ``ui`` instance the path is coming from.
1759 ``name`` is the symbolic name of the path.
1770 ``name`` is the symbolic name of the path.
1760 ``rawloc`` is the raw location, as defined in the config.
1771 ``rawloc`` is the raw location, as defined in the config.
1761 ``pushloc`` is the raw locations pushes should be made to.
1772 ``pushloc`` is the raw locations pushes should be made to.
1762
1773
1763 If ``name`` is not defined, we require that the location be a) a local
1774 If ``name`` is not defined, we require that the location be a) a local
1764 filesystem path with a .hg directory or b) a URL. If not,
1775 filesystem path with a .hg directory or b) a URL. If not,
1765 ``ValueError`` is raised.
1776 ``ValueError`` is raised.
1766 """
1777 """
1767 if not rawloc:
1778 if not rawloc:
1768 raise ValueError('rawloc must be defined')
1779 raise ValueError('rawloc must be defined')
1769
1780
1770 # Locations may define branches via syntax <base>#<branch>.
1781 # Locations may define branches via syntax <base>#<branch>.
1771 u = util.url(rawloc)
1782 u = util.url(rawloc)
1772 branch = None
1783 branch = None
1773 if u.fragment:
1784 if u.fragment:
1774 branch = u.fragment
1785 branch = u.fragment
1775 u.fragment = None
1786 u.fragment = None
1776
1787
1777 self.url = u
1788 self.url = u
1778 self.branch = branch
1789 self.branch = branch
1779
1790
1780 self.name = name
1791 self.name = name
1781 self.rawloc = rawloc
1792 self.rawloc = rawloc
1782 self.loc = '%s' % u
1793 self.loc = '%s' % u
1783
1794
1784 # When given a raw location but not a symbolic name, validate the
1795 # When given a raw location but not a symbolic name, validate the
1785 # location is valid.
1796 # location is valid.
1786 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1797 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1787 raise ValueError('location is not a URL or path to a local '
1798 raise ValueError('location is not a URL or path to a local '
1788 'repo: %s' % rawloc)
1799 'repo: %s' % rawloc)
1789
1800
1790 suboptions = suboptions or {}
1801 suboptions = suboptions or {}
1791
1802
1792 # Now process the sub-options. If a sub-option is registered, its
1803 # Now process the sub-options. If a sub-option is registered, its
1793 # attribute will always be present. The value will be None if there
1804 # attribute will always be present. The value will be None if there
1794 # was no valid sub-option.
1805 # was no valid sub-option.
1795 for suboption, (attr, func) in _pathsuboptions.iteritems():
1806 for suboption, (attr, func) in _pathsuboptions.iteritems():
1796 if suboption not in suboptions:
1807 if suboption not in suboptions:
1797 setattr(self, attr, None)
1808 setattr(self, attr, None)
1798 continue
1809 continue
1799
1810
1800 value = func(ui, self, suboptions[suboption])
1811 value = func(ui, self, suboptions[suboption])
1801 setattr(self, attr, value)
1812 setattr(self, attr, value)
1802
1813
1803 def _isvalidlocalpath(self, path):
1814 def _isvalidlocalpath(self, path):
1804 """Returns True if the given path is a potentially valid repository.
1815 """Returns True if the given path is a potentially valid repository.
1805 This is its own function so that extensions can change the definition of
1816 This is its own function so that extensions can change the definition of
1806 'valid' in this case (like when pulling from a git repo into a hg
1817 'valid' in this case (like when pulling from a git repo into a hg
1807 one)."""
1818 one)."""
1808 return os.path.isdir(os.path.join(path, '.hg'))
1819 return os.path.isdir(os.path.join(path, '.hg'))
1809
1820
1810 @property
1821 @property
1811 def suboptions(self):
1822 def suboptions(self):
1812 """Return sub-options and their values for this path.
1823 """Return sub-options and their values for this path.
1813
1824
1814 This is intended to be used for presentation purposes.
1825 This is intended to be used for presentation purposes.
1815 """
1826 """
1816 d = {}
1827 d = {}
1817 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1828 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1818 value = getattr(self, attr)
1829 value = getattr(self, attr)
1819 if value is not None:
1830 if value is not None:
1820 d[subopt] = value
1831 d[subopt] = value
1821 return d
1832 return d
1822
1833
1823 # we instantiate one globally shared progress bar to avoid
1834 # we instantiate one globally shared progress bar to avoid
1824 # competing progress bars when multiple UI objects get created
1835 # competing progress bars when multiple UI objects get created
1825 _progresssingleton = None
1836 _progresssingleton = None
1826
1837
1827 def getprogbar(ui):
1838 def getprogbar(ui):
1828 global _progresssingleton
1839 global _progresssingleton
1829 if _progresssingleton is None:
1840 if _progresssingleton is None:
1830 # passing 'ui' object to the singleton is fishy,
1841 # passing 'ui' object to the singleton is fishy,
1831 # this is how the extension used to work but feel free to rework it.
1842 # this is how the extension used to work but feel free to rework it.
1832 _progresssingleton = progress.progbar(ui)
1843 _progresssingleton = progress.progbar(ui)
1833 return _progresssingleton
1844 return _progresssingleton
1834
1845
1835 def haveprogbar():
1846 def haveprogbar():
1836 return _progresssingleton is not None
1847 return _progresssingleton is not None
General Comments 0
You need to be logged in to leave comments. Login now