##// END OF EJS Templates
ui: ensure `getpass()` returns bytes...
Matt Harbison -
r47013:07b0a687 stable
parent child Browse files
Show More
@@ -1,4578 +1,4582 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 codecs
10 import codecs
11 import collections
11 import collections
12 import difflib
12 import difflib
13 import errno
13 import errno
14 import glob
14 import glob
15 import operator
15 import operator
16 import os
16 import os
17 import platform
17 import platform
18 import random
18 import random
19 import re
19 import re
20 import socket
20 import socket
21 import ssl
21 import ssl
22 import stat
22 import stat
23 import string
23 import string
24 import subprocess
24 import subprocess
25 import sys
25 import sys
26 import time
26 import time
27
27
28 from .i18n import _
28 from .i18n import _
29 from .node import (
29 from .node import (
30 bin,
30 bin,
31 hex,
31 hex,
32 nullid,
32 nullid,
33 nullrev,
33 nullrev,
34 short,
34 short,
35 )
35 )
36 from .pycompat import (
36 from .pycompat import (
37 getattr,
37 getattr,
38 open,
38 open,
39 )
39 )
40 from . import (
40 from . import (
41 bundle2,
41 bundle2,
42 bundlerepo,
42 bundlerepo,
43 changegroup,
43 changegroup,
44 cmdutil,
44 cmdutil,
45 color,
45 color,
46 context,
46 context,
47 copies,
47 copies,
48 dagparser,
48 dagparser,
49 encoding,
49 encoding,
50 error,
50 error,
51 exchange,
51 exchange,
52 extensions,
52 extensions,
53 filemerge,
53 filemerge,
54 filesetlang,
54 filesetlang,
55 formatter,
55 formatter,
56 hg,
56 hg,
57 httppeer,
57 httppeer,
58 localrepo,
58 localrepo,
59 lock as lockmod,
59 lock as lockmod,
60 logcmdutil,
60 logcmdutil,
61 mergestate as mergestatemod,
61 mergestate as mergestatemod,
62 metadata,
62 metadata,
63 obsolete,
63 obsolete,
64 obsutil,
64 obsutil,
65 pathutil,
65 pathutil,
66 phases,
66 phases,
67 policy,
67 policy,
68 pvec,
68 pvec,
69 pycompat,
69 pycompat,
70 registrar,
70 registrar,
71 repair,
71 repair,
72 revlog,
72 revlog,
73 revset,
73 revset,
74 revsetlang,
74 revsetlang,
75 scmutil,
75 scmutil,
76 setdiscovery,
76 setdiscovery,
77 simplemerge,
77 simplemerge,
78 sshpeer,
78 sshpeer,
79 sslutil,
79 sslutil,
80 streamclone,
80 streamclone,
81 tags as tagsmod,
81 tags as tagsmod,
82 templater,
82 templater,
83 treediscovery,
83 treediscovery,
84 upgrade,
84 upgrade,
85 url as urlmod,
85 url as urlmod,
86 util,
86 util,
87 vfs as vfsmod,
87 vfs as vfsmod,
88 wireprotoframing,
88 wireprotoframing,
89 wireprotoserver,
89 wireprotoserver,
90 wireprotov2peer,
90 wireprotov2peer,
91 )
91 )
92 from .utils import (
92 from .utils import (
93 cborutil,
93 cborutil,
94 compression,
94 compression,
95 dateutil,
95 dateutil,
96 procutil,
96 procutil,
97 stringutil,
97 stringutil,
98 )
98 )
99
99
100 from .revlogutils import (
100 from .revlogutils import (
101 deltas as deltautil,
101 deltas as deltautil,
102 nodemap,
102 nodemap,
103 sidedata,
103 sidedata,
104 )
104 )
105
105
106 release = lockmod.release
106 release = lockmod.release
107
107
108 command = registrar.command()
108 command = registrar.command()
109
109
110
110
111 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
111 @command(b'debugancestor', [], _(b'[INDEX] REV1 REV2'), optionalrepo=True)
112 def debugancestor(ui, repo, *args):
112 def debugancestor(ui, repo, *args):
113 """find the ancestor revision of two revisions in a given index"""
113 """find the ancestor revision of two revisions in a given index"""
114 if len(args) == 3:
114 if len(args) == 3:
115 index, rev1, rev2 = args
115 index, rev1, rev2 = args
116 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
116 r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
117 lookup = r.lookup
117 lookup = r.lookup
118 elif len(args) == 2:
118 elif len(args) == 2:
119 if not repo:
119 if not repo:
120 raise error.Abort(
120 raise error.Abort(
121 _(b'there is no Mercurial repository here (.hg not found)')
121 _(b'there is no Mercurial repository here (.hg not found)')
122 )
122 )
123 rev1, rev2 = args
123 rev1, rev2 = args
124 r = repo.changelog
124 r = repo.changelog
125 lookup = repo.lookup
125 lookup = repo.lookup
126 else:
126 else:
127 raise error.Abort(_(b'either two or three arguments required'))
127 raise error.Abort(_(b'either two or three arguments required'))
128 a = r.ancestor(lookup(rev1), lookup(rev2))
128 a = r.ancestor(lookup(rev1), lookup(rev2))
129 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
129 ui.write(b'%d:%s\n' % (r.rev(a), hex(a)))
130
130
131
131
132 @command(b'debugantivirusrunning', [])
132 @command(b'debugantivirusrunning', [])
133 def debugantivirusrunning(ui, repo):
133 def debugantivirusrunning(ui, repo):
134 """attempt to trigger an antivirus scanner to see if one is active"""
134 """attempt to trigger an antivirus scanner to see if one is active"""
135 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
135 with repo.cachevfs.open('eicar-test-file.com', b'wb') as f:
136 f.write(
136 f.write(
137 util.b85decode(
137 util.b85decode(
138 # This is a base85-armored version of the EICAR test file. See
138 # This is a base85-armored version of the EICAR test file. See
139 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
139 # https://en.wikipedia.org/wiki/EICAR_test_file for details.
140 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
140 b'ST#=}P$fV?P+K%yP+C|uG$>GBDK|qyDK~v2MM*<JQY}+dK~6+LQba95P'
141 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
141 b'E<)&Nm5l)EmTEQR4qnHOhq9iNGnJx'
142 )
142 )
143 )
143 )
144 # Give an AV engine time to scan the file.
144 # Give an AV engine time to scan the file.
145 time.sleep(2)
145 time.sleep(2)
146 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
146 util.unlink(repo.cachevfs.join('eicar-test-file.com'))
147
147
148
148
149 @command(b'debugapplystreamclonebundle', [], b'FILE')
149 @command(b'debugapplystreamclonebundle', [], b'FILE')
150 def debugapplystreamclonebundle(ui, repo, fname):
150 def debugapplystreamclonebundle(ui, repo, fname):
151 """apply a stream clone bundle file"""
151 """apply a stream clone bundle file"""
152 f = hg.openpath(ui, fname)
152 f = hg.openpath(ui, fname)
153 gen = exchange.readbundle(ui, f, fname)
153 gen = exchange.readbundle(ui, f, fname)
154 gen.apply(repo)
154 gen.apply(repo)
155
155
156
156
157 @command(
157 @command(
158 b'debugbuilddag',
158 b'debugbuilddag',
159 [
159 [
160 (
160 (
161 b'm',
161 b'm',
162 b'mergeable-file',
162 b'mergeable-file',
163 None,
163 None,
164 _(b'add single file mergeable changes'),
164 _(b'add single file mergeable changes'),
165 ),
165 ),
166 (
166 (
167 b'o',
167 b'o',
168 b'overwritten-file',
168 b'overwritten-file',
169 None,
169 None,
170 _(b'add single file all revs overwrite'),
170 _(b'add single file all revs overwrite'),
171 ),
171 ),
172 (b'n', b'new-file', None, _(b'add new file at each rev')),
172 (b'n', b'new-file', None, _(b'add new file at each rev')),
173 ],
173 ],
174 _(b'[OPTION]... [TEXT]'),
174 _(b'[OPTION]... [TEXT]'),
175 )
175 )
176 def debugbuilddag(
176 def debugbuilddag(
177 ui,
177 ui,
178 repo,
178 repo,
179 text=None,
179 text=None,
180 mergeable_file=False,
180 mergeable_file=False,
181 overwritten_file=False,
181 overwritten_file=False,
182 new_file=False,
182 new_file=False,
183 ):
183 ):
184 """builds a repo with a given DAG from scratch in the current empty repo
184 """builds a repo with a given DAG from scratch in the current empty repo
185
185
186 The description of the DAG is read from stdin if not given on the
186 The description of the DAG is read from stdin if not given on the
187 command line.
187 command line.
188
188
189 Elements:
189 Elements:
190
190
191 - "+n" is a linear run of n nodes based on the current default parent
191 - "+n" is a linear run of n nodes based on the current default parent
192 - "." is a single node based on the current default parent
192 - "." is a single node based on the current default parent
193 - "$" resets the default parent to null (implied at the start);
193 - "$" resets the default parent to null (implied at the start);
194 otherwise the default parent is always the last node created
194 otherwise the default parent is always the last node created
195 - "<p" sets the default parent to the backref p
195 - "<p" sets the default parent to the backref p
196 - "*p" is a fork at parent p, which is a backref
196 - "*p" is a fork at parent p, which is a backref
197 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
197 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
198 - "/p2" is a merge of the preceding node and p2
198 - "/p2" is a merge of the preceding node and p2
199 - ":tag" defines a local tag for the preceding node
199 - ":tag" defines a local tag for the preceding node
200 - "@branch" sets the named branch for subsequent nodes
200 - "@branch" sets the named branch for subsequent nodes
201 - "#...\\n" is a comment up to the end of the line
201 - "#...\\n" is a comment up to the end of the line
202
202
203 Whitespace between the above elements is ignored.
203 Whitespace between the above elements is ignored.
204
204
205 A backref is either
205 A backref is either
206
206
207 - a number n, which references the node curr-n, where curr is the current
207 - a number n, which references the node curr-n, where curr is the current
208 node, or
208 node, or
209 - the name of a local tag you placed earlier using ":tag", or
209 - the name of a local tag you placed earlier using ":tag", or
210 - empty to denote the default parent.
210 - empty to denote the default parent.
211
211
212 All string valued-elements are either strictly alphanumeric, or must
212 All string valued-elements are either strictly alphanumeric, or must
213 be enclosed in double quotes ("..."), with "\\" as escape character.
213 be enclosed in double quotes ("..."), with "\\" as escape character.
214 """
214 """
215
215
216 if text is None:
216 if text is None:
217 ui.status(_(b"reading DAG from stdin\n"))
217 ui.status(_(b"reading DAG from stdin\n"))
218 text = ui.fin.read()
218 text = ui.fin.read()
219
219
220 cl = repo.changelog
220 cl = repo.changelog
221 if len(cl) > 0:
221 if len(cl) > 0:
222 raise error.Abort(_(b'repository is not empty'))
222 raise error.Abort(_(b'repository is not empty'))
223
223
224 # determine number of revs in DAG
224 # determine number of revs in DAG
225 total = 0
225 total = 0
226 for type, data in dagparser.parsedag(text):
226 for type, data in dagparser.parsedag(text):
227 if type == b'n':
227 if type == b'n':
228 total += 1
228 total += 1
229
229
230 if mergeable_file:
230 if mergeable_file:
231 linesperrev = 2
231 linesperrev = 2
232 # make a file with k lines per rev
232 # make a file with k lines per rev
233 initialmergedlines = [
233 initialmergedlines = [
234 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
234 b'%d' % i for i in pycompat.xrange(0, total * linesperrev)
235 ]
235 ]
236 initialmergedlines.append(b"")
236 initialmergedlines.append(b"")
237
237
238 tags = []
238 tags = []
239 progress = ui.makeprogress(
239 progress = ui.makeprogress(
240 _(b'building'), unit=_(b'revisions'), total=total
240 _(b'building'), unit=_(b'revisions'), total=total
241 )
241 )
242 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
242 with progress, repo.wlock(), repo.lock(), repo.transaction(b"builddag"):
243 at = -1
243 at = -1
244 atbranch = b'default'
244 atbranch = b'default'
245 nodeids = []
245 nodeids = []
246 id = 0
246 id = 0
247 progress.update(id)
247 progress.update(id)
248 for type, data in dagparser.parsedag(text):
248 for type, data in dagparser.parsedag(text):
249 if type == b'n':
249 if type == b'n':
250 ui.note((b'node %s\n' % pycompat.bytestr(data)))
250 ui.note((b'node %s\n' % pycompat.bytestr(data)))
251 id, ps = data
251 id, ps = data
252
252
253 files = []
253 files = []
254 filecontent = {}
254 filecontent = {}
255
255
256 p2 = None
256 p2 = None
257 if mergeable_file:
257 if mergeable_file:
258 fn = b"mf"
258 fn = b"mf"
259 p1 = repo[ps[0]]
259 p1 = repo[ps[0]]
260 if len(ps) > 1:
260 if len(ps) > 1:
261 p2 = repo[ps[1]]
261 p2 = repo[ps[1]]
262 pa = p1.ancestor(p2)
262 pa = p1.ancestor(p2)
263 base, local, other = [
263 base, local, other = [
264 x[fn].data() for x in (pa, p1, p2)
264 x[fn].data() for x in (pa, p1, p2)
265 ]
265 ]
266 m3 = simplemerge.Merge3Text(base, local, other)
266 m3 = simplemerge.Merge3Text(base, local, other)
267 ml = [l.strip() for l in m3.merge_lines()]
267 ml = [l.strip() for l in m3.merge_lines()]
268 ml.append(b"")
268 ml.append(b"")
269 elif at > 0:
269 elif at > 0:
270 ml = p1[fn].data().split(b"\n")
270 ml = p1[fn].data().split(b"\n")
271 else:
271 else:
272 ml = initialmergedlines
272 ml = initialmergedlines
273 ml[id * linesperrev] += b" r%i" % id
273 ml[id * linesperrev] += b" r%i" % id
274 mergedtext = b"\n".join(ml)
274 mergedtext = b"\n".join(ml)
275 files.append(fn)
275 files.append(fn)
276 filecontent[fn] = mergedtext
276 filecontent[fn] = mergedtext
277
277
278 if overwritten_file:
278 if overwritten_file:
279 fn = b"of"
279 fn = b"of"
280 files.append(fn)
280 files.append(fn)
281 filecontent[fn] = b"r%i\n" % id
281 filecontent[fn] = b"r%i\n" % id
282
282
283 if new_file:
283 if new_file:
284 fn = b"nf%i" % id
284 fn = b"nf%i" % id
285 files.append(fn)
285 files.append(fn)
286 filecontent[fn] = b"r%i\n" % id
286 filecontent[fn] = b"r%i\n" % id
287 if len(ps) > 1:
287 if len(ps) > 1:
288 if not p2:
288 if not p2:
289 p2 = repo[ps[1]]
289 p2 = repo[ps[1]]
290 for fn in p2:
290 for fn in p2:
291 if fn.startswith(b"nf"):
291 if fn.startswith(b"nf"):
292 files.append(fn)
292 files.append(fn)
293 filecontent[fn] = p2[fn].data()
293 filecontent[fn] = p2[fn].data()
294
294
295 def fctxfn(repo, cx, path):
295 def fctxfn(repo, cx, path):
296 if path in filecontent:
296 if path in filecontent:
297 return context.memfilectx(
297 return context.memfilectx(
298 repo, cx, path, filecontent[path]
298 repo, cx, path, filecontent[path]
299 )
299 )
300 return None
300 return None
301
301
302 if len(ps) == 0 or ps[0] < 0:
302 if len(ps) == 0 or ps[0] < 0:
303 pars = [None, None]
303 pars = [None, None]
304 elif len(ps) == 1:
304 elif len(ps) == 1:
305 pars = [nodeids[ps[0]], None]
305 pars = [nodeids[ps[0]], None]
306 else:
306 else:
307 pars = [nodeids[p] for p in ps]
307 pars = [nodeids[p] for p in ps]
308 cx = context.memctx(
308 cx = context.memctx(
309 repo,
309 repo,
310 pars,
310 pars,
311 b"r%i" % id,
311 b"r%i" % id,
312 files,
312 files,
313 fctxfn,
313 fctxfn,
314 date=(id, 0),
314 date=(id, 0),
315 user=b"debugbuilddag",
315 user=b"debugbuilddag",
316 extra={b'branch': atbranch},
316 extra={b'branch': atbranch},
317 )
317 )
318 nodeid = repo.commitctx(cx)
318 nodeid = repo.commitctx(cx)
319 nodeids.append(nodeid)
319 nodeids.append(nodeid)
320 at = id
320 at = id
321 elif type == b'l':
321 elif type == b'l':
322 id, name = data
322 id, name = data
323 ui.note((b'tag %s\n' % name))
323 ui.note((b'tag %s\n' % name))
324 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
324 tags.append(b"%s %s\n" % (hex(repo.changelog.node(id)), name))
325 elif type == b'a':
325 elif type == b'a':
326 ui.note((b'branch %s\n' % data))
326 ui.note((b'branch %s\n' % data))
327 atbranch = data
327 atbranch = data
328 progress.update(id)
328 progress.update(id)
329
329
330 if tags:
330 if tags:
331 repo.vfs.write(b"localtags", b"".join(tags))
331 repo.vfs.write(b"localtags", b"".join(tags))
332
332
333
333
334 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
334 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
335 indent_string = b' ' * indent
335 indent_string = b' ' * indent
336 if all:
336 if all:
337 ui.writenoi18n(
337 ui.writenoi18n(
338 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
338 b"%sformat: id, p1, p2, cset, delta base, len(delta)\n"
339 % indent_string
339 % indent_string
340 )
340 )
341
341
342 def showchunks(named):
342 def showchunks(named):
343 ui.write(b"\n%s%s\n" % (indent_string, named))
343 ui.write(b"\n%s%s\n" % (indent_string, named))
344 for deltadata in gen.deltaiter():
344 for deltadata in gen.deltaiter():
345 node, p1, p2, cs, deltabase, delta, flags = deltadata
345 node, p1, p2, cs, deltabase, delta, flags = deltadata
346 ui.write(
346 ui.write(
347 b"%s%s %s %s %s %s %d\n"
347 b"%s%s %s %s %s %s %d\n"
348 % (
348 % (
349 indent_string,
349 indent_string,
350 hex(node),
350 hex(node),
351 hex(p1),
351 hex(p1),
352 hex(p2),
352 hex(p2),
353 hex(cs),
353 hex(cs),
354 hex(deltabase),
354 hex(deltabase),
355 len(delta),
355 len(delta),
356 )
356 )
357 )
357 )
358
358
359 gen.changelogheader()
359 gen.changelogheader()
360 showchunks(b"changelog")
360 showchunks(b"changelog")
361 gen.manifestheader()
361 gen.manifestheader()
362 showchunks(b"manifest")
362 showchunks(b"manifest")
363 for chunkdata in iter(gen.filelogheader, {}):
363 for chunkdata in iter(gen.filelogheader, {}):
364 fname = chunkdata[b'filename']
364 fname = chunkdata[b'filename']
365 showchunks(fname)
365 showchunks(fname)
366 else:
366 else:
367 if isinstance(gen, bundle2.unbundle20):
367 if isinstance(gen, bundle2.unbundle20):
368 raise error.Abort(_(b'use debugbundle2 for this file'))
368 raise error.Abort(_(b'use debugbundle2 for this file'))
369 gen.changelogheader()
369 gen.changelogheader()
370 for deltadata in gen.deltaiter():
370 for deltadata in gen.deltaiter():
371 node, p1, p2, cs, deltabase, delta, flags = deltadata
371 node, p1, p2, cs, deltabase, delta, flags = deltadata
372 ui.write(b"%s%s\n" % (indent_string, hex(node)))
372 ui.write(b"%s%s\n" % (indent_string, hex(node)))
373
373
374
374
375 def _debugobsmarkers(ui, part, indent=0, **opts):
375 def _debugobsmarkers(ui, part, indent=0, **opts):
376 """display version and markers contained in 'data'"""
376 """display version and markers contained in 'data'"""
377 opts = pycompat.byteskwargs(opts)
377 opts = pycompat.byteskwargs(opts)
378 data = part.read()
378 data = part.read()
379 indent_string = b' ' * indent
379 indent_string = b' ' * indent
380 try:
380 try:
381 version, markers = obsolete._readmarkers(data)
381 version, markers = obsolete._readmarkers(data)
382 except error.UnknownVersion as exc:
382 except error.UnknownVersion as exc:
383 msg = b"%sunsupported version: %s (%d bytes)\n"
383 msg = b"%sunsupported version: %s (%d bytes)\n"
384 msg %= indent_string, exc.version, len(data)
384 msg %= indent_string, exc.version, len(data)
385 ui.write(msg)
385 ui.write(msg)
386 else:
386 else:
387 msg = b"%sversion: %d (%d bytes)\n"
387 msg = b"%sversion: %d (%d bytes)\n"
388 msg %= indent_string, version, len(data)
388 msg %= indent_string, version, len(data)
389 ui.write(msg)
389 ui.write(msg)
390 fm = ui.formatter(b'debugobsolete', opts)
390 fm = ui.formatter(b'debugobsolete', opts)
391 for rawmarker in sorted(markers):
391 for rawmarker in sorted(markers):
392 m = obsutil.marker(None, rawmarker)
392 m = obsutil.marker(None, rawmarker)
393 fm.startitem()
393 fm.startitem()
394 fm.plain(indent_string)
394 fm.plain(indent_string)
395 cmdutil.showmarker(fm, m)
395 cmdutil.showmarker(fm, m)
396 fm.end()
396 fm.end()
397
397
398
398
399 def _debugphaseheads(ui, data, indent=0):
399 def _debugphaseheads(ui, data, indent=0):
400 """display version and markers contained in 'data'"""
400 """display version and markers contained in 'data'"""
401 indent_string = b' ' * indent
401 indent_string = b' ' * indent
402 headsbyphase = phases.binarydecode(data)
402 headsbyphase = phases.binarydecode(data)
403 for phase in phases.allphases:
403 for phase in phases.allphases:
404 for head in headsbyphase[phase]:
404 for head in headsbyphase[phase]:
405 ui.write(indent_string)
405 ui.write(indent_string)
406 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
406 ui.write(b'%s %s\n' % (hex(head), phases.phasenames[phase]))
407
407
408
408
409 def _quasirepr(thing):
409 def _quasirepr(thing):
410 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
410 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
411 return b'{%s}' % (
411 return b'{%s}' % (
412 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
412 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing))
413 )
413 )
414 return pycompat.bytestr(repr(thing))
414 return pycompat.bytestr(repr(thing))
415
415
416
416
417 def _debugbundle2(ui, gen, all=None, **opts):
417 def _debugbundle2(ui, gen, all=None, **opts):
418 """lists the contents of a bundle2"""
418 """lists the contents of a bundle2"""
419 if not isinstance(gen, bundle2.unbundle20):
419 if not isinstance(gen, bundle2.unbundle20):
420 raise error.Abort(_(b'not a bundle2 file'))
420 raise error.Abort(_(b'not a bundle2 file'))
421 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
421 ui.write((b'Stream params: %s\n' % _quasirepr(gen.params)))
422 parttypes = opts.get('part_type', [])
422 parttypes = opts.get('part_type', [])
423 for part in gen.iterparts():
423 for part in gen.iterparts():
424 if parttypes and part.type not in parttypes:
424 if parttypes and part.type not in parttypes:
425 continue
425 continue
426 msg = b'%s -- %s (mandatory: %r)\n'
426 msg = b'%s -- %s (mandatory: %r)\n'
427 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
427 ui.write((msg % (part.type, _quasirepr(part.params), part.mandatory)))
428 if part.type == b'changegroup':
428 if part.type == b'changegroup':
429 version = part.params.get(b'version', b'01')
429 version = part.params.get(b'version', b'01')
430 cg = changegroup.getunbundler(version, part, b'UN')
430 cg = changegroup.getunbundler(version, part, b'UN')
431 if not ui.quiet:
431 if not ui.quiet:
432 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
432 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
433 if part.type == b'obsmarkers':
433 if part.type == b'obsmarkers':
434 if not ui.quiet:
434 if not ui.quiet:
435 _debugobsmarkers(ui, part, indent=4, **opts)
435 _debugobsmarkers(ui, part, indent=4, **opts)
436 if part.type == b'phase-heads':
436 if part.type == b'phase-heads':
437 if not ui.quiet:
437 if not ui.quiet:
438 _debugphaseheads(ui, part, indent=4)
438 _debugphaseheads(ui, part, indent=4)
439
439
440
440
441 @command(
441 @command(
442 b'debugbundle',
442 b'debugbundle',
443 [
443 [
444 (b'a', b'all', None, _(b'show all details')),
444 (b'a', b'all', None, _(b'show all details')),
445 (b'', b'part-type', [], _(b'show only the named part type')),
445 (b'', b'part-type', [], _(b'show only the named part type')),
446 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
446 (b'', b'spec', None, _(b'print the bundlespec of the bundle')),
447 ],
447 ],
448 _(b'FILE'),
448 _(b'FILE'),
449 norepo=True,
449 norepo=True,
450 )
450 )
451 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
451 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
452 """lists the contents of a bundle"""
452 """lists the contents of a bundle"""
453 with hg.openpath(ui, bundlepath) as f:
453 with hg.openpath(ui, bundlepath) as f:
454 if spec:
454 if spec:
455 spec = exchange.getbundlespec(ui, f)
455 spec = exchange.getbundlespec(ui, f)
456 ui.write(b'%s\n' % spec)
456 ui.write(b'%s\n' % spec)
457 return
457 return
458
458
459 gen = exchange.readbundle(ui, f, bundlepath)
459 gen = exchange.readbundle(ui, f, bundlepath)
460 if isinstance(gen, bundle2.unbundle20):
460 if isinstance(gen, bundle2.unbundle20):
461 return _debugbundle2(ui, gen, all=all, **opts)
461 return _debugbundle2(ui, gen, all=all, **opts)
462 _debugchangegroup(ui, gen, all=all, **opts)
462 _debugchangegroup(ui, gen, all=all, **opts)
463
463
464
464
465 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
465 @command(b'debugcapabilities', [], _(b'PATH'), norepo=True)
466 def debugcapabilities(ui, path, **opts):
466 def debugcapabilities(ui, path, **opts):
467 """lists the capabilities of a remote peer"""
467 """lists the capabilities of a remote peer"""
468 opts = pycompat.byteskwargs(opts)
468 opts = pycompat.byteskwargs(opts)
469 peer = hg.peer(ui, opts, path)
469 peer = hg.peer(ui, opts, path)
470 caps = peer.capabilities()
470 caps = peer.capabilities()
471 ui.writenoi18n(b'Main capabilities:\n')
471 ui.writenoi18n(b'Main capabilities:\n')
472 for c in sorted(caps):
472 for c in sorted(caps):
473 ui.write(b' %s\n' % c)
473 ui.write(b' %s\n' % c)
474 b2caps = bundle2.bundle2caps(peer)
474 b2caps = bundle2.bundle2caps(peer)
475 if b2caps:
475 if b2caps:
476 ui.writenoi18n(b'Bundle2 capabilities:\n')
476 ui.writenoi18n(b'Bundle2 capabilities:\n')
477 for key, values in sorted(pycompat.iteritems(b2caps)):
477 for key, values in sorted(pycompat.iteritems(b2caps)):
478 ui.write(b' %s\n' % key)
478 ui.write(b' %s\n' % key)
479 for v in values:
479 for v in values:
480 ui.write(b' %s\n' % v)
480 ui.write(b' %s\n' % v)
481
481
482
482
483 @command(b'debugchangedfiles', [], b'REV')
483 @command(b'debugchangedfiles', [], b'REV')
484 def debugchangedfiles(ui, repo, rev):
484 def debugchangedfiles(ui, repo, rev):
485 """list the stored files changes for a revision"""
485 """list the stored files changes for a revision"""
486 ctx = scmutil.revsingle(repo, rev, None)
486 ctx = scmutil.revsingle(repo, rev, None)
487 sd = repo.changelog.sidedata(ctx.rev())
487 sd = repo.changelog.sidedata(ctx.rev())
488 files_block = sd.get(sidedata.SD_FILES)
488 files_block = sd.get(sidedata.SD_FILES)
489 if files_block is not None:
489 if files_block is not None:
490 files = metadata.decode_files_sidedata(sd)
490 files = metadata.decode_files_sidedata(sd)
491 for f in sorted(files.touched):
491 for f in sorted(files.touched):
492 if f in files.added:
492 if f in files.added:
493 action = b"added"
493 action = b"added"
494 elif f in files.removed:
494 elif f in files.removed:
495 action = b"removed"
495 action = b"removed"
496 elif f in files.merged:
496 elif f in files.merged:
497 action = b"merged"
497 action = b"merged"
498 elif f in files.salvaged:
498 elif f in files.salvaged:
499 action = b"salvaged"
499 action = b"salvaged"
500 else:
500 else:
501 action = b"touched"
501 action = b"touched"
502
502
503 copy_parent = b""
503 copy_parent = b""
504 copy_source = b""
504 copy_source = b""
505 if f in files.copied_from_p1:
505 if f in files.copied_from_p1:
506 copy_parent = b"p1"
506 copy_parent = b"p1"
507 copy_source = files.copied_from_p1[f]
507 copy_source = files.copied_from_p1[f]
508 elif f in files.copied_from_p2:
508 elif f in files.copied_from_p2:
509 copy_parent = b"p2"
509 copy_parent = b"p2"
510 copy_source = files.copied_from_p2[f]
510 copy_source = files.copied_from_p2[f]
511
511
512 data = (action, copy_parent, f, copy_source)
512 data = (action, copy_parent, f, copy_source)
513 template = b"%-8s %2s: %s, %s;\n"
513 template = b"%-8s %2s: %s, %s;\n"
514 ui.write(template % data)
514 ui.write(template % data)
515
515
516
516
517 @command(b'debugcheckstate', [], b'')
517 @command(b'debugcheckstate', [], b'')
518 def debugcheckstate(ui, repo):
518 def debugcheckstate(ui, repo):
519 """validate the correctness of the current dirstate"""
519 """validate the correctness of the current dirstate"""
520 parent1, parent2 = repo.dirstate.parents()
520 parent1, parent2 = repo.dirstate.parents()
521 m1 = repo[parent1].manifest()
521 m1 = repo[parent1].manifest()
522 m2 = repo[parent2].manifest()
522 m2 = repo[parent2].manifest()
523 errors = 0
523 errors = 0
524 for f in repo.dirstate:
524 for f in repo.dirstate:
525 state = repo.dirstate[f]
525 state = repo.dirstate[f]
526 if state in b"nr" and f not in m1:
526 if state in b"nr" and f not in m1:
527 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
527 ui.warn(_(b"%s in state %s, but not in manifest1\n") % (f, state))
528 errors += 1
528 errors += 1
529 if state in b"a" and f in m1:
529 if state in b"a" and f in m1:
530 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
530 ui.warn(_(b"%s in state %s, but also in manifest1\n") % (f, state))
531 errors += 1
531 errors += 1
532 if state in b"m" and f not in m1 and f not in m2:
532 if state in b"m" and f not in m1 and f not in m2:
533 ui.warn(
533 ui.warn(
534 _(b"%s in state %s, but not in either manifest\n") % (f, state)
534 _(b"%s in state %s, but not in either manifest\n") % (f, state)
535 )
535 )
536 errors += 1
536 errors += 1
537 for f in m1:
537 for f in m1:
538 state = repo.dirstate[f]
538 state = repo.dirstate[f]
539 if state not in b"nrm":
539 if state not in b"nrm":
540 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
540 ui.warn(_(b"%s in manifest1, but listed as state %s") % (f, state))
541 errors += 1
541 errors += 1
542 if errors:
542 if errors:
543 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
543 errstr = _(b".hg/dirstate inconsistent with current parent's manifest")
544 raise error.Abort(errstr)
544 raise error.Abort(errstr)
545
545
546
546
547 @command(
547 @command(
548 b'debugcolor',
548 b'debugcolor',
549 [(b'', b'style', None, _(b'show all configured styles'))],
549 [(b'', b'style', None, _(b'show all configured styles'))],
550 b'hg debugcolor',
550 b'hg debugcolor',
551 )
551 )
552 def debugcolor(ui, repo, **opts):
552 def debugcolor(ui, repo, **opts):
553 """show available color, effects or style"""
553 """show available color, effects or style"""
554 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
554 ui.writenoi18n(b'color mode: %s\n' % stringutil.pprint(ui._colormode))
555 if opts.get('style'):
555 if opts.get('style'):
556 return _debugdisplaystyle(ui)
556 return _debugdisplaystyle(ui)
557 else:
557 else:
558 return _debugdisplaycolor(ui)
558 return _debugdisplaycolor(ui)
559
559
560
560
561 def _debugdisplaycolor(ui):
561 def _debugdisplaycolor(ui):
562 ui = ui.copy()
562 ui = ui.copy()
563 ui._styles.clear()
563 ui._styles.clear()
564 for effect in color._activeeffects(ui).keys():
564 for effect in color._activeeffects(ui).keys():
565 ui._styles[effect] = effect
565 ui._styles[effect] = effect
566 if ui._terminfoparams:
566 if ui._terminfoparams:
567 for k, v in ui.configitems(b'color'):
567 for k, v in ui.configitems(b'color'):
568 if k.startswith(b'color.'):
568 if k.startswith(b'color.'):
569 ui._styles[k] = k[6:]
569 ui._styles[k] = k[6:]
570 elif k.startswith(b'terminfo.'):
570 elif k.startswith(b'terminfo.'):
571 ui._styles[k] = k[9:]
571 ui._styles[k] = k[9:]
572 ui.write(_(b'available colors:\n'))
572 ui.write(_(b'available colors:\n'))
573 # sort label with a '_' after the other to group '_background' entry.
573 # sort label with a '_' after the other to group '_background' entry.
574 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
574 items = sorted(ui._styles.items(), key=lambda i: (b'_' in i[0], i[0], i[1]))
575 for colorname, label in items:
575 for colorname, label in items:
576 ui.write(b'%s\n' % colorname, label=label)
576 ui.write(b'%s\n' % colorname, label=label)
577
577
578
578
579 def _debugdisplaystyle(ui):
579 def _debugdisplaystyle(ui):
580 ui.write(_(b'available style:\n'))
580 ui.write(_(b'available style:\n'))
581 if not ui._styles:
581 if not ui._styles:
582 return
582 return
583 width = max(len(s) for s in ui._styles)
583 width = max(len(s) for s in ui._styles)
584 for label, effects in sorted(ui._styles.items()):
584 for label, effects in sorted(ui._styles.items()):
585 ui.write(b'%s' % label, label=label)
585 ui.write(b'%s' % label, label=label)
586 if effects:
586 if effects:
587 # 50
587 # 50
588 ui.write(b': ')
588 ui.write(b': ')
589 ui.write(b' ' * (max(0, width - len(label))))
589 ui.write(b' ' * (max(0, width - len(label))))
590 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
590 ui.write(b', '.join(ui.label(e, e) for e in effects.split()))
591 ui.write(b'\n')
591 ui.write(b'\n')
592
592
593
593
594 @command(b'debugcreatestreamclonebundle', [], b'FILE')
594 @command(b'debugcreatestreamclonebundle', [], b'FILE')
595 def debugcreatestreamclonebundle(ui, repo, fname):
595 def debugcreatestreamclonebundle(ui, repo, fname):
596 """create a stream clone bundle file
596 """create a stream clone bundle file
597
597
598 Stream bundles are special bundles that are essentially archives of
598 Stream bundles are special bundles that are essentially archives of
599 revlog files. They are commonly used for cloning very quickly.
599 revlog files. They are commonly used for cloning very quickly.
600 """
600 """
601 # TODO we may want to turn this into an abort when this functionality
601 # TODO we may want to turn this into an abort when this functionality
602 # is moved into `hg bundle`.
602 # is moved into `hg bundle`.
603 if phases.hassecret(repo):
603 if phases.hassecret(repo):
604 ui.warn(
604 ui.warn(
605 _(
605 _(
606 b'(warning: stream clone bundle will contain secret '
606 b'(warning: stream clone bundle will contain secret '
607 b'revisions)\n'
607 b'revisions)\n'
608 )
608 )
609 )
609 )
610
610
611 requirements, gen = streamclone.generatebundlev1(repo)
611 requirements, gen = streamclone.generatebundlev1(repo)
612 changegroup.writechunks(ui, gen, fname)
612 changegroup.writechunks(ui, gen, fname)
613
613
614 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
614 ui.write(_(b'bundle requirements: %s\n') % b', '.join(sorted(requirements)))
615
615
616
616
617 @command(
617 @command(
618 b'debugdag',
618 b'debugdag',
619 [
619 [
620 (b't', b'tags', None, _(b'use tags as labels')),
620 (b't', b'tags', None, _(b'use tags as labels')),
621 (b'b', b'branches', None, _(b'annotate with branch names')),
621 (b'b', b'branches', None, _(b'annotate with branch names')),
622 (b'', b'dots', None, _(b'use dots for runs')),
622 (b'', b'dots', None, _(b'use dots for runs')),
623 (b's', b'spaces', None, _(b'separate elements by spaces')),
623 (b's', b'spaces', None, _(b'separate elements by spaces')),
624 ],
624 ],
625 _(b'[OPTION]... [FILE [REV]...]'),
625 _(b'[OPTION]... [FILE [REV]...]'),
626 optionalrepo=True,
626 optionalrepo=True,
627 )
627 )
628 def debugdag(ui, repo, file_=None, *revs, **opts):
628 def debugdag(ui, repo, file_=None, *revs, **opts):
629 """format the changelog or an index DAG as a concise textual description
629 """format the changelog or an index DAG as a concise textual description
630
630
631 If you pass a revlog index, the revlog's DAG is emitted. If you list
631 If you pass a revlog index, the revlog's DAG is emitted. If you list
632 revision numbers, they get labeled in the output as rN.
632 revision numbers, they get labeled in the output as rN.
633
633
634 Otherwise, the changelog DAG of the current repo is emitted.
634 Otherwise, the changelog DAG of the current repo is emitted.
635 """
635 """
636 spaces = opts.get('spaces')
636 spaces = opts.get('spaces')
637 dots = opts.get('dots')
637 dots = opts.get('dots')
638 if file_:
638 if file_:
639 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
639 rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), file_)
640 revs = {int(r) for r in revs}
640 revs = {int(r) for r in revs}
641
641
642 def events():
642 def events():
643 for r in rlog:
643 for r in rlog:
644 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
644 yield b'n', (r, list(p for p in rlog.parentrevs(r) if p != -1))
645 if r in revs:
645 if r in revs:
646 yield b'l', (r, b"r%i" % r)
646 yield b'l', (r, b"r%i" % r)
647
647
648 elif repo:
648 elif repo:
649 cl = repo.changelog
649 cl = repo.changelog
650 tags = opts.get('tags')
650 tags = opts.get('tags')
651 branches = opts.get('branches')
651 branches = opts.get('branches')
652 if tags:
652 if tags:
653 labels = {}
653 labels = {}
654 for l, n in repo.tags().items():
654 for l, n in repo.tags().items():
655 labels.setdefault(cl.rev(n), []).append(l)
655 labels.setdefault(cl.rev(n), []).append(l)
656
656
657 def events():
657 def events():
658 b = b"default"
658 b = b"default"
659 for r in cl:
659 for r in cl:
660 if branches:
660 if branches:
661 newb = cl.read(cl.node(r))[5][b'branch']
661 newb = cl.read(cl.node(r))[5][b'branch']
662 if newb != b:
662 if newb != b:
663 yield b'a', newb
663 yield b'a', newb
664 b = newb
664 b = newb
665 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
665 yield b'n', (r, list(p for p in cl.parentrevs(r) if p != -1))
666 if tags:
666 if tags:
667 ls = labels.get(r)
667 ls = labels.get(r)
668 if ls:
668 if ls:
669 for l in ls:
669 for l in ls:
670 yield b'l', (r, l)
670 yield b'l', (r, l)
671
671
672 else:
672 else:
673 raise error.Abort(_(b'need repo for changelog dag'))
673 raise error.Abort(_(b'need repo for changelog dag'))
674
674
675 for line in dagparser.dagtextlines(
675 for line in dagparser.dagtextlines(
676 events(),
676 events(),
677 addspaces=spaces,
677 addspaces=spaces,
678 wraplabels=True,
678 wraplabels=True,
679 wrapannotations=True,
679 wrapannotations=True,
680 wrapnonlinear=dots,
680 wrapnonlinear=dots,
681 usedots=dots,
681 usedots=dots,
682 maxlinewidth=70,
682 maxlinewidth=70,
683 ):
683 ):
684 ui.write(line)
684 ui.write(line)
685 ui.write(b"\n")
685 ui.write(b"\n")
686
686
687
687
688 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
688 @command(b'debugdata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
689 def debugdata(ui, repo, file_, rev=None, **opts):
689 def debugdata(ui, repo, file_, rev=None, **opts):
690 """dump the contents of a data file revision"""
690 """dump the contents of a data file revision"""
691 opts = pycompat.byteskwargs(opts)
691 opts = pycompat.byteskwargs(opts)
692 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
692 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
693 if rev is not None:
693 if rev is not None:
694 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
694 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
695 file_, rev = None, file_
695 file_, rev = None, file_
696 elif rev is None:
696 elif rev is None:
697 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
697 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
698 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
698 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
699 try:
699 try:
700 ui.write(r.rawdata(r.lookup(rev)))
700 ui.write(r.rawdata(r.lookup(rev)))
701 except KeyError:
701 except KeyError:
702 raise error.Abort(_(b'invalid revision identifier %s') % rev)
702 raise error.Abort(_(b'invalid revision identifier %s') % rev)
703
703
704
704
705 @command(
705 @command(
706 b'debugdate',
706 b'debugdate',
707 [(b'e', b'extended', None, _(b'try extended date formats'))],
707 [(b'e', b'extended', None, _(b'try extended date formats'))],
708 _(b'[-e] DATE [RANGE]'),
708 _(b'[-e] DATE [RANGE]'),
709 norepo=True,
709 norepo=True,
710 optionalrepo=True,
710 optionalrepo=True,
711 )
711 )
712 def debugdate(ui, date, range=None, **opts):
712 def debugdate(ui, date, range=None, **opts):
713 """parse and display a date"""
713 """parse and display a date"""
714 if opts["extended"]:
714 if opts["extended"]:
715 d = dateutil.parsedate(date, dateutil.extendeddateformats)
715 d = dateutil.parsedate(date, dateutil.extendeddateformats)
716 else:
716 else:
717 d = dateutil.parsedate(date)
717 d = dateutil.parsedate(date)
718 ui.writenoi18n(b"internal: %d %d\n" % d)
718 ui.writenoi18n(b"internal: %d %d\n" % d)
719 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
719 ui.writenoi18n(b"standard: %s\n" % dateutil.datestr(d))
720 if range:
720 if range:
721 m = dateutil.matchdate(range)
721 m = dateutil.matchdate(range)
722 ui.writenoi18n(b"match: %s\n" % m(d[0]))
722 ui.writenoi18n(b"match: %s\n" % m(d[0]))
723
723
724
724
725 @command(
725 @command(
726 b'debugdeltachain',
726 b'debugdeltachain',
727 cmdutil.debugrevlogopts + cmdutil.formatteropts,
727 cmdutil.debugrevlogopts + cmdutil.formatteropts,
728 _(b'-c|-m|FILE'),
728 _(b'-c|-m|FILE'),
729 optionalrepo=True,
729 optionalrepo=True,
730 )
730 )
731 def debugdeltachain(ui, repo, file_=None, **opts):
731 def debugdeltachain(ui, repo, file_=None, **opts):
732 """dump information about delta chains in a revlog
732 """dump information about delta chains in a revlog
733
733
734 Output can be templatized. Available template keywords are:
734 Output can be templatized. Available template keywords are:
735
735
736 :``rev``: revision number
736 :``rev``: revision number
737 :``chainid``: delta chain identifier (numbered by unique base)
737 :``chainid``: delta chain identifier (numbered by unique base)
738 :``chainlen``: delta chain length to this revision
738 :``chainlen``: delta chain length to this revision
739 :``prevrev``: previous revision in delta chain
739 :``prevrev``: previous revision in delta chain
740 :``deltatype``: role of delta / how it was computed
740 :``deltatype``: role of delta / how it was computed
741 :``compsize``: compressed size of revision
741 :``compsize``: compressed size of revision
742 :``uncompsize``: uncompressed size of revision
742 :``uncompsize``: uncompressed size of revision
743 :``chainsize``: total size of compressed revisions in chain
743 :``chainsize``: total size of compressed revisions in chain
744 :``chainratio``: total chain size divided by uncompressed revision size
744 :``chainratio``: total chain size divided by uncompressed revision size
745 (new delta chains typically start at ratio 2.00)
745 (new delta chains typically start at ratio 2.00)
746 :``lindist``: linear distance from base revision in delta chain to end
746 :``lindist``: linear distance from base revision in delta chain to end
747 of this revision
747 of this revision
748 :``extradist``: total size of revisions not part of this delta chain from
748 :``extradist``: total size of revisions not part of this delta chain from
749 base of delta chain to end of this revision; a measurement
749 base of delta chain to end of this revision; a measurement
750 of how much extra data we need to read/seek across to read
750 of how much extra data we need to read/seek across to read
751 the delta chain for this revision
751 the delta chain for this revision
752 :``extraratio``: extradist divided by chainsize; another representation of
752 :``extraratio``: extradist divided by chainsize; another representation of
753 how much unrelated data is needed to load this delta chain
753 how much unrelated data is needed to load this delta chain
754
754
755 If the repository is configured to use the sparse read, additional keywords
755 If the repository is configured to use the sparse read, additional keywords
756 are available:
756 are available:
757
757
758 :``readsize``: total size of data read from the disk for a revision
758 :``readsize``: total size of data read from the disk for a revision
759 (sum of the sizes of all the blocks)
759 (sum of the sizes of all the blocks)
760 :``largestblock``: size of the largest block of data read from the disk
760 :``largestblock``: size of the largest block of data read from the disk
761 :``readdensity``: density of useful bytes in the data read from the disk
761 :``readdensity``: density of useful bytes in the data read from the disk
762 :``srchunks``: in how many data hunks the whole revision would be read
762 :``srchunks``: in how many data hunks the whole revision would be read
763
763
764 The sparse read can be enabled with experimental.sparse-read = True
764 The sparse read can be enabled with experimental.sparse-read = True
765 """
765 """
766 opts = pycompat.byteskwargs(opts)
766 opts = pycompat.byteskwargs(opts)
767 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
767 r = cmdutil.openrevlog(repo, b'debugdeltachain', file_, opts)
768 index = r.index
768 index = r.index
769 start = r.start
769 start = r.start
770 length = r.length
770 length = r.length
771 generaldelta = r.version & revlog.FLAG_GENERALDELTA
771 generaldelta = r.version & revlog.FLAG_GENERALDELTA
772 withsparseread = getattr(r, '_withsparseread', False)
772 withsparseread = getattr(r, '_withsparseread', False)
773
773
774 def revinfo(rev):
774 def revinfo(rev):
775 e = index[rev]
775 e = index[rev]
776 compsize = e[1]
776 compsize = e[1]
777 uncompsize = e[2]
777 uncompsize = e[2]
778 chainsize = 0
778 chainsize = 0
779
779
780 if generaldelta:
780 if generaldelta:
781 if e[3] == e[5]:
781 if e[3] == e[5]:
782 deltatype = b'p1'
782 deltatype = b'p1'
783 elif e[3] == e[6]:
783 elif e[3] == e[6]:
784 deltatype = b'p2'
784 deltatype = b'p2'
785 elif e[3] == rev - 1:
785 elif e[3] == rev - 1:
786 deltatype = b'prev'
786 deltatype = b'prev'
787 elif e[3] == rev:
787 elif e[3] == rev:
788 deltatype = b'base'
788 deltatype = b'base'
789 else:
789 else:
790 deltatype = b'other'
790 deltatype = b'other'
791 else:
791 else:
792 if e[3] == rev:
792 if e[3] == rev:
793 deltatype = b'base'
793 deltatype = b'base'
794 else:
794 else:
795 deltatype = b'prev'
795 deltatype = b'prev'
796
796
797 chain = r._deltachain(rev)[0]
797 chain = r._deltachain(rev)[0]
798 for iterrev in chain:
798 for iterrev in chain:
799 e = index[iterrev]
799 e = index[iterrev]
800 chainsize += e[1]
800 chainsize += e[1]
801
801
802 return compsize, uncompsize, deltatype, chain, chainsize
802 return compsize, uncompsize, deltatype, chain, chainsize
803
803
804 fm = ui.formatter(b'debugdeltachain', opts)
804 fm = ui.formatter(b'debugdeltachain', opts)
805
805
806 fm.plain(
806 fm.plain(
807 b' rev chain# chainlen prev delta '
807 b' rev chain# chainlen prev delta '
808 b'size rawsize chainsize ratio lindist extradist '
808 b'size rawsize chainsize ratio lindist extradist '
809 b'extraratio'
809 b'extraratio'
810 )
810 )
811 if withsparseread:
811 if withsparseread:
812 fm.plain(b' readsize largestblk rddensity srchunks')
812 fm.plain(b' readsize largestblk rddensity srchunks')
813 fm.plain(b'\n')
813 fm.plain(b'\n')
814
814
815 chainbases = {}
815 chainbases = {}
816 for rev in r:
816 for rev in r:
817 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
817 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
818 chainbase = chain[0]
818 chainbase = chain[0]
819 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
819 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
820 basestart = start(chainbase)
820 basestart = start(chainbase)
821 revstart = start(rev)
821 revstart = start(rev)
822 lineardist = revstart + comp - basestart
822 lineardist = revstart + comp - basestart
823 extradist = lineardist - chainsize
823 extradist = lineardist - chainsize
824 try:
824 try:
825 prevrev = chain[-2]
825 prevrev = chain[-2]
826 except IndexError:
826 except IndexError:
827 prevrev = -1
827 prevrev = -1
828
828
829 if uncomp != 0:
829 if uncomp != 0:
830 chainratio = float(chainsize) / float(uncomp)
830 chainratio = float(chainsize) / float(uncomp)
831 else:
831 else:
832 chainratio = chainsize
832 chainratio = chainsize
833
833
834 if chainsize != 0:
834 if chainsize != 0:
835 extraratio = float(extradist) / float(chainsize)
835 extraratio = float(extradist) / float(chainsize)
836 else:
836 else:
837 extraratio = extradist
837 extraratio = extradist
838
838
839 fm.startitem()
839 fm.startitem()
840 fm.write(
840 fm.write(
841 b'rev chainid chainlen prevrev deltatype compsize '
841 b'rev chainid chainlen prevrev deltatype compsize '
842 b'uncompsize chainsize chainratio lindist extradist '
842 b'uncompsize chainsize chainratio lindist extradist '
843 b'extraratio',
843 b'extraratio',
844 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
844 b'%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
845 rev,
845 rev,
846 chainid,
846 chainid,
847 len(chain),
847 len(chain),
848 prevrev,
848 prevrev,
849 deltatype,
849 deltatype,
850 comp,
850 comp,
851 uncomp,
851 uncomp,
852 chainsize,
852 chainsize,
853 chainratio,
853 chainratio,
854 lineardist,
854 lineardist,
855 extradist,
855 extradist,
856 extraratio,
856 extraratio,
857 rev=rev,
857 rev=rev,
858 chainid=chainid,
858 chainid=chainid,
859 chainlen=len(chain),
859 chainlen=len(chain),
860 prevrev=prevrev,
860 prevrev=prevrev,
861 deltatype=deltatype,
861 deltatype=deltatype,
862 compsize=comp,
862 compsize=comp,
863 uncompsize=uncomp,
863 uncompsize=uncomp,
864 chainsize=chainsize,
864 chainsize=chainsize,
865 chainratio=chainratio,
865 chainratio=chainratio,
866 lindist=lineardist,
866 lindist=lineardist,
867 extradist=extradist,
867 extradist=extradist,
868 extraratio=extraratio,
868 extraratio=extraratio,
869 )
869 )
870 if withsparseread:
870 if withsparseread:
871 readsize = 0
871 readsize = 0
872 largestblock = 0
872 largestblock = 0
873 srchunks = 0
873 srchunks = 0
874
874
875 for revschunk in deltautil.slicechunk(r, chain):
875 for revschunk in deltautil.slicechunk(r, chain):
876 srchunks += 1
876 srchunks += 1
877 blkend = start(revschunk[-1]) + length(revschunk[-1])
877 blkend = start(revschunk[-1]) + length(revschunk[-1])
878 blksize = blkend - start(revschunk[0])
878 blksize = blkend - start(revschunk[0])
879
879
880 readsize += blksize
880 readsize += blksize
881 if largestblock < blksize:
881 if largestblock < blksize:
882 largestblock = blksize
882 largestblock = blksize
883
883
884 if readsize:
884 if readsize:
885 readdensity = float(chainsize) / float(readsize)
885 readdensity = float(chainsize) / float(readsize)
886 else:
886 else:
887 readdensity = 1
887 readdensity = 1
888
888
889 fm.write(
889 fm.write(
890 b'readsize largestblock readdensity srchunks',
890 b'readsize largestblock readdensity srchunks',
891 b' %10d %10d %9.5f %8d',
891 b' %10d %10d %9.5f %8d',
892 readsize,
892 readsize,
893 largestblock,
893 largestblock,
894 readdensity,
894 readdensity,
895 srchunks,
895 srchunks,
896 readsize=readsize,
896 readsize=readsize,
897 largestblock=largestblock,
897 largestblock=largestblock,
898 readdensity=readdensity,
898 readdensity=readdensity,
899 srchunks=srchunks,
899 srchunks=srchunks,
900 )
900 )
901
901
902 fm.plain(b'\n')
902 fm.plain(b'\n')
903
903
904 fm.end()
904 fm.end()
905
905
906
906
907 @command(
907 @command(
908 b'debugdirstate|debugstate',
908 b'debugdirstate|debugstate',
909 [
909 [
910 (
910 (
911 b'',
911 b'',
912 b'nodates',
912 b'nodates',
913 None,
913 None,
914 _(b'do not display the saved mtime (DEPRECATED)'),
914 _(b'do not display the saved mtime (DEPRECATED)'),
915 ),
915 ),
916 (b'', b'dates', True, _(b'display the saved mtime')),
916 (b'', b'dates', True, _(b'display the saved mtime')),
917 (b'', b'datesort', None, _(b'sort by saved mtime')),
917 (b'', b'datesort', None, _(b'sort by saved mtime')),
918 ],
918 ],
919 _(b'[OPTION]...'),
919 _(b'[OPTION]...'),
920 )
920 )
921 def debugstate(ui, repo, **opts):
921 def debugstate(ui, repo, **opts):
922 """show the contents of the current dirstate"""
922 """show the contents of the current dirstate"""
923
923
924 nodates = not opts['dates']
924 nodates = not opts['dates']
925 if opts.get('nodates') is not None:
925 if opts.get('nodates') is not None:
926 nodates = True
926 nodates = True
927 datesort = opts.get('datesort')
927 datesort = opts.get('datesort')
928
928
929 if datesort:
929 if datesort:
930 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
930 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
931 else:
931 else:
932 keyfunc = None # sort by filename
932 keyfunc = None # sort by filename
933 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
933 for file_, ent in sorted(pycompat.iteritems(repo.dirstate), key=keyfunc):
934 if ent[3] == -1:
934 if ent[3] == -1:
935 timestr = b'unset '
935 timestr = b'unset '
936 elif nodates:
936 elif nodates:
937 timestr = b'set '
937 timestr = b'set '
938 else:
938 else:
939 timestr = time.strftime(
939 timestr = time.strftime(
940 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
940 "%Y-%m-%d %H:%M:%S ", time.localtime(ent[3])
941 )
941 )
942 timestr = encoding.strtolocal(timestr)
942 timestr = encoding.strtolocal(timestr)
943 if ent[1] & 0o20000:
943 if ent[1] & 0o20000:
944 mode = b'lnk'
944 mode = b'lnk'
945 else:
945 else:
946 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
946 mode = b'%3o' % (ent[1] & 0o777 & ~util.umask)
947 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
947 ui.write(b"%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
948 for f in repo.dirstate.copies():
948 for f in repo.dirstate.copies():
949 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
949 ui.write(_(b"copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
950
950
951
951
952 @command(
952 @command(
953 b'debugdiscovery',
953 b'debugdiscovery',
954 [
954 [
955 (b'', b'old', None, _(b'use old-style discovery')),
955 (b'', b'old', None, _(b'use old-style discovery')),
956 (
956 (
957 b'',
957 b'',
958 b'nonheads',
958 b'nonheads',
959 None,
959 None,
960 _(b'use old-style discovery with non-heads included'),
960 _(b'use old-style discovery with non-heads included'),
961 ),
961 ),
962 (b'', b'rev', [], b'restrict discovery to this set of revs'),
962 (b'', b'rev', [], b'restrict discovery to this set of revs'),
963 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
963 (b'', b'seed', b'12323', b'specify the random seed use for discovery'),
964 ]
964 ]
965 + cmdutil.remoteopts,
965 + cmdutil.remoteopts,
966 _(b'[--rev REV] [OTHER]'),
966 _(b'[--rev REV] [OTHER]'),
967 )
967 )
968 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
968 def debugdiscovery(ui, repo, remoteurl=b"default", **opts):
969 """runs the changeset discovery protocol in isolation"""
969 """runs the changeset discovery protocol in isolation"""
970 opts = pycompat.byteskwargs(opts)
970 opts = pycompat.byteskwargs(opts)
971 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
971 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
972 remote = hg.peer(repo, opts, remoteurl)
972 remote = hg.peer(repo, opts, remoteurl)
973 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
973 ui.status(_(b'comparing with %s\n') % util.hidepassword(remoteurl))
974
974
975 # make sure tests are repeatable
975 # make sure tests are repeatable
976 random.seed(int(opts[b'seed']))
976 random.seed(int(opts[b'seed']))
977
977
978 if opts.get(b'old'):
978 if opts.get(b'old'):
979
979
980 def doit(pushedrevs, remoteheads, remote=remote):
980 def doit(pushedrevs, remoteheads, remote=remote):
981 if not util.safehasattr(remote, b'branches'):
981 if not util.safehasattr(remote, b'branches'):
982 # enable in-client legacy support
982 # enable in-client legacy support
983 remote = localrepo.locallegacypeer(remote.local())
983 remote = localrepo.locallegacypeer(remote.local())
984 common, _in, hds = treediscovery.findcommonincoming(
984 common, _in, hds = treediscovery.findcommonincoming(
985 repo, remote, force=True
985 repo, remote, force=True
986 )
986 )
987 common = set(common)
987 common = set(common)
988 if not opts.get(b'nonheads'):
988 if not opts.get(b'nonheads'):
989 ui.writenoi18n(
989 ui.writenoi18n(
990 b"unpruned common: %s\n"
990 b"unpruned common: %s\n"
991 % b" ".join(sorted(short(n) for n in common))
991 % b" ".join(sorted(short(n) for n in common))
992 )
992 )
993
993
994 clnode = repo.changelog.node
994 clnode = repo.changelog.node
995 common = repo.revs(b'heads(::%ln)', common)
995 common = repo.revs(b'heads(::%ln)', common)
996 common = {clnode(r) for r in common}
996 common = {clnode(r) for r in common}
997 return common, hds
997 return common, hds
998
998
999 else:
999 else:
1000
1000
1001 def doit(pushedrevs, remoteheads, remote=remote):
1001 def doit(pushedrevs, remoteheads, remote=remote):
1002 nodes = None
1002 nodes = None
1003 if pushedrevs:
1003 if pushedrevs:
1004 revs = scmutil.revrange(repo, pushedrevs)
1004 revs = scmutil.revrange(repo, pushedrevs)
1005 nodes = [repo[r].node() for r in revs]
1005 nodes = [repo[r].node() for r in revs]
1006 common, any, hds = setdiscovery.findcommonheads(
1006 common, any, hds = setdiscovery.findcommonheads(
1007 ui, repo, remote, ancestorsof=nodes
1007 ui, repo, remote, ancestorsof=nodes
1008 )
1008 )
1009 return common, hds
1009 return common, hds
1010
1010
1011 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1011 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
1012 localrevs = opts[b'rev']
1012 localrevs = opts[b'rev']
1013 with util.timedcm('debug-discovery') as t:
1013 with util.timedcm('debug-discovery') as t:
1014 common, hds = doit(localrevs, remoterevs)
1014 common, hds = doit(localrevs, remoterevs)
1015
1015
1016 # compute all statistics
1016 # compute all statistics
1017 common = set(common)
1017 common = set(common)
1018 rheads = set(hds)
1018 rheads = set(hds)
1019 lheads = set(repo.heads())
1019 lheads = set(repo.heads())
1020
1020
1021 data = {}
1021 data = {}
1022 data[b'elapsed'] = t.elapsed
1022 data[b'elapsed'] = t.elapsed
1023 data[b'nb-common'] = len(common)
1023 data[b'nb-common'] = len(common)
1024 data[b'nb-common-local'] = len(common & lheads)
1024 data[b'nb-common-local'] = len(common & lheads)
1025 data[b'nb-common-remote'] = len(common & rheads)
1025 data[b'nb-common-remote'] = len(common & rheads)
1026 data[b'nb-common-both'] = len(common & rheads & lheads)
1026 data[b'nb-common-both'] = len(common & rheads & lheads)
1027 data[b'nb-local'] = len(lheads)
1027 data[b'nb-local'] = len(lheads)
1028 data[b'nb-local-missing'] = data[b'nb-local'] - data[b'nb-common-local']
1028 data[b'nb-local-missing'] = data[b'nb-local'] - data[b'nb-common-local']
1029 data[b'nb-remote'] = len(rheads)
1029 data[b'nb-remote'] = len(rheads)
1030 data[b'nb-remote-unknown'] = data[b'nb-remote'] - data[b'nb-common-remote']
1030 data[b'nb-remote-unknown'] = data[b'nb-remote'] - data[b'nb-common-remote']
1031 data[b'nb-revs'] = len(repo.revs(b'all()'))
1031 data[b'nb-revs'] = len(repo.revs(b'all()'))
1032 data[b'nb-revs-common'] = len(repo.revs(b'::%ln', common))
1032 data[b'nb-revs-common'] = len(repo.revs(b'::%ln', common))
1033 data[b'nb-revs-missing'] = data[b'nb-revs'] - data[b'nb-revs-common']
1033 data[b'nb-revs-missing'] = data[b'nb-revs'] - data[b'nb-revs-common']
1034
1034
1035 # display discovery summary
1035 # display discovery summary
1036 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1036 ui.writenoi18n(b"elapsed time: %(elapsed)f seconds\n" % data)
1037 ui.writenoi18n(b"heads summary:\n")
1037 ui.writenoi18n(b"heads summary:\n")
1038 ui.writenoi18n(b" total common heads: %(nb-common)9d\n" % data)
1038 ui.writenoi18n(b" total common heads: %(nb-common)9d\n" % data)
1039 ui.writenoi18n(b" also local heads: %(nb-common-local)9d\n" % data)
1039 ui.writenoi18n(b" also local heads: %(nb-common-local)9d\n" % data)
1040 ui.writenoi18n(b" also remote heads: %(nb-common-remote)9d\n" % data)
1040 ui.writenoi18n(b" also remote heads: %(nb-common-remote)9d\n" % data)
1041 ui.writenoi18n(b" both: %(nb-common-both)9d\n" % data)
1041 ui.writenoi18n(b" both: %(nb-common-both)9d\n" % data)
1042 ui.writenoi18n(b" local heads: %(nb-local)9d\n" % data)
1042 ui.writenoi18n(b" local heads: %(nb-local)9d\n" % data)
1043 ui.writenoi18n(b" common: %(nb-common-local)9d\n" % data)
1043 ui.writenoi18n(b" common: %(nb-common-local)9d\n" % data)
1044 ui.writenoi18n(b" missing: %(nb-local-missing)9d\n" % data)
1044 ui.writenoi18n(b" missing: %(nb-local-missing)9d\n" % data)
1045 ui.writenoi18n(b" remote heads: %(nb-remote)9d\n" % data)
1045 ui.writenoi18n(b" remote heads: %(nb-remote)9d\n" % data)
1046 ui.writenoi18n(b" common: %(nb-common-remote)9d\n" % data)
1046 ui.writenoi18n(b" common: %(nb-common-remote)9d\n" % data)
1047 ui.writenoi18n(b" unknown: %(nb-remote-unknown)9d\n" % data)
1047 ui.writenoi18n(b" unknown: %(nb-remote-unknown)9d\n" % data)
1048 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1048 ui.writenoi18n(b"local changesets: %(nb-revs)9d\n" % data)
1049 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1049 ui.writenoi18n(b" common: %(nb-revs-common)9d\n" % data)
1050 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1050 ui.writenoi18n(b" missing: %(nb-revs-missing)9d\n" % data)
1051
1051
1052 if ui.verbose:
1052 if ui.verbose:
1053 ui.writenoi18n(
1053 ui.writenoi18n(
1054 b"common heads: %s\n" % b" ".join(sorted(short(n) for n in common))
1054 b"common heads: %s\n" % b" ".join(sorted(short(n) for n in common))
1055 )
1055 )
1056
1056
1057
1057
1058 _chunksize = 4 << 10
1058 _chunksize = 4 << 10
1059
1059
1060
1060
1061 @command(
1061 @command(
1062 b'debugdownload', [(b'o', b'output', b'', _(b'path')),], optionalrepo=True
1062 b'debugdownload', [(b'o', b'output', b'', _(b'path')),], optionalrepo=True
1063 )
1063 )
1064 def debugdownload(ui, repo, url, output=None, **opts):
1064 def debugdownload(ui, repo, url, output=None, **opts):
1065 """download a resource using Mercurial logic and config
1065 """download a resource using Mercurial logic and config
1066 """
1066 """
1067 fh = urlmod.open(ui, url, output)
1067 fh = urlmod.open(ui, url, output)
1068
1068
1069 dest = ui
1069 dest = ui
1070 if output:
1070 if output:
1071 dest = open(output, b"wb", _chunksize)
1071 dest = open(output, b"wb", _chunksize)
1072 try:
1072 try:
1073 data = fh.read(_chunksize)
1073 data = fh.read(_chunksize)
1074 while data:
1074 while data:
1075 dest.write(data)
1075 dest.write(data)
1076 data = fh.read(_chunksize)
1076 data = fh.read(_chunksize)
1077 finally:
1077 finally:
1078 if output:
1078 if output:
1079 dest.close()
1079 dest.close()
1080
1080
1081
1081
1082 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1082 @command(b'debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
1083 def debugextensions(ui, repo, **opts):
1083 def debugextensions(ui, repo, **opts):
1084 '''show information about active extensions'''
1084 '''show information about active extensions'''
1085 opts = pycompat.byteskwargs(opts)
1085 opts = pycompat.byteskwargs(opts)
1086 exts = extensions.extensions(ui)
1086 exts = extensions.extensions(ui)
1087 hgver = util.version()
1087 hgver = util.version()
1088 fm = ui.formatter(b'debugextensions', opts)
1088 fm = ui.formatter(b'debugextensions', opts)
1089 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1089 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
1090 isinternal = extensions.ismoduleinternal(extmod)
1090 isinternal = extensions.ismoduleinternal(extmod)
1091 extsource = None
1091 extsource = None
1092
1092
1093 if util.safehasattr(extmod, '__file__'):
1093 if util.safehasattr(extmod, '__file__'):
1094 extsource = pycompat.fsencode(extmod.__file__)
1094 extsource = pycompat.fsencode(extmod.__file__)
1095 elif getattr(sys, 'oxidized', False):
1095 elif getattr(sys, 'oxidized', False):
1096 extsource = pycompat.sysexecutable
1096 extsource = pycompat.sysexecutable
1097 if isinternal:
1097 if isinternal:
1098 exttestedwith = [] # never expose magic string to users
1098 exttestedwith = [] # never expose magic string to users
1099 else:
1099 else:
1100 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1100 exttestedwith = getattr(extmod, 'testedwith', b'').split()
1101 extbuglink = getattr(extmod, 'buglink', None)
1101 extbuglink = getattr(extmod, 'buglink', None)
1102
1102
1103 fm.startitem()
1103 fm.startitem()
1104
1104
1105 if ui.quiet or ui.verbose:
1105 if ui.quiet or ui.verbose:
1106 fm.write(b'name', b'%s\n', extname)
1106 fm.write(b'name', b'%s\n', extname)
1107 else:
1107 else:
1108 fm.write(b'name', b'%s', extname)
1108 fm.write(b'name', b'%s', extname)
1109 if isinternal or hgver in exttestedwith:
1109 if isinternal or hgver in exttestedwith:
1110 fm.plain(b'\n')
1110 fm.plain(b'\n')
1111 elif not exttestedwith:
1111 elif not exttestedwith:
1112 fm.plain(_(b' (untested!)\n'))
1112 fm.plain(_(b' (untested!)\n'))
1113 else:
1113 else:
1114 lasttestedversion = exttestedwith[-1]
1114 lasttestedversion = exttestedwith[-1]
1115 fm.plain(b' (%s!)\n' % lasttestedversion)
1115 fm.plain(b' (%s!)\n' % lasttestedversion)
1116
1116
1117 fm.condwrite(
1117 fm.condwrite(
1118 ui.verbose and extsource,
1118 ui.verbose and extsource,
1119 b'source',
1119 b'source',
1120 _(b' location: %s\n'),
1120 _(b' location: %s\n'),
1121 extsource or b"",
1121 extsource or b"",
1122 )
1122 )
1123
1123
1124 if ui.verbose:
1124 if ui.verbose:
1125 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1125 fm.plain(_(b' bundled: %s\n') % [b'no', b'yes'][isinternal])
1126 fm.data(bundled=isinternal)
1126 fm.data(bundled=isinternal)
1127
1127
1128 fm.condwrite(
1128 fm.condwrite(
1129 ui.verbose and exttestedwith,
1129 ui.verbose and exttestedwith,
1130 b'testedwith',
1130 b'testedwith',
1131 _(b' tested with: %s\n'),
1131 _(b' tested with: %s\n'),
1132 fm.formatlist(exttestedwith, name=b'ver'),
1132 fm.formatlist(exttestedwith, name=b'ver'),
1133 )
1133 )
1134
1134
1135 fm.condwrite(
1135 fm.condwrite(
1136 ui.verbose and extbuglink,
1136 ui.verbose and extbuglink,
1137 b'buglink',
1137 b'buglink',
1138 _(b' bug reporting: %s\n'),
1138 _(b' bug reporting: %s\n'),
1139 extbuglink or b"",
1139 extbuglink or b"",
1140 )
1140 )
1141
1141
1142 fm.end()
1142 fm.end()
1143
1143
1144
1144
1145 @command(
1145 @command(
1146 b'debugfileset',
1146 b'debugfileset',
1147 [
1147 [
1148 (
1148 (
1149 b'r',
1149 b'r',
1150 b'rev',
1150 b'rev',
1151 b'',
1151 b'',
1152 _(b'apply the filespec on this revision'),
1152 _(b'apply the filespec on this revision'),
1153 _(b'REV'),
1153 _(b'REV'),
1154 ),
1154 ),
1155 (
1155 (
1156 b'',
1156 b'',
1157 b'all-files',
1157 b'all-files',
1158 False,
1158 False,
1159 _(b'test files from all revisions and working directory'),
1159 _(b'test files from all revisions and working directory'),
1160 ),
1160 ),
1161 (
1161 (
1162 b's',
1162 b's',
1163 b'show-matcher',
1163 b'show-matcher',
1164 None,
1164 None,
1165 _(b'print internal representation of matcher'),
1165 _(b'print internal representation of matcher'),
1166 ),
1166 ),
1167 (
1167 (
1168 b'p',
1168 b'p',
1169 b'show-stage',
1169 b'show-stage',
1170 [],
1170 [],
1171 _(b'print parsed tree at the given stage'),
1171 _(b'print parsed tree at the given stage'),
1172 _(b'NAME'),
1172 _(b'NAME'),
1173 ),
1173 ),
1174 ],
1174 ],
1175 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1175 _(b'[-r REV] [--all-files] [OPTION]... FILESPEC'),
1176 )
1176 )
1177 def debugfileset(ui, repo, expr, **opts):
1177 def debugfileset(ui, repo, expr, **opts):
1178 '''parse and apply a fileset specification'''
1178 '''parse and apply a fileset specification'''
1179 from . import fileset
1179 from . import fileset
1180
1180
1181 fileset.symbols # force import of fileset so we have predicates to optimize
1181 fileset.symbols # force import of fileset so we have predicates to optimize
1182 opts = pycompat.byteskwargs(opts)
1182 opts = pycompat.byteskwargs(opts)
1183 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1183 ctx = scmutil.revsingle(repo, opts.get(b'rev'), None)
1184
1184
1185 stages = [
1185 stages = [
1186 (b'parsed', pycompat.identity),
1186 (b'parsed', pycompat.identity),
1187 (b'analyzed', filesetlang.analyze),
1187 (b'analyzed', filesetlang.analyze),
1188 (b'optimized', filesetlang.optimize),
1188 (b'optimized', filesetlang.optimize),
1189 ]
1189 ]
1190 stagenames = {n for n, f in stages}
1190 stagenames = {n for n, f in stages}
1191
1191
1192 showalways = set()
1192 showalways = set()
1193 if ui.verbose and not opts[b'show_stage']:
1193 if ui.verbose and not opts[b'show_stage']:
1194 # show parsed tree by --verbose (deprecated)
1194 # show parsed tree by --verbose (deprecated)
1195 showalways.add(b'parsed')
1195 showalways.add(b'parsed')
1196 if opts[b'show_stage'] == [b'all']:
1196 if opts[b'show_stage'] == [b'all']:
1197 showalways.update(stagenames)
1197 showalways.update(stagenames)
1198 else:
1198 else:
1199 for n in opts[b'show_stage']:
1199 for n in opts[b'show_stage']:
1200 if n not in stagenames:
1200 if n not in stagenames:
1201 raise error.Abort(_(b'invalid stage name: %s') % n)
1201 raise error.Abort(_(b'invalid stage name: %s') % n)
1202 showalways.update(opts[b'show_stage'])
1202 showalways.update(opts[b'show_stage'])
1203
1203
1204 tree = filesetlang.parse(expr)
1204 tree = filesetlang.parse(expr)
1205 for n, f in stages:
1205 for n, f in stages:
1206 tree = f(tree)
1206 tree = f(tree)
1207 if n in showalways:
1207 if n in showalways:
1208 if opts[b'show_stage'] or n != b'parsed':
1208 if opts[b'show_stage'] or n != b'parsed':
1209 ui.write(b"* %s:\n" % n)
1209 ui.write(b"* %s:\n" % n)
1210 ui.write(filesetlang.prettyformat(tree), b"\n")
1210 ui.write(filesetlang.prettyformat(tree), b"\n")
1211
1211
1212 files = set()
1212 files = set()
1213 if opts[b'all_files']:
1213 if opts[b'all_files']:
1214 for r in repo:
1214 for r in repo:
1215 c = repo[r]
1215 c = repo[r]
1216 files.update(c.files())
1216 files.update(c.files())
1217 files.update(c.substate)
1217 files.update(c.substate)
1218 if opts[b'all_files'] or ctx.rev() is None:
1218 if opts[b'all_files'] or ctx.rev() is None:
1219 wctx = repo[None]
1219 wctx = repo[None]
1220 files.update(
1220 files.update(
1221 repo.dirstate.walk(
1221 repo.dirstate.walk(
1222 scmutil.matchall(repo),
1222 scmutil.matchall(repo),
1223 subrepos=list(wctx.substate),
1223 subrepos=list(wctx.substate),
1224 unknown=True,
1224 unknown=True,
1225 ignored=True,
1225 ignored=True,
1226 )
1226 )
1227 )
1227 )
1228 files.update(wctx.substate)
1228 files.update(wctx.substate)
1229 else:
1229 else:
1230 files.update(ctx.files())
1230 files.update(ctx.files())
1231 files.update(ctx.substate)
1231 files.update(ctx.substate)
1232
1232
1233 m = ctx.matchfileset(repo.getcwd(), expr)
1233 m = ctx.matchfileset(repo.getcwd(), expr)
1234 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1234 if opts[b'show_matcher'] or (opts[b'show_matcher'] is None and ui.verbose):
1235 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1235 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
1236 for f in sorted(files):
1236 for f in sorted(files):
1237 if not m(f):
1237 if not m(f):
1238 continue
1238 continue
1239 ui.write(b"%s\n" % f)
1239 ui.write(b"%s\n" % f)
1240
1240
1241
1241
1242 @command(b'debugformat', [] + cmdutil.formatteropts)
1242 @command(b'debugformat', [] + cmdutil.formatteropts)
1243 def debugformat(ui, repo, **opts):
1243 def debugformat(ui, repo, **opts):
1244 """display format information about the current repository
1244 """display format information about the current repository
1245
1245
1246 Use --verbose to get extra information about current config value and
1246 Use --verbose to get extra information about current config value and
1247 Mercurial default."""
1247 Mercurial default."""
1248 opts = pycompat.byteskwargs(opts)
1248 opts = pycompat.byteskwargs(opts)
1249 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1249 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
1250 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1250 maxvariantlength = max(len(b'format-variant'), maxvariantlength)
1251
1251
1252 def makeformatname(name):
1252 def makeformatname(name):
1253 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1253 return b'%s:' + (b' ' * (maxvariantlength - len(name)))
1254
1254
1255 fm = ui.formatter(b'debugformat', opts)
1255 fm = ui.formatter(b'debugformat', opts)
1256 if fm.isplain():
1256 if fm.isplain():
1257
1257
1258 def formatvalue(value):
1258 def formatvalue(value):
1259 if util.safehasattr(value, b'startswith'):
1259 if util.safehasattr(value, b'startswith'):
1260 return value
1260 return value
1261 if value:
1261 if value:
1262 return b'yes'
1262 return b'yes'
1263 else:
1263 else:
1264 return b'no'
1264 return b'no'
1265
1265
1266 else:
1266 else:
1267 formatvalue = pycompat.identity
1267 formatvalue = pycompat.identity
1268
1268
1269 fm.plain(b'format-variant')
1269 fm.plain(b'format-variant')
1270 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1270 fm.plain(b' ' * (maxvariantlength - len(b'format-variant')))
1271 fm.plain(b' repo')
1271 fm.plain(b' repo')
1272 if ui.verbose:
1272 if ui.verbose:
1273 fm.plain(b' config default')
1273 fm.plain(b' config default')
1274 fm.plain(b'\n')
1274 fm.plain(b'\n')
1275 for fv in upgrade.allformatvariant:
1275 for fv in upgrade.allformatvariant:
1276 fm.startitem()
1276 fm.startitem()
1277 repovalue = fv.fromrepo(repo)
1277 repovalue = fv.fromrepo(repo)
1278 configvalue = fv.fromconfig(repo)
1278 configvalue = fv.fromconfig(repo)
1279
1279
1280 if repovalue != configvalue:
1280 if repovalue != configvalue:
1281 namelabel = b'formatvariant.name.mismatchconfig'
1281 namelabel = b'formatvariant.name.mismatchconfig'
1282 repolabel = b'formatvariant.repo.mismatchconfig'
1282 repolabel = b'formatvariant.repo.mismatchconfig'
1283 elif repovalue != fv.default:
1283 elif repovalue != fv.default:
1284 namelabel = b'formatvariant.name.mismatchdefault'
1284 namelabel = b'formatvariant.name.mismatchdefault'
1285 repolabel = b'formatvariant.repo.mismatchdefault'
1285 repolabel = b'formatvariant.repo.mismatchdefault'
1286 else:
1286 else:
1287 namelabel = b'formatvariant.name.uptodate'
1287 namelabel = b'formatvariant.name.uptodate'
1288 repolabel = b'formatvariant.repo.uptodate'
1288 repolabel = b'formatvariant.repo.uptodate'
1289
1289
1290 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1290 fm.write(b'name', makeformatname(fv.name), fv.name, label=namelabel)
1291 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1291 fm.write(b'repo', b' %3s', formatvalue(repovalue), label=repolabel)
1292 if fv.default != configvalue:
1292 if fv.default != configvalue:
1293 configlabel = b'formatvariant.config.special'
1293 configlabel = b'formatvariant.config.special'
1294 else:
1294 else:
1295 configlabel = b'formatvariant.config.default'
1295 configlabel = b'formatvariant.config.default'
1296 fm.condwrite(
1296 fm.condwrite(
1297 ui.verbose,
1297 ui.verbose,
1298 b'config',
1298 b'config',
1299 b' %6s',
1299 b' %6s',
1300 formatvalue(configvalue),
1300 formatvalue(configvalue),
1301 label=configlabel,
1301 label=configlabel,
1302 )
1302 )
1303 fm.condwrite(
1303 fm.condwrite(
1304 ui.verbose,
1304 ui.verbose,
1305 b'default',
1305 b'default',
1306 b' %7s',
1306 b' %7s',
1307 formatvalue(fv.default),
1307 formatvalue(fv.default),
1308 label=b'formatvariant.default',
1308 label=b'formatvariant.default',
1309 )
1309 )
1310 fm.plain(b'\n')
1310 fm.plain(b'\n')
1311 fm.end()
1311 fm.end()
1312
1312
1313
1313
1314 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1314 @command(b'debugfsinfo', [], _(b'[PATH]'), norepo=True)
1315 def debugfsinfo(ui, path=b"."):
1315 def debugfsinfo(ui, path=b"."):
1316 """show information detected about current filesystem"""
1316 """show information detected about current filesystem"""
1317 ui.writenoi18n(b'path: %s\n' % path)
1317 ui.writenoi18n(b'path: %s\n' % path)
1318 ui.writenoi18n(
1318 ui.writenoi18n(
1319 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1319 b'mounted on: %s\n' % (util.getfsmountpoint(path) or b'(unknown)')
1320 )
1320 )
1321 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1321 ui.writenoi18n(b'exec: %s\n' % (util.checkexec(path) and b'yes' or b'no'))
1322 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1322 ui.writenoi18n(b'fstype: %s\n' % (util.getfstype(path) or b'(unknown)'))
1323 ui.writenoi18n(
1323 ui.writenoi18n(
1324 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1324 b'symlink: %s\n' % (util.checklink(path) and b'yes' or b'no')
1325 )
1325 )
1326 ui.writenoi18n(
1326 ui.writenoi18n(
1327 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1327 b'hardlink: %s\n' % (util.checknlink(path) and b'yes' or b'no')
1328 )
1328 )
1329 casesensitive = b'(unknown)'
1329 casesensitive = b'(unknown)'
1330 try:
1330 try:
1331 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1331 with pycompat.namedtempfile(prefix=b'.debugfsinfo', dir=path) as f:
1332 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1332 casesensitive = util.fscasesensitive(f.name) and b'yes' or b'no'
1333 except OSError:
1333 except OSError:
1334 pass
1334 pass
1335 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1335 ui.writenoi18n(b'case-sensitive: %s\n' % casesensitive)
1336
1336
1337
1337
1338 @command(
1338 @command(
1339 b'debuggetbundle',
1339 b'debuggetbundle',
1340 [
1340 [
1341 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1341 (b'H', b'head', [], _(b'id of head node'), _(b'ID')),
1342 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1342 (b'C', b'common', [], _(b'id of common node'), _(b'ID')),
1343 (
1343 (
1344 b't',
1344 b't',
1345 b'type',
1345 b'type',
1346 b'bzip2',
1346 b'bzip2',
1347 _(b'bundle compression type to use'),
1347 _(b'bundle compression type to use'),
1348 _(b'TYPE'),
1348 _(b'TYPE'),
1349 ),
1349 ),
1350 ],
1350 ],
1351 _(b'REPO FILE [-H|-C ID]...'),
1351 _(b'REPO FILE [-H|-C ID]...'),
1352 norepo=True,
1352 norepo=True,
1353 )
1353 )
1354 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1354 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
1355 """retrieves a bundle from a repo
1355 """retrieves a bundle from a repo
1356
1356
1357 Every ID must be a full-length hex node id string. Saves the bundle to the
1357 Every ID must be a full-length hex node id string. Saves the bundle to the
1358 given file.
1358 given file.
1359 """
1359 """
1360 opts = pycompat.byteskwargs(opts)
1360 opts = pycompat.byteskwargs(opts)
1361 repo = hg.peer(ui, opts, repopath)
1361 repo = hg.peer(ui, opts, repopath)
1362 if not repo.capable(b'getbundle'):
1362 if not repo.capable(b'getbundle'):
1363 raise error.Abort(b"getbundle() not supported by target repository")
1363 raise error.Abort(b"getbundle() not supported by target repository")
1364 args = {}
1364 args = {}
1365 if common:
1365 if common:
1366 args['common'] = [bin(s) for s in common]
1366 args['common'] = [bin(s) for s in common]
1367 if head:
1367 if head:
1368 args['heads'] = [bin(s) for s in head]
1368 args['heads'] = [bin(s) for s in head]
1369 # TODO: get desired bundlecaps from command line.
1369 # TODO: get desired bundlecaps from command line.
1370 args['bundlecaps'] = None
1370 args['bundlecaps'] = None
1371 bundle = repo.getbundle(b'debug', **args)
1371 bundle = repo.getbundle(b'debug', **args)
1372
1372
1373 bundletype = opts.get(b'type', b'bzip2').lower()
1373 bundletype = opts.get(b'type', b'bzip2').lower()
1374 btypes = {
1374 btypes = {
1375 b'none': b'HG10UN',
1375 b'none': b'HG10UN',
1376 b'bzip2': b'HG10BZ',
1376 b'bzip2': b'HG10BZ',
1377 b'gzip': b'HG10GZ',
1377 b'gzip': b'HG10GZ',
1378 b'bundle2': b'HG20',
1378 b'bundle2': b'HG20',
1379 }
1379 }
1380 bundletype = btypes.get(bundletype)
1380 bundletype = btypes.get(bundletype)
1381 if bundletype not in bundle2.bundletypes:
1381 if bundletype not in bundle2.bundletypes:
1382 raise error.Abort(_(b'unknown bundle type specified with --type'))
1382 raise error.Abort(_(b'unknown bundle type specified with --type'))
1383 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1383 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1384
1384
1385
1385
1386 @command(b'debugignore', [], b'[FILE]')
1386 @command(b'debugignore', [], b'[FILE]')
1387 def debugignore(ui, repo, *files, **opts):
1387 def debugignore(ui, repo, *files, **opts):
1388 """display the combined ignore pattern and information about ignored files
1388 """display the combined ignore pattern and information about ignored files
1389
1389
1390 With no argument display the combined ignore pattern.
1390 With no argument display the combined ignore pattern.
1391
1391
1392 Given space separated file names, shows if the given file is ignored and
1392 Given space separated file names, shows if the given file is ignored and
1393 if so, show the ignore rule (file and line number) that matched it.
1393 if so, show the ignore rule (file and line number) that matched it.
1394 """
1394 """
1395 ignore = repo.dirstate._ignore
1395 ignore = repo.dirstate._ignore
1396 if not files:
1396 if not files:
1397 # Show all the patterns
1397 # Show all the patterns
1398 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1398 ui.write(b"%s\n" % pycompat.byterepr(ignore))
1399 else:
1399 else:
1400 m = scmutil.match(repo[None], pats=files)
1400 m = scmutil.match(repo[None], pats=files)
1401 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1401 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1402 for f in m.files():
1402 for f in m.files():
1403 nf = util.normpath(f)
1403 nf = util.normpath(f)
1404 ignored = None
1404 ignored = None
1405 ignoredata = None
1405 ignoredata = None
1406 if nf != b'.':
1406 if nf != b'.':
1407 if ignore(nf):
1407 if ignore(nf):
1408 ignored = nf
1408 ignored = nf
1409 ignoredata = repo.dirstate._ignorefileandline(nf)
1409 ignoredata = repo.dirstate._ignorefileandline(nf)
1410 else:
1410 else:
1411 for p in pathutil.finddirs(nf):
1411 for p in pathutil.finddirs(nf):
1412 if ignore(p):
1412 if ignore(p):
1413 ignored = p
1413 ignored = p
1414 ignoredata = repo.dirstate._ignorefileandline(p)
1414 ignoredata = repo.dirstate._ignorefileandline(p)
1415 break
1415 break
1416 if ignored:
1416 if ignored:
1417 if ignored == nf:
1417 if ignored == nf:
1418 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1418 ui.write(_(b"%s is ignored\n") % uipathfn(f))
1419 else:
1419 else:
1420 ui.write(
1420 ui.write(
1421 _(
1421 _(
1422 b"%s is ignored because of "
1422 b"%s is ignored because of "
1423 b"containing directory %s\n"
1423 b"containing directory %s\n"
1424 )
1424 )
1425 % (uipathfn(f), ignored)
1425 % (uipathfn(f), ignored)
1426 )
1426 )
1427 ignorefile, lineno, line = ignoredata
1427 ignorefile, lineno, line = ignoredata
1428 ui.write(
1428 ui.write(
1429 _(b"(ignore rule in %s, line %d: '%s')\n")
1429 _(b"(ignore rule in %s, line %d: '%s')\n")
1430 % (ignorefile, lineno, line)
1430 % (ignorefile, lineno, line)
1431 )
1431 )
1432 else:
1432 else:
1433 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1433 ui.write(_(b"%s is not ignored\n") % uipathfn(f))
1434
1434
1435
1435
1436 @command(
1436 @command(
1437 b'debugindex',
1437 b'debugindex',
1438 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1438 cmdutil.debugrevlogopts + cmdutil.formatteropts,
1439 _(b'-c|-m|FILE'),
1439 _(b'-c|-m|FILE'),
1440 )
1440 )
1441 def debugindex(ui, repo, file_=None, **opts):
1441 def debugindex(ui, repo, file_=None, **opts):
1442 """dump index data for a storage primitive"""
1442 """dump index data for a storage primitive"""
1443 opts = pycompat.byteskwargs(opts)
1443 opts = pycompat.byteskwargs(opts)
1444 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1444 store = cmdutil.openstorage(repo, b'debugindex', file_, opts)
1445
1445
1446 if ui.debugflag:
1446 if ui.debugflag:
1447 shortfn = hex
1447 shortfn = hex
1448 else:
1448 else:
1449 shortfn = short
1449 shortfn = short
1450
1450
1451 idlen = 12
1451 idlen = 12
1452 for i in store:
1452 for i in store:
1453 idlen = len(shortfn(store.node(i)))
1453 idlen = len(shortfn(store.node(i)))
1454 break
1454 break
1455
1455
1456 fm = ui.formatter(b'debugindex', opts)
1456 fm = ui.formatter(b'debugindex', opts)
1457 fm.plain(
1457 fm.plain(
1458 b' rev linkrev %s %s p2\n'
1458 b' rev linkrev %s %s p2\n'
1459 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1459 % (b'nodeid'.ljust(idlen), b'p1'.ljust(idlen))
1460 )
1460 )
1461
1461
1462 for rev in store:
1462 for rev in store:
1463 node = store.node(rev)
1463 node = store.node(rev)
1464 parents = store.parents(node)
1464 parents = store.parents(node)
1465
1465
1466 fm.startitem()
1466 fm.startitem()
1467 fm.write(b'rev', b'%6d ', rev)
1467 fm.write(b'rev', b'%6d ', rev)
1468 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1468 fm.write(b'linkrev', b'%7d ', store.linkrev(rev))
1469 fm.write(b'node', b'%s ', shortfn(node))
1469 fm.write(b'node', b'%s ', shortfn(node))
1470 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1470 fm.write(b'p1', b'%s ', shortfn(parents[0]))
1471 fm.write(b'p2', b'%s', shortfn(parents[1]))
1471 fm.write(b'p2', b'%s', shortfn(parents[1]))
1472 fm.plain(b'\n')
1472 fm.plain(b'\n')
1473
1473
1474 fm.end()
1474 fm.end()
1475
1475
1476
1476
1477 @command(
1477 @command(
1478 b'debugindexdot',
1478 b'debugindexdot',
1479 cmdutil.debugrevlogopts,
1479 cmdutil.debugrevlogopts,
1480 _(b'-c|-m|FILE'),
1480 _(b'-c|-m|FILE'),
1481 optionalrepo=True,
1481 optionalrepo=True,
1482 )
1482 )
1483 def debugindexdot(ui, repo, file_=None, **opts):
1483 def debugindexdot(ui, repo, file_=None, **opts):
1484 """dump an index DAG as a graphviz dot file"""
1484 """dump an index DAG as a graphviz dot file"""
1485 opts = pycompat.byteskwargs(opts)
1485 opts = pycompat.byteskwargs(opts)
1486 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1486 r = cmdutil.openstorage(repo, b'debugindexdot', file_, opts)
1487 ui.writenoi18n(b"digraph G {\n")
1487 ui.writenoi18n(b"digraph G {\n")
1488 for i in r:
1488 for i in r:
1489 node = r.node(i)
1489 node = r.node(i)
1490 pp = r.parents(node)
1490 pp = r.parents(node)
1491 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1491 ui.write(b"\t%d -> %d\n" % (r.rev(pp[0]), i))
1492 if pp[1] != nullid:
1492 if pp[1] != nullid:
1493 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1493 ui.write(b"\t%d -> %d\n" % (r.rev(pp[1]), i))
1494 ui.write(b"}\n")
1494 ui.write(b"}\n")
1495
1495
1496
1496
1497 @command(b'debugindexstats', [])
1497 @command(b'debugindexstats', [])
1498 def debugindexstats(ui, repo):
1498 def debugindexstats(ui, repo):
1499 """show stats related to the changelog index"""
1499 """show stats related to the changelog index"""
1500 repo.changelog.shortest(nullid, 1)
1500 repo.changelog.shortest(nullid, 1)
1501 index = repo.changelog.index
1501 index = repo.changelog.index
1502 if not util.safehasattr(index, b'stats'):
1502 if not util.safehasattr(index, b'stats'):
1503 raise error.Abort(_(b'debugindexstats only works with native code'))
1503 raise error.Abort(_(b'debugindexstats only works with native code'))
1504 for k, v in sorted(index.stats().items()):
1504 for k, v in sorted(index.stats().items()):
1505 ui.write(b'%s: %d\n' % (k, v))
1505 ui.write(b'%s: %d\n' % (k, v))
1506
1506
1507
1507
1508 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1508 @command(b'debuginstall', [] + cmdutil.formatteropts, b'', norepo=True)
1509 def debuginstall(ui, **opts):
1509 def debuginstall(ui, **opts):
1510 '''test Mercurial installation
1510 '''test Mercurial installation
1511
1511
1512 Returns 0 on success.
1512 Returns 0 on success.
1513 '''
1513 '''
1514 opts = pycompat.byteskwargs(opts)
1514 opts = pycompat.byteskwargs(opts)
1515
1515
1516 problems = 0
1516 problems = 0
1517
1517
1518 fm = ui.formatter(b'debuginstall', opts)
1518 fm = ui.formatter(b'debuginstall', opts)
1519 fm.startitem()
1519 fm.startitem()
1520
1520
1521 # encoding might be unknown or wrong. don't translate these messages.
1521 # encoding might be unknown or wrong. don't translate these messages.
1522 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1522 fm.write(b'encoding', b"checking encoding (%s)...\n", encoding.encoding)
1523 err = None
1523 err = None
1524 try:
1524 try:
1525 codecs.lookup(pycompat.sysstr(encoding.encoding))
1525 codecs.lookup(pycompat.sysstr(encoding.encoding))
1526 except LookupError as inst:
1526 except LookupError as inst:
1527 err = stringutil.forcebytestr(inst)
1527 err = stringutil.forcebytestr(inst)
1528 problems += 1
1528 problems += 1
1529 fm.condwrite(
1529 fm.condwrite(
1530 err,
1530 err,
1531 b'encodingerror',
1531 b'encodingerror',
1532 b" %s\n (check that your locale is properly set)\n",
1532 b" %s\n (check that your locale is properly set)\n",
1533 err,
1533 err,
1534 )
1534 )
1535
1535
1536 # Python
1536 # Python
1537 pythonlib = None
1537 pythonlib = None
1538 if util.safehasattr(os, '__file__'):
1538 if util.safehasattr(os, '__file__'):
1539 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1539 pythonlib = os.path.dirname(pycompat.fsencode(os.__file__))
1540 elif getattr(sys, 'oxidized', False):
1540 elif getattr(sys, 'oxidized', False):
1541 pythonlib = pycompat.sysexecutable
1541 pythonlib = pycompat.sysexecutable
1542
1542
1543 fm.write(
1543 fm.write(
1544 b'pythonexe',
1544 b'pythonexe',
1545 _(b"checking Python executable (%s)\n"),
1545 _(b"checking Python executable (%s)\n"),
1546 pycompat.sysexecutable or _(b"unknown"),
1546 pycompat.sysexecutable or _(b"unknown"),
1547 )
1547 )
1548 fm.write(
1548 fm.write(
1549 b'pythonimplementation',
1549 b'pythonimplementation',
1550 _(b"checking Python implementation (%s)\n"),
1550 _(b"checking Python implementation (%s)\n"),
1551 pycompat.sysbytes(platform.python_implementation()),
1551 pycompat.sysbytes(platform.python_implementation()),
1552 )
1552 )
1553 fm.write(
1553 fm.write(
1554 b'pythonver',
1554 b'pythonver',
1555 _(b"checking Python version (%s)\n"),
1555 _(b"checking Python version (%s)\n"),
1556 (b"%d.%d.%d" % sys.version_info[:3]),
1556 (b"%d.%d.%d" % sys.version_info[:3]),
1557 )
1557 )
1558 fm.write(
1558 fm.write(
1559 b'pythonlib',
1559 b'pythonlib',
1560 _(b"checking Python lib (%s)...\n"),
1560 _(b"checking Python lib (%s)...\n"),
1561 pythonlib or _(b"unknown"),
1561 pythonlib or _(b"unknown"),
1562 )
1562 )
1563
1563
1564 try:
1564 try:
1565 from . import rustext
1565 from . import rustext
1566
1566
1567 rustext.__doc__ # trigger lazy import
1567 rustext.__doc__ # trigger lazy import
1568 except ImportError:
1568 except ImportError:
1569 rustext = None
1569 rustext = None
1570
1570
1571 security = set(sslutil.supportedprotocols)
1571 security = set(sslutil.supportedprotocols)
1572 if sslutil.hassni:
1572 if sslutil.hassni:
1573 security.add(b'sni')
1573 security.add(b'sni')
1574
1574
1575 fm.write(
1575 fm.write(
1576 b'pythonsecurity',
1576 b'pythonsecurity',
1577 _(b"checking Python security support (%s)\n"),
1577 _(b"checking Python security support (%s)\n"),
1578 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1578 fm.formatlist(sorted(security), name=b'protocol', fmt=b'%s', sep=b','),
1579 )
1579 )
1580
1580
1581 # These are warnings, not errors. So don't increment problem count. This
1581 # These are warnings, not errors. So don't increment problem count. This
1582 # may change in the future.
1582 # may change in the future.
1583 if b'tls1.2' not in security:
1583 if b'tls1.2' not in security:
1584 fm.plain(
1584 fm.plain(
1585 _(
1585 _(
1586 b' TLS 1.2 not supported by Python install; '
1586 b' TLS 1.2 not supported by Python install; '
1587 b'network connections lack modern security\n'
1587 b'network connections lack modern security\n'
1588 )
1588 )
1589 )
1589 )
1590 if b'sni' not in security:
1590 if b'sni' not in security:
1591 fm.plain(
1591 fm.plain(
1592 _(
1592 _(
1593 b' SNI not supported by Python install; may have '
1593 b' SNI not supported by Python install; may have '
1594 b'connectivity issues with some servers\n'
1594 b'connectivity issues with some servers\n'
1595 )
1595 )
1596 )
1596 )
1597
1597
1598 fm.plain(
1598 fm.plain(
1599 _(
1599 _(
1600 b"checking Rust extensions (%s)\n"
1600 b"checking Rust extensions (%s)\n"
1601 % (b'missing' if rustext is None else b'installed')
1601 % (b'missing' if rustext is None else b'installed')
1602 ),
1602 ),
1603 )
1603 )
1604
1604
1605 # TODO print CA cert info
1605 # TODO print CA cert info
1606
1606
1607 # hg version
1607 # hg version
1608 hgver = util.version()
1608 hgver = util.version()
1609 fm.write(
1609 fm.write(
1610 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1610 b'hgver', _(b"checking Mercurial version (%s)\n"), hgver.split(b'+')[0]
1611 )
1611 )
1612 fm.write(
1612 fm.write(
1613 b'hgverextra',
1613 b'hgverextra',
1614 _(b"checking Mercurial custom build (%s)\n"),
1614 _(b"checking Mercurial custom build (%s)\n"),
1615 b'+'.join(hgver.split(b'+')[1:]),
1615 b'+'.join(hgver.split(b'+')[1:]),
1616 )
1616 )
1617
1617
1618 # compiled modules
1618 # compiled modules
1619 hgmodules = None
1619 hgmodules = None
1620 if util.safehasattr(sys.modules[__name__], '__file__'):
1620 if util.safehasattr(sys.modules[__name__], '__file__'):
1621 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1621 hgmodules = os.path.dirname(pycompat.fsencode(__file__))
1622 elif getattr(sys, 'oxidized', False):
1622 elif getattr(sys, 'oxidized', False):
1623 hgmodules = pycompat.sysexecutable
1623 hgmodules = pycompat.sysexecutable
1624
1624
1625 fm.write(
1625 fm.write(
1626 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1626 b'hgmodulepolicy', _(b"checking module policy (%s)\n"), policy.policy
1627 )
1627 )
1628 fm.write(
1628 fm.write(
1629 b'hgmodules',
1629 b'hgmodules',
1630 _(b"checking installed modules (%s)...\n"),
1630 _(b"checking installed modules (%s)...\n"),
1631 hgmodules or _(b"unknown"),
1631 hgmodules or _(b"unknown"),
1632 )
1632 )
1633
1633
1634 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1634 rustandc = policy.policy in (b'rust+c', b'rust+c-allow')
1635 rustext = rustandc # for now, that's the only case
1635 rustext = rustandc # for now, that's the only case
1636 cext = policy.policy in (b'c', b'allow') or rustandc
1636 cext = policy.policy in (b'c', b'allow') or rustandc
1637 nopure = cext or rustext
1637 nopure = cext or rustext
1638 if nopure:
1638 if nopure:
1639 err = None
1639 err = None
1640 try:
1640 try:
1641 if cext:
1641 if cext:
1642 from .cext import ( # pytype: disable=import-error
1642 from .cext import ( # pytype: disable=import-error
1643 base85,
1643 base85,
1644 bdiff,
1644 bdiff,
1645 mpatch,
1645 mpatch,
1646 osutil,
1646 osutil,
1647 )
1647 )
1648
1648
1649 # quiet pyflakes
1649 # quiet pyflakes
1650 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1650 dir(bdiff), dir(mpatch), dir(base85), dir(osutil)
1651 if rustext:
1651 if rustext:
1652 from .rustext import ( # pytype: disable=import-error
1652 from .rustext import ( # pytype: disable=import-error
1653 ancestor,
1653 ancestor,
1654 dirstate,
1654 dirstate,
1655 )
1655 )
1656
1656
1657 dir(ancestor), dir(dirstate) # quiet pyflakes
1657 dir(ancestor), dir(dirstate) # quiet pyflakes
1658 except Exception as inst:
1658 except Exception as inst:
1659 err = stringutil.forcebytestr(inst)
1659 err = stringutil.forcebytestr(inst)
1660 problems += 1
1660 problems += 1
1661 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1661 fm.condwrite(err, b'extensionserror', b" %s\n", err)
1662
1662
1663 compengines = util.compengines._engines.values()
1663 compengines = util.compengines._engines.values()
1664 fm.write(
1664 fm.write(
1665 b'compengines',
1665 b'compengines',
1666 _(b'checking registered compression engines (%s)\n'),
1666 _(b'checking registered compression engines (%s)\n'),
1667 fm.formatlist(
1667 fm.formatlist(
1668 sorted(e.name() for e in compengines),
1668 sorted(e.name() for e in compengines),
1669 name=b'compengine',
1669 name=b'compengine',
1670 fmt=b'%s',
1670 fmt=b'%s',
1671 sep=b', ',
1671 sep=b', ',
1672 ),
1672 ),
1673 )
1673 )
1674 fm.write(
1674 fm.write(
1675 b'compenginesavail',
1675 b'compenginesavail',
1676 _(b'checking available compression engines (%s)\n'),
1676 _(b'checking available compression engines (%s)\n'),
1677 fm.formatlist(
1677 fm.formatlist(
1678 sorted(e.name() for e in compengines if e.available()),
1678 sorted(e.name() for e in compengines if e.available()),
1679 name=b'compengine',
1679 name=b'compengine',
1680 fmt=b'%s',
1680 fmt=b'%s',
1681 sep=b', ',
1681 sep=b', ',
1682 ),
1682 ),
1683 )
1683 )
1684 wirecompengines = compression.compengines.supportedwireengines(
1684 wirecompengines = compression.compengines.supportedwireengines(
1685 compression.SERVERROLE
1685 compression.SERVERROLE
1686 )
1686 )
1687 fm.write(
1687 fm.write(
1688 b'compenginesserver',
1688 b'compenginesserver',
1689 _(
1689 _(
1690 b'checking available compression engines '
1690 b'checking available compression engines '
1691 b'for wire protocol (%s)\n'
1691 b'for wire protocol (%s)\n'
1692 ),
1692 ),
1693 fm.formatlist(
1693 fm.formatlist(
1694 [e.name() for e in wirecompengines if e.wireprotosupport()],
1694 [e.name() for e in wirecompengines if e.wireprotosupport()],
1695 name=b'compengine',
1695 name=b'compengine',
1696 fmt=b'%s',
1696 fmt=b'%s',
1697 sep=b', ',
1697 sep=b', ',
1698 ),
1698 ),
1699 )
1699 )
1700 re2 = b'missing'
1700 re2 = b'missing'
1701 if util._re2:
1701 if util._re2:
1702 re2 = b'available'
1702 re2 = b'available'
1703 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1703 fm.plain(_(b'checking "re2" regexp engine (%s)\n') % re2)
1704 fm.data(re2=bool(util._re2))
1704 fm.data(re2=bool(util._re2))
1705
1705
1706 # templates
1706 # templates
1707 p = templater.templatedir()
1707 p = templater.templatedir()
1708 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1708 fm.write(b'templatedirs', b'checking templates (%s)...\n', p or b'')
1709 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1709 fm.condwrite(not p, b'', _(b" no template directories found\n"))
1710 if p:
1710 if p:
1711 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1711 (m, fp) = templater.try_open_template(b"map-cmdline.default")
1712 if m:
1712 if m:
1713 # template found, check if it is working
1713 # template found, check if it is working
1714 err = None
1714 err = None
1715 try:
1715 try:
1716 templater.templater.frommapfile(m)
1716 templater.templater.frommapfile(m)
1717 except Exception as inst:
1717 except Exception as inst:
1718 err = stringutil.forcebytestr(inst)
1718 err = stringutil.forcebytestr(inst)
1719 p = None
1719 p = None
1720 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1720 fm.condwrite(err, b'defaulttemplateerror', b" %s\n", err)
1721 else:
1721 else:
1722 p = None
1722 p = None
1723 fm.condwrite(
1723 fm.condwrite(
1724 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1724 p, b'defaulttemplate', _(b"checking default template (%s)\n"), m
1725 )
1725 )
1726 fm.condwrite(
1726 fm.condwrite(
1727 not m,
1727 not m,
1728 b'defaulttemplatenotfound',
1728 b'defaulttemplatenotfound',
1729 _(b" template '%s' not found\n"),
1729 _(b" template '%s' not found\n"),
1730 b"default",
1730 b"default",
1731 )
1731 )
1732 if not p:
1732 if not p:
1733 problems += 1
1733 problems += 1
1734 fm.condwrite(
1734 fm.condwrite(
1735 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1735 not p, b'', _(b" (templates seem to have been installed incorrectly)\n")
1736 )
1736 )
1737
1737
1738 # editor
1738 # editor
1739 editor = ui.geteditor()
1739 editor = ui.geteditor()
1740 editor = util.expandpath(editor)
1740 editor = util.expandpath(editor)
1741 editorbin = procutil.shellsplit(editor)[0]
1741 editorbin = procutil.shellsplit(editor)[0]
1742 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1742 fm.write(b'editor', _(b"checking commit editor... (%s)\n"), editorbin)
1743 cmdpath = procutil.findexe(editorbin)
1743 cmdpath = procutil.findexe(editorbin)
1744 fm.condwrite(
1744 fm.condwrite(
1745 not cmdpath and editor == b'vi',
1745 not cmdpath and editor == b'vi',
1746 b'vinotfound',
1746 b'vinotfound',
1747 _(
1747 _(
1748 b" No commit editor set and can't find %s in PATH\n"
1748 b" No commit editor set and can't find %s in PATH\n"
1749 b" (specify a commit editor in your configuration"
1749 b" (specify a commit editor in your configuration"
1750 b" file)\n"
1750 b" file)\n"
1751 ),
1751 ),
1752 not cmdpath and editor == b'vi' and editorbin,
1752 not cmdpath and editor == b'vi' and editorbin,
1753 )
1753 )
1754 fm.condwrite(
1754 fm.condwrite(
1755 not cmdpath and editor != b'vi',
1755 not cmdpath and editor != b'vi',
1756 b'editornotfound',
1756 b'editornotfound',
1757 _(
1757 _(
1758 b" Can't find editor '%s' in PATH\n"
1758 b" Can't find editor '%s' in PATH\n"
1759 b" (specify a commit editor in your configuration"
1759 b" (specify a commit editor in your configuration"
1760 b" file)\n"
1760 b" file)\n"
1761 ),
1761 ),
1762 not cmdpath and editorbin,
1762 not cmdpath and editorbin,
1763 )
1763 )
1764 if not cmdpath and editor != b'vi':
1764 if not cmdpath and editor != b'vi':
1765 problems += 1
1765 problems += 1
1766
1766
1767 # check username
1767 # check username
1768 username = None
1768 username = None
1769 err = None
1769 err = None
1770 try:
1770 try:
1771 username = ui.username()
1771 username = ui.username()
1772 except error.Abort as e:
1772 except error.Abort as e:
1773 err = e.message
1773 err = e.message
1774 problems += 1
1774 problems += 1
1775
1775
1776 fm.condwrite(
1776 fm.condwrite(
1777 username, b'username', _(b"checking username (%s)\n"), username
1777 username, b'username', _(b"checking username (%s)\n"), username
1778 )
1778 )
1779 fm.condwrite(
1779 fm.condwrite(
1780 err,
1780 err,
1781 b'usernameerror',
1781 b'usernameerror',
1782 _(
1782 _(
1783 b"checking username...\n %s\n"
1783 b"checking username...\n %s\n"
1784 b" (specify a username in your configuration file)\n"
1784 b" (specify a username in your configuration file)\n"
1785 ),
1785 ),
1786 err,
1786 err,
1787 )
1787 )
1788
1788
1789 for name, mod in extensions.extensions():
1789 for name, mod in extensions.extensions():
1790 handler = getattr(mod, 'debuginstall', None)
1790 handler = getattr(mod, 'debuginstall', None)
1791 if handler is not None:
1791 if handler is not None:
1792 problems += handler(ui, fm)
1792 problems += handler(ui, fm)
1793
1793
1794 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1794 fm.condwrite(not problems, b'', _(b"no problems detected\n"))
1795 if not problems:
1795 if not problems:
1796 fm.data(problems=problems)
1796 fm.data(problems=problems)
1797 fm.condwrite(
1797 fm.condwrite(
1798 problems,
1798 problems,
1799 b'problems',
1799 b'problems',
1800 _(b"%d problems detected, please check your install!\n"),
1800 _(b"%d problems detected, please check your install!\n"),
1801 problems,
1801 problems,
1802 )
1802 )
1803 fm.end()
1803 fm.end()
1804
1804
1805 return problems
1805 return problems
1806
1806
1807
1807
1808 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1808 @command(b'debugknown', [], _(b'REPO ID...'), norepo=True)
1809 def debugknown(ui, repopath, *ids, **opts):
1809 def debugknown(ui, repopath, *ids, **opts):
1810 """test whether node ids are known to a repo
1810 """test whether node ids are known to a repo
1811
1811
1812 Every ID must be a full-length hex node id string. Returns a list of 0s
1812 Every ID must be a full-length hex node id string. Returns a list of 0s
1813 and 1s indicating unknown/known.
1813 and 1s indicating unknown/known.
1814 """
1814 """
1815 opts = pycompat.byteskwargs(opts)
1815 opts = pycompat.byteskwargs(opts)
1816 repo = hg.peer(ui, opts, repopath)
1816 repo = hg.peer(ui, opts, repopath)
1817 if not repo.capable(b'known'):
1817 if not repo.capable(b'known'):
1818 raise error.Abort(b"known() not supported by target repository")
1818 raise error.Abort(b"known() not supported by target repository")
1819 flags = repo.known([bin(s) for s in ids])
1819 flags = repo.known([bin(s) for s in ids])
1820 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1820 ui.write(b"%s\n" % (b"".join([f and b"1" or b"0" for f in flags])))
1821
1821
1822
1822
1823 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1823 @command(b'debuglabelcomplete', [], _(b'LABEL...'))
1824 def debuglabelcomplete(ui, repo, *args):
1824 def debuglabelcomplete(ui, repo, *args):
1825 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1825 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1826 debugnamecomplete(ui, repo, *args)
1826 debugnamecomplete(ui, repo, *args)
1827
1827
1828
1828
1829 @command(
1829 @command(
1830 b'debuglocks',
1830 b'debuglocks',
1831 [
1831 [
1832 (b'L', b'force-lock', None, _(b'free the store lock (DANGEROUS)')),
1832 (b'L', b'force-lock', None, _(b'free the store lock (DANGEROUS)')),
1833 (
1833 (
1834 b'W',
1834 b'W',
1835 b'force-wlock',
1835 b'force-wlock',
1836 None,
1836 None,
1837 _(b'free the working state lock (DANGEROUS)'),
1837 _(b'free the working state lock (DANGEROUS)'),
1838 ),
1838 ),
1839 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1839 (b's', b'set-lock', None, _(b'set the store lock until stopped')),
1840 (
1840 (
1841 b'S',
1841 b'S',
1842 b'set-wlock',
1842 b'set-wlock',
1843 None,
1843 None,
1844 _(b'set the working state lock until stopped'),
1844 _(b'set the working state lock until stopped'),
1845 ),
1845 ),
1846 ],
1846 ],
1847 _(b'[OPTION]...'),
1847 _(b'[OPTION]...'),
1848 )
1848 )
1849 def debuglocks(ui, repo, **opts):
1849 def debuglocks(ui, repo, **opts):
1850 """show or modify state of locks
1850 """show or modify state of locks
1851
1851
1852 By default, this command will show which locks are held. This
1852 By default, this command will show which locks are held. This
1853 includes the user and process holding the lock, the amount of time
1853 includes the user and process holding the lock, the amount of time
1854 the lock has been held, and the machine name where the process is
1854 the lock has been held, and the machine name where the process is
1855 running if it's not local.
1855 running if it's not local.
1856
1856
1857 Locks protect the integrity of Mercurial's data, so should be
1857 Locks protect the integrity of Mercurial's data, so should be
1858 treated with care. System crashes or other interruptions may cause
1858 treated with care. System crashes or other interruptions may cause
1859 locks to not be properly released, though Mercurial will usually
1859 locks to not be properly released, though Mercurial will usually
1860 detect and remove such stale locks automatically.
1860 detect and remove such stale locks automatically.
1861
1861
1862 However, detecting stale locks may not always be possible (for
1862 However, detecting stale locks may not always be possible (for
1863 instance, on a shared filesystem). Removing locks may also be
1863 instance, on a shared filesystem). Removing locks may also be
1864 blocked by filesystem permissions.
1864 blocked by filesystem permissions.
1865
1865
1866 Setting a lock will prevent other commands from changing the data.
1866 Setting a lock will prevent other commands from changing the data.
1867 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1867 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1868 The set locks are removed when the command exits.
1868 The set locks are removed when the command exits.
1869
1869
1870 Returns 0 if no locks are held.
1870 Returns 0 if no locks are held.
1871
1871
1872 """
1872 """
1873
1873
1874 if opts.get('force_lock'):
1874 if opts.get('force_lock'):
1875 repo.svfs.unlink(b'lock')
1875 repo.svfs.unlink(b'lock')
1876 if opts.get('force_wlock'):
1876 if opts.get('force_wlock'):
1877 repo.vfs.unlink(b'wlock')
1877 repo.vfs.unlink(b'wlock')
1878 if opts.get('force_lock') or opts.get('force_wlock'):
1878 if opts.get('force_lock') or opts.get('force_wlock'):
1879 return 0
1879 return 0
1880
1880
1881 locks = []
1881 locks = []
1882 try:
1882 try:
1883 if opts.get('set_wlock'):
1883 if opts.get('set_wlock'):
1884 try:
1884 try:
1885 locks.append(repo.wlock(False))
1885 locks.append(repo.wlock(False))
1886 except error.LockHeld:
1886 except error.LockHeld:
1887 raise error.Abort(_(b'wlock is already held'))
1887 raise error.Abort(_(b'wlock is already held'))
1888 if opts.get('set_lock'):
1888 if opts.get('set_lock'):
1889 try:
1889 try:
1890 locks.append(repo.lock(False))
1890 locks.append(repo.lock(False))
1891 except error.LockHeld:
1891 except error.LockHeld:
1892 raise error.Abort(_(b'lock is already held'))
1892 raise error.Abort(_(b'lock is already held'))
1893 if len(locks):
1893 if len(locks):
1894 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
1894 ui.promptchoice(_(b"ready to release the lock (y)? $$ &Yes"))
1895 return 0
1895 return 0
1896 finally:
1896 finally:
1897 release(*locks)
1897 release(*locks)
1898
1898
1899 now = time.time()
1899 now = time.time()
1900 held = 0
1900 held = 0
1901
1901
1902 def report(vfs, name, method):
1902 def report(vfs, name, method):
1903 # this causes stale locks to get reaped for more accurate reporting
1903 # this causes stale locks to get reaped for more accurate reporting
1904 try:
1904 try:
1905 l = method(False)
1905 l = method(False)
1906 except error.LockHeld:
1906 except error.LockHeld:
1907 l = None
1907 l = None
1908
1908
1909 if l:
1909 if l:
1910 l.release()
1910 l.release()
1911 else:
1911 else:
1912 try:
1912 try:
1913 st = vfs.lstat(name)
1913 st = vfs.lstat(name)
1914 age = now - st[stat.ST_MTIME]
1914 age = now - st[stat.ST_MTIME]
1915 user = util.username(st.st_uid)
1915 user = util.username(st.st_uid)
1916 locker = vfs.readlock(name)
1916 locker = vfs.readlock(name)
1917 if b":" in locker:
1917 if b":" in locker:
1918 host, pid = locker.split(b':')
1918 host, pid = locker.split(b':')
1919 if host == socket.gethostname():
1919 if host == socket.gethostname():
1920 locker = b'user %s, process %s' % (user or b'None', pid)
1920 locker = b'user %s, process %s' % (user or b'None', pid)
1921 else:
1921 else:
1922 locker = b'user %s, process %s, host %s' % (
1922 locker = b'user %s, process %s, host %s' % (
1923 user or b'None',
1923 user or b'None',
1924 pid,
1924 pid,
1925 host,
1925 host,
1926 )
1926 )
1927 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
1927 ui.writenoi18n(b"%-6s %s (%ds)\n" % (name + b":", locker, age))
1928 return 1
1928 return 1
1929 except OSError as e:
1929 except OSError as e:
1930 if e.errno != errno.ENOENT:
1930 if e.errno != errno.ENOENT:
1931 raise
1931 raise
1932
1932
1933 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
1933 ui.writenoi18n(b"%-6s free\n" % (name + b":"))
1934 return 0
1934 return 0
1935
1935
1936 held += report(repo.svfs, b"lock", repo.lock)
1936 held += report(repo.svfs, b"lock", repo.lock)
1937 held += report(repo.vfs, b"wlock", repo.wlock)
1937 held += report(repo.vfs, b"wlock", repo.wlock)
1938
1938
1939 return held
1939 return held
1940
1940
1941
1941
1942 @command(
1942 @command(
1943 b'debugmanifestfulltextcache',
1943 b'debugmanifestfulltextcache',
1944 [
1944 [
1945 (b'', b'clear', False, _(b'clear the cache')),
1945 (b'', b'clear', False, _(b'clear the cache')),
1946 (
1946 (
1947 b'a',
1947 b'a',
1948 b'add',
1948 b'add',
1949 [],
1949 [],
1950 _(b'add the given manifest nodes to the cache'),
1950 _(b'add the given manifest nodes to the cache'),
1951 _(b'NODE'),
1951 _(b'NODE'),
1952 ),
1952 ),
1953 ],
1953 ],
1954 b'',
1954 b'',
1955 )
1955 )
1956 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
1956 def debugmanifestfulltextcache(ui, repo, add=(), **opts):
1957 """show, clear or amend the contents of the manifest fulltext cache"""
1957 """show, clear or amend the contents of the manifest fulltext cache"""
1958
1958
1959 def getcache():
1959 def getcache():
1960 r = repo.manifestlog.getstorage(b'')
1960 r = repo.manifestlog.getstorage(b'')
1961 try:
1961 try:
1962 return r._fulltextcache
1962 return r._fulltextcache
1963 except AttributeError:
1963 except AttributeError:
1964 msg = _(
1964 msg = _(
1965 b"Current revlog implementation doesn't appear to have a "
1965 b"Current revlog implementation doesn't appear to have a "
1966 b"manifest fulltext cache\n"
1966 b"manifest fulltext cache\n"
1967 )
1967 )
1968 raise error.Abort(msg)
1968 raise error.Abort(msg)
1969
1969
1970 if opts.get('clear'):
1970 if opts.get('clear'):
1971 with repo.wlock():
1971 with repo.wlock():
1972 cache = getcache()
1972 cache = getcache()
1973 cache.clear(clear_persisted_data=True)
1973 cache.clear(clear_persisted_data=True)
1974 return
1974 return
1975
1975
1976 if add:
1976 if add:
1977 with repo.wlock():
1977 with repo.wlock():
1978 m = repo.manifestlog
1978 m = repo.manifestlog
1979 store = m.getstorage(b'')
1979 store = m.getstorage(b'')
1980 for n in add:
1980 for n in add:
1981 try:
1981 try:
1982 manifest = m[store.lookup(n)]
1982 manifest = m[store.lookup(n)]
1983 except error.LookupError as e:
1983 except error.LookupError as e:
1984 raise error.Abort(e, hint=b"Check your manifest node id")
1984 raise error.Abort(e, hint=b"Check your manifest node id")
1985 manifest.read() # stores revisision in cache too
1985 manifest.read() # stores revisision in cache too
1986 return
1986 return
1987
1987
1988 cache = getcache()
1988 cache = getcache()
1989 if not len(cache):
1989 if not len(cache):
1990 ui.write(_(b'cache empty\n'))
1990 ui.write(_(b'cache empty\n'))
1991 else:
1991 else:
1992 ui.write(
1992 ui.write(
1993 _(
1993 _(
1994 b'cache contains %d manifest entries, in order of most to '
1994 b'cache contains %d manifest entries, in order of most to '
1995 b'least recent:\n'
1995 b'least recent:\n'
1996 )
1996 )
1997 % (len(cache),)
1997 % (len(cache),)
1998 )
1998 )
1999 totalsize = 0
1999 totalsize = 0
2000 for nodeid in cache:
2000 for nodeid in cache:
2001 # Use cache.get to not update the LRU order
2001 # Use cache.get to not update the LRU order
2002 data = cache.peek(nodeid)
2002 data = cache.peek(nodeid)
2003 size = len(data)
2003 size = len(data)
2004 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2004 totalsize += size + 24 # 20 bytes nodeid, 4 bytes size
2005 ui.write(
2005 ui.write(
2006 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2006 _(b'id: %s, size %s\n') % (hex(nodeid), util.bytecount(size))
2007 )
2007 )
2008 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2008 ondisk = cache._opener.stat(b'manifestfulltextcache').st_size
2009 ui.write(
2009 ui.write(
2010 _(b'total cache data size %s, on-disk %s\n')
2010 _(b'total cache data size %s, on-disk %s\n')
2011 % (util.bytecount(totalsize), util.bytecount(ondisk))
2011 % (util.bytecount(totalsize), util.bytecount(ondisk))
2012 )
2012 )
2013
2013
2014
2014
2015 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2015 @command(b'debugmergestate', [] + cmdutil.templateopts, b'')
2016 def debugmergestate(ui, repo, *args, **opts):
2016 def debugmergestate(ui, repo, *args, **opts):
2017 """print merge state
2017 """print merge state
2018
2018
2019 Use --verbose to print out information about whether v1 or v2 merge state
2019 Use --verbose to print out information about whether v1 or v2 merge state
2020 was chosen."""
2020 was chosen."""
2021
2021
2022 if ui.verbose:
2022 if ui.verbose:
2023 ms = mergestatemod.mergestate(repo)
2023 ms = mergestatemod.mergestate(repo)
2024
2024
2025 # sort so that reasonable information is on top
2025 # sort so that reasonable information is on top
2026 v1records = ms._readrecordsv1()
2026 v1records = ms._readrecordsv1()
2027 v2records = ms._readrecordsv2()
2027 v2records = ms._readrecordsv2()
2028
2028
2029 if not v1records and not v2records:
2029 if not v1records and not v2records:
2030 pass
2030 pass
2031 elif not v2records:
2031 elif not v2records:
2032 ui.writenoi18n(b'no version 2 merge state\n')
2032 ui.writenoi18n(b'no version 2 merge state\n')
2033 elif ms._v1v2match(v1records, v2records):
2033 elif ms._v1v2match(v1records, v2records):
2034 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2034 ui.writenoi18n(b'v1 and v2 states match: using v2\n')
2035 else:
2035 else:
2036 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2036 ui.writenoi18n(b'v1 and v2 states mismatch: using v1\n')
2037
2037
2038 opts = pycompat.byteskwargs(opts)
2038 opts = pycompat.byteskwargs(opts)
2039 if not opts[b'template']:
2039 if not opts[b'template']:
2040 opts[b'template'] = (
2040 opts[b'template'] = (
2041 b'{if(commits, "", "no merge state found\n")}'
2041 b'{if(commits, "", "no merge state found\n")}'
2042 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2042 b'{commits % "{name}{if(label, " ({label})")}: {node}\n"}'
2043 b'{files % "file: {path} (state \\"{state}\\")\n'
2043 b'{files % "file: {path} (state \\"{state}\\")\n'
2044 b'{if(local_path, "'
2044 b'{if(local_path, "'
2045 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2045 b' local path: {local_path} (hash {local_key}, flags \\"{local_flags}\\")\n'
2046 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2046 b' ancestor path: {ancestor_path} (node {ancestor_node})\n'
2047 b' other path: {other_path} (node {other_node})\n'
2047 b' other path: {other_path} (node {other_node})\n'
2048 b'")}'
2048 b'")}'
2049 b'{if(rename_side, "'
2049 b'{if(rename_side, "'
2050 b' rename side: {rename_side}\n'
2050 b' rename side: {rename_side}\n'
2051 b' renamed path: {renamed_path}\n'
2051 b' renamed path: {renamed_path}\n'
2052 b'")}'
2052 b'")}'
2053 b'{extras % " extra: {key} = {value}\n"}'
2053 b'{extras % " extra: {key} = {value}\n"}'
2054 b'"}'
2054 b'"}'
2055 b'{extras % "extra: {file} ({key} = {value})\n"}'
2055 b'{extras % "extra: {file} ({key} = {value})\n"}'
2056 )
2056 )
2057
2057
2058 ms = mergestatemod.mergestate.read(repo)
2058 ms = mergestatemod.mergestate.read(repo)
2059
2059
2060 fm = ui.formatter(b'debugmergestate', opts)
2060 fm = ui.formatter(b'debugmergestate', opts)
2061 fm.startitem()
2061 fm.startitem()
2062
2062
2063 fm_commits = fm.nested(b'commits')
2063 fm_commits = fm.nested(b'commits')
2064 if ms.active():
2064 if ms.active():
2065 for name, node, label_index in (
2065 for name, node, label_index in (
2066 (b'local', ms.local, 0),
2066 (b'local', ms.local, 0),
2067 (b'other', ms.other, 1),
2067 (b'other', ms.other, 1),
2068 ):
2068 ):
2069 fm_commits.startitem()
2069 fm_commits.startitem()
2070 fm_commits.data(name=name)
2070 fm_commits.data(name=name)
2071 fm_commits.data(node=hex(node))
2071 fm_commits.data(node=hex(node))
2072 if ms._labels and len(ms._labels) > label_index:
2072 if ms._labels and len(ms._labels) > label_index:
2073 fm_commits.data(label=ms._labels[label_index])
2073 fm_commits.data(label=ms._labels[label_index])
2074 fm_commits.end()
2074 fm_commits.end()
2075
2075
2076 fm_files = fm.nested(b'files')
2076 fm_files = fm.nested(b'files')
2077 if ms.active():
2077 if ms.active():
2078 for f in ms:
2078 for f in ms:
2079 fm_files.startitem()
2079 fm_files.startitem()
2080 fm_files.data(path=f)
2080 fm_files.data(path=f)
2081 state = ms._state[f]
2081 state = ms._state[f]
2082 fm_files.data(state=state[0])
2082 fm_files.data(state=state[0])
2083 if state[0] in (
2083 if state[0] in (
2084 mergestatemod.MERGE_RECORD_UNRESOLVED,
2084 mergestatemod.MERGE_RECORD_UNRESOLVED,
2085 mergestatemod.MERGE_RECORD_RESOLVED,
2085 mergestatemod.MERGE_RECORD_RESOLVED,
2086 ):
2086 ):
2087 fm_files.data(local_key=state[1])
2087 fm_files.data(local_key=state[1])
2088 fm_files.data(local_path=state[2])
2088 fm_files.data(local_path=state[2])
2089 fm_files.data(ancestor_path=state[3])
2089 fm_files.data(ancestor_path=state[3])
2090 fm_files.data(ancestor_node=state[4])
2090 fm_files.data(ancestor_node=state[4])
2091 fm_files.data(other_path=state[5])
2091 fm_files.data(other_path=state[5])
2092 fm_files.data(other_node=state[6])
2092 fm_files.data(other_node=state[6])
2093 fm_files.data(local_flags=state[7])
2093 fm_files.data(local_flags=state[7])
2094 elif state[0] in (
2094 elif state[0] in (
2095 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2095 mergestatemod.MERGE_RECORD_UNRESOLVED_PATH,
2096 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2096 mergestatemod.MERGE_RECORD_RESOLVED_PATH,
2097 ):
2097 ):
2098 fm_files.data(renamed_path=state[1])
2098 fm_files.data(renamed_path=state[1])
2099 fm_files.data(rename_side=state[2])
2099 fm_files.data(rename_side=state[2])
2100 fm_extras = fm_files.nested(b'extras')
2100 fm_extras = fm_files.nested(b'extras')
2101 for k, v in sorted(ms.extras(f).items()):
2101 for k, v in sorted(ms.extras(f).items()):
2102 fm_extras.startitem()
2102 fm_extras.startitem()
2103 fm_extras.data(key=k)
2103 fm_extras.data(key=k)
2104 fm_extras.data(value=v)
2104 fm_extras.data(value=v)
2105 fm_extras.end()
2105 fm_extras.end()
2106
2106
2107 fm_files.end()
2107 fm_files.end()
2108
2108
2109 fm_extras = fm.nested(b'extras')
2109 fm_extras = fm.nested(b'extras')
2110 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2110 for f, d in sorted(pycompat.iteritems(ms.allextras())):
2111 if f in ms:
2111 if f in ms:
2112 # If file is in mergestate, we have already processed it's extras
2112 # If file is in mergestate, we have already processed it's extras
2113 continue
2113 continue
2114 for k, v in pycompat.iteritems(d):
2114 for k, v in pycompat.iteritems(d):
2115 fm_extras.startitem()
2115 fm_extras.startitem()
2116 fm_extras.data(file=f)
2116 fm_extras.data(file=f)
2117 fm_extras.data(key=k)
2117 fm_extras.data(key=k)
2118 fm_extras.data(value=v)
2118 fm_extras.data(value=v)
2119 fm_extras.end()
2119 fm_extras.end()
2120
2120
2121 fm.end()
2121 fm.end()
2122
2122
2123
2123
2124 @command(b'debugnamecomplete', [], _(b'NAME...'))
2124 @command(b'debugnamecomplete', [], _(b'NAME...'))
2125 def debugnamecomplete(ui, repo, *args):
2125 def debugnamecomplete(ui, repo, *args):
2126 '''complete "names" - tags, open branch names, bookmark names'''
2126 '''complete "names" - tags, open branch names, bookmark names'''
2127
2127
2128 names = set()
2128 names = set()
2129 # since we previously only listed open branches, we will handle that
2129 # since we previously only listed open branches, we will handle that
2130 # specially (after this for loop)
2130 # specially (after this for loop)
2131 for name, ns in pycompat.iteritems(repo.names):
2131 for name, ns in pycompat.iteritems(repo.names):
2132 if name != b'branches':
2132 if name != b'branches':
2133 names.update(ns.listnames(repo))
2133 names.update(ns.listnames(repo))
2134 names.update(
2134 names.update(
2135 tag
2135 tag
2136 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2136 for (tag, heads, tip, closed) in repo.branchmap().iterbranches()
2137 if not closed
2137 if not closed
2138 )
2138 )
2139 completions = set()
2139 completions = set()
2140 if not args:
2140 if not args:
2141 args = [b'']
2141 args = [b'']
2142 for a in args:
2142 for a in args:
2143 completions.update(n for n in names if n.startswith(a))
2143 completions.update(n for n in names if n.startswith(a))
2144 ui.write(b'\n'.join(sorted(completions)))
2144 ui.write(b'\n'.join(sorted(completions)))
2145 ui.write(b'\n')
2145 ui.write(b'\n')
2146
2146
2147
2147
2148 @command(
2148 @command(
2149 b'debugnodemap',
2149 b'debugnodemap',
2150 [
2150 [
2151 (
2151 (
2152 b'',
2152 b'',
2153 b'dump-new',
2153 b'dump-new',
2154 False,
2154 False,
2155 _(b'write a (new) persistent binary nodemap on stdin'),
2155 _(b'write a (new) persistent binary nodemap on stdin'),
2156 ),
2156 ),
2157 (b'', b'dump-disk', False, _(b'dump on-disk data on stdin')),
2157 (b'', b'dump-disk', False, _(b'dump on-disk data on stdin')),
2158 (
2158 (
2159 b'',
2159 b'',
2160 b'check',
2160 b'check',
2161 False,
2161 False,
2162 _(b'check that the data on disk data are correct.'),
2162 _(b'check that the data on disk data are correct.'),
2163 ),
2163 ),
2164 (
2164 (
2165 b'',
2165 b'',
2166 b'metadata',
2166 b'metadata',
2167 False,
2167 False,
2168 _(b'display the on disk meta data for the nodemap'),
2168 _(b'display the on disk meta data for the nodemap'),
2169 ),
2169 ),
2170 ],
2170 ],
2171 )
2171 )
2172 def debugnodemap(ui, repo, **opts):
2172 def debugnodemap(ui, repo, **opts):
2173 """write and inspect on disk nodemap
2173 """write and inspect on disk nodemap
2174 """
2174 """
2175 if opts['dump_new']:
2175 if opts['dump_new']:
2176 unfi = repo.unfiltered()
2176 unfi = repo.unfiltered()
2177 cl = unfi.changelog
2177 cl = unfi.changelog
2178 if util.safehasattr(cl.index, "nodemap_data_all"):
2178 if util.safehasattr(cl.index, "nodemap_data_all"):
2179 data = cl.index.nodemap_data_all()
2179 data = cl.index.nodemap_data_all()
2180 else:
2180 else:
2181 data = nodemap.persistent_data(cl.index)
2181 data = nodemap.persistent_data(cl.index)
2182 ui.write(data)
2182 ui.write(data)
2183 elif opts['dump_disk']:
2183 elif opts['dump_disk']:
2184 unfi = repo.unfiltered()
2184 unfi = repo.unfiltered()
2185 cl = unfi.changelog
2185 cl = unfi.changelog
2186 nm_data = nodemap.persisted_data(cl)
2186 nm_data = nodemap.persisted_data(cl)
2187 if nm_data is not None:
2187 if nm_data is not None:
2188 docket, data = nm_data
2188 docket, data = nm_data
2189 ui.write(data[:])
2189 ui.write(data[:])
2190 elif opts['check']:
2190 elif opts['check']:
2191 unfi = repo.unfiltered()
2191 unfi = repo.unfiltered()
2192 cl = unfi.changelog
2192 cl = unfi.changelog
2193 nm_data = nodemap.persisted_data(cl)
2193 nm_data = nodemap.persisted_data(cl)
2194 if nm_data is not None:
2194 if nm_data is not None:
2195 docket, data = nm_data
2195 docket, data = nm_data
2196 return nodemap.check_data(ui, cl.index, data)
2196 return nodemap.check_data(ui, cl.index, data)
2197 elif opts['metadata']:
2197 elif opts['metadata']:
2198 unfi = repo.unfiltered()
2198 unfi = repo.unfiltered()
2199 cl = unfi.changelog
2199 cl = unfi.changelog
2200 nm_data = nodemap.persisted_data(cl)
2200 nm_data = nodemap.persisted_data(cl)
2201 if nm_data is not None:
2201 if nm_data is not None:
2202 docket, data = nm_data
2202 docket, data = nm_data
2203 ui.write((b"uid: %s\n") % docket.uid)
2203 ui.write((b"uid: %s\n") % docket.uid)
2204 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2204 ui.write((b"tip-rev: %d\n") % docket.tip_rev)
2205 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2205 ui.write((b"tip-node: %s\n") % hex(docket.tip_node))
2206 ui.write((b"data-length: %d\n") % docket.data_length)
2206 ui.write((b"data-length: %d\n") % docket.data_length)
2207 ui.write((b"data-unused: %d\n") % docket.data_unused)
2207 ui.write((b"data-unused: %d\n") % docket.data_unused)
2208 unused_perc = docket.data_unused * 100.0 / docket.data_length
2208 unused_perc = docket.data_unused * 100.0 / docket.data_length
2209 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2209 ui.write((b"data-unused: %2.3f%%\n") % unused_perc)
2210
2210
2211
2211
2212 @command(
2212 @command(
2213 b'debugobsolete',
2213 b'debugobsolete',
2214 [
2214 [
2215 (b'', b'flags', 0, _(b'markers flag')),
2215 (b'', b'flags', 0, _(b'markers flag')),
2216 (
2216 (
2217 b'',
2217 b'',
2218 b'record-parents',
2218 b'record-parents',
2219 False,
2219 False,
2220 _(b'record parent information for the precursor'),
2220 _(b'record parent information for the precursor'),
2221 ),
2221 ),
2222 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2222 (b'r', b'rev', [], _(b'display markers relevant to REV')),
2223 (
2223 (
2224 b'',
2224 b'',
2225 b'exclusive',
2225 b'exclusive',
2226 False,
2226 False,
2227 _(b'restrict display to markers only relevant to REV'),
2227 _(b'restrict display to markers only relevant to REV'),
2228 ),
2228 ),
2229 (b'', b'index', False, _(b'display index of the marker')),
2229 (b'', b'index', False, _(b'display index of the marker')),
2230 (b'', b'delete', [], _(b'delete markers specified by indices')),
2230 (b'', b'delete', [], _(b'delete markers specified by indices')),
2231 ]
2231 ]
2232 + cmdutil.commitopts2
2232 + cmdutil.commitopts2
2233 + cmdutil.formatteropts,
2233 + cmdutil.formatteropts,
2234 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2234 _(b'[OBSOLETED [REPLACEMENT ...]]'),
2235 )
2235 )
2236 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2236 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
2237 """create arbitrary obsolete marker
2237 """create arbitrary obsolete marker
2238
2238
2239 With no arguments, displays the list of obsolescence markers."""
2239 With no arguments, displays the list of obsolescence markers."""
2240
2240
2241 opts = pycompat.byteskwargs(opts)
2241 opts = pycompat.byteskwargs(opts)
2242
2242
2243 def parsenodeid(s):
2243 def parsenodeid(s):
2244 try:
2244 try:
2245 # We do not use revsingle/revrange functions here to accept
2245 # We do not use revsingle/revrange functions here to accept
2246 # arbitrary node identifiers, possibly not present in the
2246 # arbitrary node identifiers, possibly not present in the
2247 # local repository.
2247 # local repository.
2248 n = bin(s)
2248 n = bin(s)
2249 if len(n) != len(nullid):
2249 if len(n) != len(nullid):
2250 raise TypeError()
2250 raise TypeError()
2251 return n
2251 return n
2252 except TypeError:
2252 except TypeError:
2253 raise error.Abort(
2253 raise error.Abort(
2254 b'changeset references must be full hexadecimal '
2254 b'changeset references must be full hexadecimal '
2255 b'node identifiers'
2255 b'node identifiers'
2256 )
2256 )
2257
2257
2258 if opts.get(b'delete'):
2258 if opts.get(b'delete'):
2259 indices = []
2259 indices = []
2260 for v in opts.get(b'delete'):
2260 for v in opts.get(b'delete'):
2261 try:
2261 try:
2262 indices.append(int(v))
2262 indices.append(int(v))
2263 except ValueError:
2263 except ValueError:
2264 raise error.Abort(
2264 raise error.Abort(
2265 _(b'invalid index value: %r') % v,
2265 _(b'invalid index value: %r') % v,
2266 hint=_(b'use integers for indices'),
2266 hint=_(b'use integers for indices'),
2267 )
2267 )
2268
2268
2269 if repo.currenttransaction():
2269 if repo.currenttransaction():
2270 raise error.Abort(
2270 raise error.Abort(
2271 _(b'cannot delete obsmarkers in the middle of transaction.')
2271 _(b'cannot delete obsmarkers in the middle of transaction.')
2272 )
2272 )
2273
2273
2274 with repo.lock():
2274 with repo.lock():
2275 n = repair.deleteobsmarkers(repo.obsstore, indices)
2275 n = repair.deleteobsmarkers(repo.obsstore, indices)
2276 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2276 ui.write(_(b'deleted %i obsolescence markers\n') % n)
2277
2277
2278 return
2278 return
2279
2279
2280 if precursor is not None:
2280 if precursor is not None:
2281 if opts[b'rev']:
2281 if opts[b'rev']:
2282 raise error.Abort(b'cannot select revision when creating marker')
2282 raise error.Abort(b'cannot select revision when creating marker')
2283 metadata = {}
2283 metadata = {}
2284 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2284 metadata[b'user'] = encoding.fromlocal(opts[b'user'] or ui.username())
2285 succs = tuple(parsenodeid(succ) for succ in successors)
2285 succs = tuple(parsenodeid(succ) for succ in successors)
2286 l = repo.lock()
2286 l = repo.lock()
2287 try:
2287 try:
2288 tr = repo.transaction(b'debugobsolete')
2288 tr = repo.transaction(b'debugobsolete')
2289 try:
2289 try:
2290 date = opts.get(b'date')
2290 date = opts.get(b'date')
2291 if date:
2291 if date:
2292 date = dateutil.parsedate(date)
2292 date = dateutil.parsedate(date)
2293 else:
2293 else:
2294 date = None
2294 date = None
2295 prec = parsenodeid(precursor)
2295 prec = parsenodeid(precursor)
2296 parents = None
2296 parents = None
2297 if opts[b'record_parents']:
2297 if opts[b'record_parents']:
2298 if prec not in repo.unfiltered():
2298 if prec not in repo.unfiltered():
2299 raise error.Abort(
2299 raise error.Abort(
2300 b'cannot used --record-parents on '
2300 b'cannot used --record-parents on '
2301 b'unknown changesets'
2301 b'unknown changesets'
2302 )
2302 )
2303 parents = repo.unfiltered()[prec].parents()
2303 parents = repo.unfiltered()[prec].parents()
2304 parents = tuple(p.node() for p in parents)
2304 parents = tuple(p.node() for p in parents)
2305 repo.obsstore.create(
2305 repo.obsstore.create(
2306 tr,
2306 tr,
2307 prec,
2307 prec,
2308 succs,
2308 succs,
2309 opts[b'flags'],
2309 opts[b'flags'],
2310 parents=parents,
2310 parents=parents,
2311 date=date,
2311 date=date,
2312 metadata=metadata,
2312 metadata=metadata,
2313 ui=ui,
2313 ui=ui,
2314 )
2314 )
2315 tr.close()
2315 tr.close()
2316 except ValueError as exc:
2316 except ValueError as exc:
2317 raise error.Abort(
2317 raise error.Abort(
2318 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2318 _(b'bad obsmarker input: %s') % pycompat.bytestr(exc)
2319 )
2319 )
2320 finally:
2320 finally:
2321 tr.release()
2321 tr.release()
2322 finally:
2322 finally:
2323 l.release()
2323 l.release()
2324 else:
2324 else:
2325 if opts[b'rev']:
2325 if opts[b'rev']:
2326 revs = scmutil.revrange(repo, opts[b'rev'])
2326 revs = scmutil.revrange(repo, opts[b'rev'])
2327 nodes = [repo[r].node() for r in revs]
2327 nodes = [repo[r].node() for r in revs]
2328 markers = list(
2328 markers = list(
2329 obsutil.getmarkers(
2329 obsutil.getmarkers(
2330 repo, nodes=nodes, exclusive=opts[b'exclusive']
2330 repo, nodes=nodes, exclusive=opts[b'exclusive']
2331 )
2331 )
2332 )
2332 )
2333 markers.sort(key=lambda x: x._data)
2333 markers.sort(key=lambda x: x._data)
2334 else:
2334 else:
2335 markers = obsutil.getmarkers(repo)
2335 markers = obsutil.getmarkers(repo)
2336
2336
2337 markerstoiter = markers
2337 markerstoiter = markers
2338 isrelevant = lambda m: True
2338 isrelevant = lambda m: True
2339 if opts.get(b'rev') and opts.get(b'index'):
2339 if opts.get(b'rev') and opts.get(b'index'):
2340 markerstoiter = obsutil.getmarkers(repo)
2340 markerstoiter = obsutil.getmarkers(repo)
2341 markerset = set(markers)
2341 markerset = set(markers)
2342 isrelevant = lambda m: m in markerset
2342 isrelevant = lambda m: m in markerset
2343
2343
2344 fm = ui.formatter(b'debugobsolete', opts)
2344 fm = ui.formatter(b'debugobsolete', opts)
2345 for i, m in enumerate(markerstoiter):
2345 for i, m in enumerate(markerstoiter):
2346 if not isrelevant(m):
2346 if not isrelevant(m):
2347 # marker can be irrelevant when we're iterating over a set
2347 # marker can be irrelevant when we're iterating over a set
2348 # of markers (markerstoiter) which is bigger than the set
2348 # of markers (markerstoiter) which is bigger than the set
2349 # of markers we want to display (markers)
2349 # of markers we want to display (markers)
2350 # this can happen if both --index and --rev options are
2350 # this can happen if both --index and --rev options are
2351 # provided and thus we need to iterate over all of the markers
2351 # provided and thus we need to iterate over all of the markers
2352 # to get the correct indices, but only display the ones that
2352 # to get the correct indices, but only display the ones that
2353 # are relevant to --rev value
2353 # are relevant to --rev value
2354 continue
2354 continue
2355 fm.startitem()
2355 fm.startitem()
2356 ind = i if opts.get(b'index') else None
2356 ind = i if opts.get(b'index') else None
2357 cmdutil.showmarker(fm, m, index=ind)
2357 cmdutil.showmarker(fm, m, index=ind)
2358 fm.end()
2358 fm.end()
2359
2359
2360
2360
2361 @command(
2361 @command(
2362 b'debugp1copies',
2362 b'debugp1copies',
2363 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2363 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2364 _(b'[-r REV]'),
2364 _(b'[-r REV]'),
2365 )
2365 )
2366 def debugp1copies(ui, repo, **opts):
2366 def debugp1copies(ui, repo, **opts):
2367 """dump copy information compared to p1"""
2367 """dump copy information compared to p1"""
2368
2368
2369 opts = pycompat.byteskwargs(opts)
2369 opts = pycompat.byteskwargs(opts)
2370 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2370 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2371 for dst, src in ctx.p1copies().items():
2371 for dst, src in ctx.p1copies().items():
2372 ui.write(b'%s -> %s\n' % (src, dst))
2372 ui.write(b'%s -> %s\n' % (src, dst))
2373
2373
2374
2374
2375 @command(
2375 @command(
2376 b'debugp2copies',
2376 b'debugp2copies',
2377 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2377 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2378 _(b'[-r REV]'),
2378 _(b'[-r REV]'),
2379 )
2379 )
2380 def debugp1copies(ui, repo, **opts):
2380 def debugp1copies(ui, repo, **opts):
2381 """dump copy information compared to p2"""
2381 """dump copy information compared to p2"""
2382
2382
2383 opts = pycompat.byteskwargs(opts)
2383 opts = pycompat.byteskwargs(opts)
2384 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2384 ctx = scmutil.revsingle(repo, opts.get(b'rev'), default=None)
2385 for dst, src in ctx.p2copies().items():
2385 for dst, src in ctx.p2copies().items():
2386 ui.write(b'%s -> %s\n' % (src, dst))
2386 ui.write(b'%s -> %s\n' % (src, dst))
2387
2387
2388
2388
2389 @command(
2389 @command(
2390 b'debugpathcomplete',
2390 b'debugpathcomplete',
2391 [
2391 [
2392 (b'f', b'full', None, _(b'complete an entire path')),
2392 (b'f', b'full', None, _(b'complete an entire path')),
2393 (b'n', b'normal', None, _(b'show only normal files')),
2393 (b'n', b'normal', None, _(b'show only normal files')),
2394 (b'a', b'added', None, _(b'show only added files')),
2394 (b'a', b'added', None, _(b'show only added files')),
2395 (b'r', b'removed', None, _(b'show only removed files')),
2395 (b'r', b'removed', None, _(b'show only removed files')),
2396 ],
2396 ],
2397 _(b'FILESPEC...'),
2397 _(b'FILESPEC...'),
2398 )
2398 )
2399 def debugpathcomplete(ui, repo, *specs, **opts):
2399 def debugpathcomplete(ui, repo, *specs, **opts):
2400 '''complete part or all of a tracked path
2400 '''complete part or all of a tracked path
2401
2401
2402 This command supports shells that offer path name completion. It
2402 This command supports shells that offer path name completion. It
2403 currently completes only files already known to the dirstate.
2403 currently completes only files already known to the dirstate.
2404
2404
2405 Completion extends only to the next path segment unless
2405 Completion extends only to the next path segment unless
2406 --full is specified, in which case entire paths are used.'''
2406 --full is specified, in which case entire paths are used.'''
2407
2407
2408 def complete(path, acceptable):
2408 def complete(path, acceptable):
2409 dirstate = repo.dirstate
2409 dirstate = repo.dirstate
2410 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2410 spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
2411 rootdir = repo.root + pycompat.ossep
2411 rootdir = repo.root + pycompat.ossep
2412 if spec != repo.root and not spec.startswith(rootdir):
2412 if spec != repo.root and not spec.startswith(rootdir):
2413 return [], []
2413 return [], []
2414 if os.path.isdir(spec):
2414 if os.path.isdir(spec):
2415 spec += b'/'
2415 spec += b'/'
2416 spec = spec[len(rootdir) :]
2416 spec = spec[len(rootdir) :]
2417 fixpaths = pycompat.ossep != b'/'
2417 fixpaths = pycompat.ossep != b'/'
2418 if fixpaths:
2418 if fixpaths:
2419 spec = spec.replace(pycompat.ossep, b'/')
2419 spec = spec.replace(pycompat.ossep, b'/')
2420 speclen = len(spec)
2420 speclen = len(spec)
2421 fullpaths = opts['full']
2421 fullpaths = opts['full']
2422 files, dirs = set(), set()
2422 files, dirs = set(), set()
2423 adddir, addfile = dirs.add, files.add
2423 adddir, addfile = dirs.add, files.add
2424 for f, st in pycompat.iteritems(dirstate):
2424 for f, st in pycompat.iteritems(dirstate):
2425 if f.startswith(spec) and st[0] in acceptable:
2425 if f.startswith(spec) and st[0] in acceptable:
2426 if fixpaths:
2426 if fixpaths:
2427 f = f.replace(b'/', pycompat.ossep)
2427 f = f.replace(b'/', pycompat.ossep)
2428 if fullpaths:
2428 if fullpaths:
2429 addfile(f)
2429 addfile(f)
2430 continue
2430 continue
2431 s = f.find(pycompat.ossep, speclen)
2431 s = f.find(pycompat.ossep, speclen)
2432 if s >= 0:
2432 if s >= 0:
2433 adddir(f[:s])
2433 adddir(f[:s])
2434 else:
2434 else:
2435 addfile(f)
2435 addfile(f)
2436 return files, dirs
2436 return files, dirs
2437
2437
2438 acceptable = b''
2438 acceptable = b''
2439 if opts['normal']:
2439 if opts['normal']:
2440 acceptable += b'nm'
2440 acceptable += b'nm'
2441 if opts['added']:
2441 if opts['added']:
2442 acceptable += b'a'
2442 acceptable += b'a'
2443 if opts['removed']:
2443 if opts['removed']:
2444 acceptable += b'r'
2444 acceptable += b'r'
2445 cwd = repo.getcwd()
2445 cwd = repo.getcwd()
2446 if not specs:
2446 if not specs:
2447 specs = [b'.']
2447 specs = [b'.']
2448
2448
2449 files, dirs = set(), set()
2449 files, dirs = set(), set()
2450 for spec in specs:
2450 for spec in specs:
2451 f, d = complete(spec, acceptable or b'nmar')
2451 f, d = complete(spec, acceptable or b'nmar')
2452 files.update(f)
2452 files.update(f)
2453 dirs.update(d)
2453 dirs.update(d)
2454 files.update(dirs)
2454 files.update(dirs)
2455 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2455 ui.write(b'\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
2456 ui.write(b'\n')
2456 ui.write(b'\n')
2457
2457
2458
2458
2459 @command(
2459 @command(
2460 b'debugpathcopies',
2460 b'debugpathcopies',
2461 cmdutil.walkopts,
2461 cmdutil.walkopts,
2462 b'hg debugpathcopies REV1 REV2 [FILE]',
2462 b'hg debugpathcopies REV1 REV2 [FILE]',
2463 inferrepo=True,
2463 inferrepo=True,
2464 )
2464 )
2465 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2465 def debugpathcopies(ui, repo, rev1, rev2, *pats, **opts):
2466 """show copies between two revisions"""
2466 """show copies between two revisions"""
2467 ctx1 = scmutil.revsingle(repo, rev1)
2467 ctx1 = scmutil.revsingle(repo, rev1)
2468 ctx2 = scmutil.revsingle(repo, rev2)
2468 ctx2 = scmutil.revsingle(repo, rev2)
2469 m = scmutil.match(ctx1, pats, opts)
2469 m = scmutil.match(ctx1, pats, opts)
2470 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2470 for dst, src in sorted(copies.pathcopies(ctx1, ctx2, m).items()):
2471 ui.write(b'%s -> %s\n' % (src, dst))
2471 ui.write(b'%s -> %s\n' % (src, dst))
2472
2472
2473
2473
2474 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2474 @command(b'debugpeer', [], _(b'PATH'), norepo=True)
2475 def debugpeer(ui, path):
2475 def debugpeer(ui, path):
2476 """establish a connection to a peer repository"""
2476 """establish a connection to a peer repository"""
2477 # Always enable peer request logging. Requires --debug to display
2477 # Always enable peer request logging. Requires --debug to display
2478 # though.
2478 # though.
2479 overrides = {
2479 overrides = {
2480 (b'devel', b'debug.peer-request'): True,
2480 (b'devel', b'debug.peer-request'): True,
2481 }
2481 }
2482
2482
2483 with ui.configoverride(overrides):
2483 with ui.configoverride(overrides):
2484 peer = hg.peer(ui, {}, path)
2484 peer = hg.peer(ui, {}, path)
2485
2485
2486 local = peer.local() is not None
2486 local = peer.local() is not None
2487 canpush = peer.canpush()
2487 canpush = peer.canpush()
2488
2488
2489 ui.write(_(b'url: %s\n') % peer.url())
2489 ui.write(_(b'url: %s\n') % peer.url())
2490 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2490 ui.write(_(b'local: %s\n') % (_(b'yes') if local else _(b'no')))
2491 ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')))
2491 ui.write(_(b'pushable: %s\n') % (_(b'yes') if canpush else _(b'no')))
2492
2492
2493
2493
2494 @command(
2494 @command(
2495 b'debugpickmergetool',
2495 b'debugpickmergetool',
2496 [
2496 [
2497 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2497 (b'r', b'rev', b'', _(b'check for files in this revision'), _(b'REV')),
2498 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2498 (b'', b'changedelete', None, _(b'emulate merging change and delete')),
2499 ]
2499 ]
2500 + cmdutil.walkopts
2500 + cmdutil.walkopts
2501 + cmdutil.mergetoolopts,
2501 + cmdutil.mergetoolopts,
2502 _(b'[PATTERN]...'),
2502 _(b'[PATTERN]...'),
2503 inferrepo=True,
2503 inferrepo=True,
2504 )
2504 )
2505 def debugpickmergetool(ui, repo, *pats, **opts):
2505 def debugpickmergetool(ui, repo, *pats, **opts):
2506 """examine which merge tool is chosen for specified file
2506 """examine which merge tool is chosen for specified file
2507
2507
2508 As described in :hg:`help merge-tools`, Mercurial examines
2508 As described in :hg:`help merge-tools`, Mercurial examines
2509 configurations below in this order to decide which merge tool is
2509 configurations below in this order to decide which merge tool is
2510 chosen for specified file.
2510 chosen for specified file.
2511
2511
2512 1. ``--tool`` option
2512 1. ``--tool`` option
2513 2. ``HGMERGE`` environment variable
2513 2. ``HGMERGE`` environment variable
2514 3. configurations in ``merge-patterns`` section
2514 3. configurations in ``merge-patterns`` section
2515 4. configuration of ``ui.merge``
2515 4. configuration of ``ui.merge``
2516 5. configurations in ``merge-tools`` section
2516 5. configurations in ``merge-tools`` section
2517 6. ``hgmerge`` tool (for historical reason only)
2517 6. ``hgmerge`` tool (for historical reason only)
2518 7. default tool for fallback (``:merge`` or ``:prompt``)
2518 7. default tool for fallback (``:merge`` or ``:prompt``)
2519
2519
2520 This command writes out examination result in the style below::
2520 This command writes out examination result in the style below::
2521
2521
2522 FILE = MERGETOOL
2522 FILE = MERGETOOL
2523
2523
2524 By default, all files known in the first parent context of the
2524 By default, all files known in the first parent context of the
2525 working directory are examined. Use file patterns and/or -I/-X
2525 working directory are examined. Use file patterns and/or -I/-X
2526 options to limit target files. -r/--rev is also useful to examine
2526 options to limit target files. -r/--rev is also useful to examine
2527 files in another context without actual updating to it.
2527 files in another context without actual updating to it.
2528
2528
2529 With --debug, this command shows warning messages while matching
2529 With --debug, this command shows warning messages while matching
2530 against ``merge-patterns`` and so on, too. It is recommended to
2530 against ``merge-patterns`` and so on, too. It is recommended to
2531 use this option with explicit file patterns and/or -I/-X options,
2531 use this option with explicit file patterns and/or -I/-X options,
2532 because this option increases amount of output per file according
2532 because this option increases amount of output per file according
2533 to configurations in hgrc.
2533 to configurations in hgrc.
2534
2534
2535 With -v/--verbose, this command shows configurations below at
2535 With -v/--verbose, this command shows configurations below at
2536 first (only if specified).
2536 first (only if specified).
2537
2537
2538 - ``--tool`` option
2538 - ``--tool`` option
2539 - ``HGMERGE`` environment variable
2539 - ``HGMERGE`` environment variable
2540 - configuration of ``ui.merge``
2540 - configuration of ``ui.merge``
2541
2541
2542 If merge tool is chosen before matching against
2542 If merge tool is chosen before matching against
2543 ``merge-patterns``, this command can't show any helpful
2543 ``merge-patterns``, this command can't show any helpful
2544 information, even with --debug. In such case, information above is
2544 information, even with --debug. In such case, information above is
2545 useful to know why a merge tool is chosen.
2545 useful to know why a merge tool is chosen.
2546 """
2546 """
2547 opts = pycompat.byteskwargs(opts)
2547 opts = pycompat.byteskwargs(opts)
2548 overrides = {}
2548 overrides = {}
2549 if opts[b'tool']:
2549 if opts[b'tool']:
2550 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2550 overrides[(b'ui', b'forcemerge')] = opts[b'tool']
2551 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2551 ui.notenoi18n(b'with --tool %r\n' % (pycompat.bytestr(opts[b'tool'])))
2552
2552
2553 with ui.configoverride(overrides, b'debugmergepatterns'):
2553 with ui.configoverride(overrides, b'debugmergepatterns'):
2554 hgmerge = encoding.environ.get(b"HGMERGE")
2554 hgmerge = encoding.environ.get(b"HGMERGE")
2555 if hgmerge is not None:
2555 if hgmerge is not None:
2556 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2556 ui.notenoi18n(b'with HGMERGE=%r\n' % (pycompat.bytestr(hgmerge)))
2557 uimerge = ui.config(b"ui", b"merge")
2557 uimerge = ui.config(b"ui", b"merge")
2558 if uimerge:
2558 if uimerge:
2559 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2559 ui.notenoi18n(b'with ui.merge=%r\n' % (pycompat.bytestr(uimerge)))
2560
2560
2561 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2561 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2562 m = scmutil.match(ctx, pats, opts)
2562 m = scmutil.match(ctx, pats, opts)
2563 changedelete = opts[b'changedelete']
2563 changedelete = opts[b'changedelete']
2564 for path in ctx.walk(m):
2564 for path in ctx.walk(m):
2565 fctx = ctx[path]
2565 fctx = ctx[path]
2566 try:
2566 try:
2567 if not ui.debugflag:
2567 if not ui.debugflag:
2568 ui.pushbuffer(error=True)
2568 ui.pushbuffer(error=True)
2569 tool, toolpath = filemerge._picktool(
2569 tool, toolpath = filemerge._picktool(
2570 repo,
2570 repo,
2571 ui,
2571 ui,
2572 path,
2572 path,
2573 fctx.isbinary(),
2573 fctx.isbinary(),
2574 b'l' in fctx.flags(),
2574 b'l' in fctx.flags(),
2575 changedelete,
2575 changedelete,
2576 )
2576 )
2577 finally:
2577 finally:
2578 if not ui.debugflag:
2578 if not ui.debugflag:
2579 ui.popbuffer()
2579 ui.popbuffer()
2580 ui.write(b'%s = %s\n' % (path, tool))
2580 ui.write(b'%s = %s\n' % (path, tool))
2581
2581
2582
2582
2583 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2583 @command(b'debugpushkey', [], _(b'REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
2584 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2584 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
2585 '''access the pushkey key/value protocol
2585 '''access the pushkey key/value protocol
2586
2586
2587 With two args, list the keys in the given namespace.
2587 With two args, list the keys in the given namespace.
2588
2588
2589 With five args, set a key to new if it currently is set to old.
2589 With five args, set a key to new if it currently is set to old.
2590 Reports success or failure.
2590 Reports success or failure.
2591 '''
2591 '''
2592
2592
2593 target = hg.peer(ui, {}, repopath)
2593 target = hg.peer(ui, {}, repopath)
2594 if keyinfo:
2594 if keyinfo:
2595 key, old, new = keyinfo
2595 key, old, new = keyinfo
2596 with target.commandexecutor() as e:
2596 with target.commandexecutor() as e:
2597 r = e.callcommand(
2597 r = e.callcommand(
2598 b'pushkey',
2598 b'pushkey',
2599 {
2599 {
2600 b'namespace': namespace,
2600 b'namespace': namespace,
2601 b'key': key,
2601 b'key': key,
2602 b'old': old,
2602 b'old': old,
2603 b'new': new,
2603 b'new': new,
2604 },
2604 },
2605 ).result()
2605 ).result()
2606
2606
2607 ui.status(pycompat.bytestr(r) + b'\n')
2607 ui.status(pycompat.bytestr(r) + b'\n')
2608 return not r
2608 return not r
2609 else:
2609 else:
2610 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2610 for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
2611 ui.write(
2611 ui.write(
2612 b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v))
2612 b"%s\t%s\n" % (stringutil.escapestr(k), stringutil.escapestr(v))
2613 )
2613 )
2614
2614
2615
2615
2616 @command(b'debugpvec', [], _(b'A B'))
2616 @command(b'debugpvec', [], _(b'A B'))
2617 def debugpvec(ui, repo, a, b=None):
2617 def debugpvec(ui, repo, a, b=None):
2618 ca = scmutil.revsingle(repo, a)
2618 ca = scmutil.revsingle(repo, a)
2619 cb = scmutil.revsingle(repo, b)
2619 cb = scmutil.revsingle(repo, b)
2620 pa = pvec.ctxpvec(ca)
2620 pa = pvec.ctxpvec(ca)
2621 pb = pvec.ctxpvec(cb)
2621 pb = pvec.ctxpvec(cb)
2622 if pa == pb:
2622 if pa == pb:
2623 rel = b"="
2623 rel = b"="
2624 elif pa > pb:
2624 elif pa > pb:
2625 rel = b">"
2625 rel = b">"
2626 elif pa < pb:
2626 elif pa < pb:
2627 rel = b"<"
2627 rel = b"<"
2628 elif pa | pb:
2628 elif pa | pb:
2629 rel = b"|"
2629 rel = b"|"
2630 ui.write(_(b"a: %s\n") % pa)
2630 ui.write(_(b"a: %s\n") % pa)
2631 ui.write(_(b"b: %s\n") % pb)
2631 ui.write(_(b"b: %s\n") % pb)
2632 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2632 ui.write(_(b"depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
2633 ui.write(
2633 ui.write(
2634 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2634 _(b"delta: %d hdist: %d distance: %d relation: %s\n")
2635 % (
2635 % (
2636 abs(pa._depth - pb._depth),
2636 abs(pa._depth - pb._depth),
2637 pvec._hamming(pa._vec, pb._vec),
2637 pvec._hamming(pa._vec, pb._vec),
2638 pa.distance(pb),
2638 pa.distance(pb),
2639 rel,
2639 rel,
2640 )
2640 )
2641 )
2641 )
2642
2642
2643
2643
2644 @command(
2644 @command(
2645 b'debugrebuilddirstate|debugrebuildstate',
2645 b'debugrebuilddirstate|debugrebuildstate',
2646 [
2646 [
2647 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2647 (b'r', b'rev', b'', _(b'revision to rebuild to'), _(b'REV')),
2648 (
2648 (
2649 b'',
2649 b'',
2650 b'minimal',
2650 b'minimal',
2651 None,
2651 None,
2652 _(
2652 _(
2653 b'only rebuild files that are inconsistent with '
2653 b'only rebuild files that are inconsistent with '
2654 b'the working copy parent'
2654 b'the working copy parent'
2655 ),
2655 ),
2656 ),
2656 ),
2657 ],
2657 ],
2658 _(b'[-r REV]'),
2658 _(b'[-r REV]'),
2659 )
2659 )
2660 def debugrebuilddirstate(ui, repo, rev, **opts):
2660 def debugrebuilddirstate(ui, repo, rev, **opts):
2661 """rebuild the dirstate as it would look like for the given revision
2661 """rebuild the dirstate as it would look like for the given revision
2662
2662
2663 If no revision is specified the first current parent will be used.
2663 If no revision is specified the first current parent will be used.
2664
2664
2665 The dirstate will be set to the files of the given revision.
2665 The dirstate will be set to the files of the given revision.
2666 The actual working directory content or existing dirstate
2666 The actual working directory content or existing dirstate
2667 information such as adds or removes is not considered.
2667 information such as adds or removes is not considered.
2668
2668
2669 ``minimal`` will only rebuild the dirstate status for files that claim to be
2669 ``minimal`` will only rebuild the dirstate status for files that claim to be
2670 tracked but are not in the parent manifest, or that exist in the parent
2670 tracked but are not in the parent manifest, or that exist in the parent
2671 manifest but are not in the dirstate. It will not change adds, removes, or
2671 manifest but are not in the dirstate. It will not change adds, removes, or
2672 modified files that are in the working copy parent.
2672 modified files that are in the working copy parent.
2673
2673
2674 One use of this command is to make the next :hg:`status` invocation
2674 One use of this command is to make the next :hg:`status` invocation
2675 check the actual file content.
2675 check the actual file content.
2676 """
2676 """
2677 ctx = scmutil.revsingle(repo, rev)
2677 ctx = scmutil.revsingle(repo, rev)
2678 with repo.wlock():
2678 with repo.wlock():
2679 dirstate = repo.dirstate
2679 dirstate = repo.dirstate
2680 changedfiles = None
2680 changedfiles = None
2681 # See command doc for what minimal does.
2681 # See command doc for what minimal does.
2682 if opts.get('minimal'):
2682 if opts.get('minimal'):
2683 manifestfiles = set(ctx.manifest().keys())
2683 manifestfiles = set(ctx.manifest().keys())
2684 dirstatefiles = set(dirstate)
2684 dirstatefiles = set(dirstate)
2685 manifestonly = manifestfiles - dirstatefiles
2685 manifestonly = manifestfiles - dirstatefiles
2686 dsonly = dirstatefiles - manifestfiles
2686 dsonly = dirstatefiles - manifestfiles
2687 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2687 dsnotadded = {f for f in dsonly if dirstate[f] != b'a'}
2688 changedfiles = manifestonly | dsnotadded
2688 changedfiles = manifestonly | dsnotadded
2689
2689
2690 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2690 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
2691
2691
2692
2692
2693 @command(b'debugrebuildfncache', [], b'')
2693 @command(b'debugrebuildfncache', [], b'')
2694 def debugrebuildfncache(ui, repo):
2694 def debugrebuildfncache(ui, repo):
2695 """rebuild the fncache file"""
2695 """rebuild the fncache file"""
2696 repair.rebuildfncache(ui, repo)
2696 repair.rebuildfncache(ui, repo)
2697
2697
2698
2698
2699 @command(
2699 @command(
2700 b'debugrename',
2700 b'debugrename',
2701 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2701 [(b'r', b'rev', b'', _(b'revision to debug'), _(b'REV'))],
2702 _(b'[-r REV] [FILE]...'),
2702 _(b'[-r REV] [FILE]...'),
2703 )
2703 )
2704 def debugrename(ui, repo, *pats, **opts):
2704 def debugrename(ui, repo, *pats, **opts):
2705 """dump rename information"""
2705 """dump rename information"""
2706
2706
2707 opts = pycompat.byteskwargs(opts)
2707 opts = pycompat.byteskwargs(opts)
2708 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2708 ctx = scmutil.revsingle(repo, opts.get(b'rev'))
2709 m = scmutil.match(ctx, pats, opts)
2709 m = scmutil.match(ctx, pats, opts)
2710 for abs in ctx.walk(m):
2710 for abs in ctx.walk(m):
2711 fctx = ctx[abs]
2711 fctx = ctx[abs]
2712 o = fctx.filelog().renamed(fctx.filenode())
2712 o = fctx.filelog().renamed(fctx.filenode())
2713 rel = repo.pathto(abs)
2713 rel = repo.pathto(abs)
2714 if o:
2714 if o:
2715 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2715 ui.write(_(b"%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
2716 else:
2716 else:
2717 ui.write(_(b"%s not renamed\n") % rel)
2717 ui.write(_(b"%s not renamed\n") % rel)
2718
2718
2719
2719
2720 @command(b'debugrequires|debugrequirements', [], b'')
2720 @command(b'debugrequires|debugrequirements', [], b'')
2721 def debugrequirements(ui, repo):
2721 def debugrequirements(ui, repo):
2722 """ print the current repo requirements """
2722 """ print the current repo requirements """
2723 for r in sorted(repo.requirements):
2723 for r in sorted(repo.requirements):
2724 ui.write(b"%s\n" % r)
2724 ui.write(b"%s\n" % r)
2725
2725
2726
2726
2727 @command(
2727 @command(
2728 b'debugrevlog',
2728 b'debugrevlog',
2729 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2729 cmdutil.debugrevlogopts + [(b'd', b'dump', False, _(b'dump index data'))],
2730 _(b'-c|-m|FILE'),
2730 _(b'-c|-m|FILE'),
2731 optionalrepo=True,
2731 optionalrepo=True,
2732 )
2732 )
2733 def debugrevlog(ui, repo, file_=None, **opts):
2733 def debugrevlog(ui, repo, file_=None, **opts):
2734 """show data and statistics about a revlog"""
2734 """show data and statistics about a revlog"""
2735 opts = pycompat.byteskwargs(opts)
2735 opts = pycompat.byteskwargs(opts)
2736 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2736 r = cmdutil.openrevlog(repo, b'debugrevlog', file_, opts)
2737
2737
2738 if opts.get(b"dump"):
2738 if opts.get(b"dump"):
2739 numrevs = len(r)
2739 numrevs = len(r)
2740 ui.write(
2740 ui.write(
2741 (
2741 (
2742 b"# rev p1rev p2rev start end deltastart base p1 p2"
2742 b"# rev p1rev p2rev start end deltastart base p1 p2"
2743 b" rawsize totalsize compression heads chainlen\n"
2743 b" rawsize totalsize compression heads chainlen\n"
2744 )
2744 )
2745 )
2745 )
2746 ts = 0
2746 ts = 0
2747 heads = set()
2747 heads = set()
2748
2748
2749 for rev in pycompat.xrange(numrevs):
2749 for rev in pycompat.xrange(numrevs):
2750 dbase = r.deltaparent(rev)
2750 dbase = r.deltaparent(rev)
2751 if dbase == -1:
2751 if dbase == -1:
2752 dbase = rev
2752 dbase = rev
2753 cbase = r.chainbase(rev)
2753 cbase = r.chainbase(rev)
2754 clen = r.chainlen(rev)
2754 clen = r.chainlen(rev)
2755 p1, p2 = r.parentrevs(rev)
2755 p1, p2 = r.parentrevs(rev)
2756 rs = r.rawsize(rev)
2756 rs = r.rawsize(rev)
2757 ts = ts + rs
2757 ts = ts + rs
2758 heads -= set(r.parentrevs(rev))
2758 heads -= set(r.parentrevs(rev))
2759 heads.add(rev)
2759 heads.add(rev)
2760 try:
2760 try:
2761 compression = ts / r.end(rev)
2761 compression = ts / r.end(rev)
2762 except ZeroDivisionError:
2762 except ZeroDivisionError:
2763 compression = 0
2763 compression = 0
2764 ui.write(
2764 ui.write(
2765 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2765 b"%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
2766 b"%11d %5d %8d\n"
2766 b"%11d %5d %8d\n"
2767 % (
2767 % (
2768 rev,
2768 rev,
2769 p1,
2769 p1,
2770 p2,
2770 p2,
2771 r.start(rev),
2771 r.start(rev),
2772 r.end(rev),
2772 r.end(rev),
2773 r.start(dbase),
2773 r.start(dbase),
2774 r.start(cbase),
2774 r.start(cbase),
2775 r.start(p1),
2775 r.start(p1),
2776 r.start(p2),
2776 r.start(p2),
2777 rs,
2777 rs,
2778 ts,
2778 ts,
2779 compression,
2779 compression,
2780 len(heads),
2780 len(heads),
2781 clen,
2781 clen,
2782 )
2782 )
2783 )
2783 )
2784 return 0
2784 return 0
2785
2785
2786 v = r.version
2786 v = r.version
2787 format = v & 0xFFFF
2787 format = v & 0xFFFF
2788 flags = []
2788 flags = []
2789 gdelta = False
2789 gdelta = False
2790 if v & revlog.FLAG_INLINE_DATA:
2790 if v & revlog.FLAG_INLINE_DATA:
2791 flags.append(b'inline')
2791 flags.append(b'inline')
2792 if v & revlog.FLAG_GENERALDELTA:
2792 if v & revlog.FLAG_GENERALDELTA:
2793 gdelta = True
2793 gdelta = True
2794 flags.append(b'generaldelta')
2794 flags.append(b'generaldelta')
2795 if not flags:
2795 if not flags:
2796 flags = [b'(none)']
2796 flags = [b'(none)']
2797
2797
2798 ### tracks merge vs single parent
2798 ### tracks merge vs single parent
2799 nummerges = 0
2799 nummerges = 0
2800
2800
2801 ### tracks ways the "delta" are build
2801 ### tracks ways the "delta" are build
2802 # nodelta
2802 # nodelta
2803 numempty = 0
2803 numempty = 0
2804 numemptytext = 0
2804 numemptytext = 0
2805 numemptydelta = 0
2805 numemptydelta = 0
2806 # full file content
2806 # full file content
2807 numfull = 0
2807 numfull = 0
2808 # intermediate snapshot against a prior snapshot
2808 # intermediate snapshot against a prior snapshot
2809 numsemi = 0
2809 numsemi = 0
2810 # snapshot count per depth
2810 # snapshot count per depth
2811 numsnapdepth = collections.defaultdict(lambda: 0)
2811 numsnapdepth = collections.defaultdict(lambda: 0)
2812 # delta against previous revision
2812 # delta against previous revision
2813 numprev = 0
2813 numprev = 0
2814 # delta against first or second parent (not prev)
2814 # delta against first or second parent (not prev)
2815 nump1 = 0
2815 nump1 = 0
2816 nump2 = 0
2816 nump2 = 0
2817 # delta against neither prev nor parents
2817 # delta against neither prev nor parents
2818 numother = 0
2818 numother = 0
2819 # delta against prev that are also first or second parent
2819 # delta against prev that are also first or second parent
2820 # (details of `numprev`)
2820 # (details of `numprev`)
2821 nump1prev = 0
2821 nump1prev = 0
2822 nump2prev = 0
2822 nump2prev = 0
2823
2823
2824 # data about delta chain of each revs
2824 # data about delta chain of each revs
2825 chainlengths = []
2825 chainlengths = []
2826 chainbases = []
2826 chainbases = []
2827 chainspans = []
2827 chainspans = []
2828
2828
2829 # data about each revision
2829 # data about each revision
2830 datasize = [None, 0, 0]
2830 datasize = [None, 0, 0]
2831 fullsize = [None, 0, 0]
2831 fullsize = [None, 0, 0]
2832 semisize = [None, 0, 0]
2832 semisize = [None, 0, 0]
2833 # snapshot count per depth
2833 # snapshot count per depth
2834 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2834 snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
2835 deltasize = [None, 0, 0]
2835 deltasize = [None, 0, 0]
2836 chunktypecounts = {}
2836 chunktypecounts = {}
2837 chunktypesizes = {}
2837 chunktypesizes = {}
2838
2838
2839 def addsize(size, l):
2839 def addsize(size, l):
2840 if l[0] is None or size < l[0]:
2840 if l[0] is None or size < l[0]:
2841 l[0] = size
2841 l[0] = size
2842 if size > l[1]:
2842 if size > l[1]:
2843 l[1] = size
2843 l[1] = size
2844 l[2] += size
2844 l[2] += size
2845
2845
2846 numrevs = len(r)
2846 numrevs = len(r)
2847 for rev in pycompat.xrange(numrevs):
2847 for rev in pycompat.xrange(numrevs):
2848 p1, p2 = r.parentrevs(rev)
2848 p1, p2 = r.parentrevs(rev)
2849 delta = r.deltaparent(rev)
2849 delta = r.deltaparent(rev)
2850 if format > 0:
2850 if format > 0:
2851 addsize(r.rawsize(rev), datasize)
2851 addsize(r.rawsize(rev), datasize)
2852 if p2 != nullrev:
2852 if p2 != nullrev:
2853 nummerges += 1
2853 nummerges += 1
2854 size = r.length(rev)
2854 size = r.length(rev)
2855 if delta == nullrev:
2855 if delta == nullrev:
2856 chainlengths.append(0)
2856 chainlengths.append(0)
2857 chainbases.append(r.start(rev))
2857 chainbases.append(r.start(rev))
2858 chainspans.append(size)
2858 chainspans.append(size)
2859 if size == 0:
2859 if size == 0:
2860 numempty += 1
2860 numempty += 1
2861 numemptytext += 1
2861 numemptytext += 1
2862 else:
2862 else:
2863 numfull += 1
2863 numfull += 1
2864 numsnapdepth[0] += 1
2864 numsnapdepth[0] += 1
2865 addsize(size, fullsize)
2865 addsize(size, fullsize)
2866 addsize(size, snapsizedepth[0])
2866 addsize(size, snapsizedepth[0])
2867 else:
2867 else:
2868 chainlengths.append(chainlengths[delta] + 1)
2868 chainlengths.append(chainlengths[delta] + 1)
2869 baseaddr = chainbases[delta]
2869 baseaddr = chainbases[delta]
2870 revaddr = r.start(rev)
2870 revaddr = r.start(rev)
2871 chainbases.append(baseaddr)
2871 chainbases.append(baseaddr)
2872 chainspans.append((revaddr - baseaddr) + size)
2872 chainspans.append((revaddr - baseaddr) + size)
2873 if size == 0:
2873 if size == 0:
2874 numempty += 1
2874 numempty += 1
2875 numemptydelta += 1
2875 numemptydelta += 1
2876 elif r.issnapshot(rev):
2876 elif r.issnapshot(rev):
2877 addsize(size, semisize)
2877 addsize(size, semisize)
2878 numsemi += 1
2878 numsemi += 1
2879 depth = r.snapshotdepth(rev)
2879 depth = r.snapshotdepth(rev)
2880 numsnapdepth[depth] += 1
2880 numsnapdepth[depth] += 1
2881 addsize(size, snapsizedepth[depth])
2881 addsize(size, snapsizedepth[depth])
2882 else:
2882 else:
2883 addsize(size, deltasize)
2883 addsize(size, deltasize)
2884 if delta == rev - 1:
2884 if delta == rev - 1:
2885 numprev += 1
2885 numprev += 1
2886 if delta == p1:
2886 if delta == p1:
2887 nump1prev += 1
2887 nump1prev += 1
2888 elif delta == p2:
2888 elif delta == p2:
2889 nump2prev += 1
2889 nump2prev += 1
2890 elif delta == p1:
2890 elif delta == p1:
2891 nump1 += 1
2891 nump1 += 1
2892 elif delta == p2:
2892 elif delta == p2:
2893 nump2 += 1
2893 nump2 += 1
2894 elif delta != nullrev:
2894 elif delta != nullrev:
2895 numother += 1
2895 numother += 1
2896
2896
2897 # Obtain data on the raw chunks in the revlog.
2897 # Obtain data on the raw chunks in the revlog.
2898 if util.safehasattr(r, b'_getsegmentforrevs'):
2898 if util.safehasattr(r, b'_getsegmentforrevs'):
2899 segment = r._getsegmentforrevs(rev, rev)[1]
2899 segment = r._getsegmentforrevs(rev, rev)[1]
2900 else:
2900 else:
2901 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2901 segment = r._revlog._getsegmentforrevs(rev, rev)[1]
2902 if segment:
2902 if segment:
2903 chunktype = bytes(segment[0:1])
2903 chunktype = bytes(segment[0:1])
2904 else:
2904 else:
2905 chunktype = b'empty'
2905 chunktype = b'empty'
2906
2906
2907 if chunktype not in chunktypecounts:
2907 if chunktype not in chunktypecounts:
2908 chunktypecounts[chunktype] = 0
2908 chunktypecounts[chunktype] = 0
2909 chunktypesizes[chunktype] = 0
2909 chunktypesizes[chunktype] = 0
2910
2910
2911 chunktypecounts[chunktype] += 1
2911 chunktypecounts[chunktype] += 1
2912 chunktypesizes[chunktype] += size
2912 chunktypesizes[chunktype] += size
2913
2913
2914 # Adjust size min value for empty cases
2914 # Adjust size min value for empty cases
2915 for size in (datasize, fullsize, semisize, deltasize):
2915 for size in (datasize, fullsize, semisize, deltasize):
2916 if size[0] is None:
2916 if size[0] is None:
2917 size[0] = 0
2917 size[0] = 0
2918
2918
2919 numdeltas = numrevs - numfull - numempty - numsemi
2919 numdeltas = numrevs - numfull - numempty - numsemi
2920 numoprev = numprev - nump1prev - nump2prev
2920 numoprev = numprev - nump1prev - nump2prev
2921 totalrawsize = datasize[2]
2921 totalrawsize = datasize[2]
2922 datasize[2] /= numrevs
2922 datasize[2] /= numrevs
2923 fulltotal = fullsize[2]
2923 fulltotal = fullsize[2]
2924 if numfull == 0:
2924 if numfull == 0:
2925 fullsize[2] = 0
2925 fullsize[2] = 0
2926 else:
2926 else:
2927 fullsize[2] /= numfull
2927 fullsize[2] /= numfull
2928 semitotal = semisize[2]
2928 semitotal = semisize[2]
2929 snaptotal = {}
2929 snaptotal = {}
2930 if numsemi > 0:
2930 if numsemi > 0:
2931 semisize[2] /= numsemi
2931 semisize[2] /= numsemi
2932 for depth in snapsizedepth:
2932 for depth in snapsizedepth:
2933 snaptotal[depth] = snapsizedepth[depth][2]
2933 snaptotal[depth] = snapsizedepth[depth][2]
2934 snapsizedepth[depth][2] /= numsnapdepth[depth]
2934 snapsizedepth[depth][2] /= numsnapdepth[depth]
2935
2935
2936 deltatotal = deltasize[2]
2936 deltatotal = deltasize[2]
2937 if numdeltas > 0:
2937 if numdeltas > 0:
2938 deltasize[2] /= numdeltas
2938 deltasize[2] /= numdeltas
2939 totalsize = fulltotal + semitotal + deltatotal
2939 totalsize = fulltotal + semitotal + deltatotal
2940 avgchainlen = sum(chainlengths) / numrevs
2940 avgchainlen = sum(chainlengths) / numrevs
2941 maxchainlen = max(chainlengths)
2941 maxchainlen = max(chainlengths)
2942 maxchainspan = max(chainspans)
2942 maxchainspan = max(chainspans)
2943 compratio = 1
2943 compratio = 1
2944 if totalsize:
2944 if totalsize:
2945 compratio = totalrawsize / totalsize
2945 compratio = totalrawsize / totalsize
2946
2946
2947 basedfmtstr = b'%%%dd\n'
2947 basedfmtstr = b'%%%dd\n'
2948 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
2948 basepcfmtstr = b'%%%dd %s(%%5.2f%%%%)\n'
2949
2949
2950 def dfmtstr(max):
2950 def dfmtstr(max):
2951 return basedfmtstr % len(str(max))
2951 return basedfmtstr % len(str(max))
2952
2952
2953 def pcfmtstr(max, padding=0):
2953 def pcfmtstr(max, padding=0):
2954 return basepcfmtstr % (len(str(max)), b' ' * padding)
2954 return basepcfmtstr % (len(str(max)), b' ' * padding)
2955
2955
2956 def pcfmt(value, total):
2956 def pcfmt(value, total):
2957 if total:
2957 if total:
2958 return (value, 100 * float(value) / total)
2958 return (value, 100 * float(value) / total)
2959 else:
2959 else:
2960 return value, 100.0
2960 return value, 100.0
2961
2961
2962 ui.writenoi18n(b'format : %d\n' % format)
2962 ui.writenoi18n(b'format : %d\n' % format)
2963 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
2963 ui.writenoi18n(b'flags : %s\n' % b', '.join(flags))
2964
2964
2965 ui.write(b'\n')
2965 ui.write(b'\n')
2966 fmt = pcfmtstr(totalsize)
2966 fmt = pcfmtstr(totalsize)
2967 fmt2 = dfmtstr(totalsize)
2967 fmt2 = dfmtstr(totalsize)
2968 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2968 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2969 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
2969 ui.writenoi18n(b' merges : ' + fmt % pcfmt(nummerges, numrevs))
2970 ui.writenoi18n(
2970 ui.writenoi18n(
2971 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
2971 b' normal : ' + fmt % pcfmt(numrevs - nummerges, numrevs)
2972 )
2972 )
2973 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2973 ui.writenoi18n(b'revisions : ' + fmt2 % numrevs)
2974 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
2974 ui.writenoi18n(b' empty : ' + fmt % pcfmt(numempty, numrevs))
2975 ui.writenoi18n(
2975 ui.writenoi18n(
2976 b' text : '
2976 b' text : '
2977 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
2977 + fmt % pcfmt(numemptytext, numemptytext + numemptydelta)
2978 )
2978 )
2979 ui.writenoi18n(
2979 ui.writenoi18n(
2980 b' delta : '
2980 b' delta : '
2981 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
2981 + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta)
2982 )
2982 )
2983 ui.writenoi18n(
2983 ui.writenoi18n(
2984 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
2984 b' snapshot : ' + fmt % pcfmt(numfull + numsemi, numrevs)
2985 )
2985 )
2986 for depth in sorted(numsnapdepth):
2986 for depth in sorted(numsnapdepth):
2987 ui.write(
2987 ui.write(
2988 (b' lvl-%-3d : ' % depth)
2988 (b' lvl-%-3d : ' % depth)
2989 + fmt % pcfmt(numsnapdepth[depth], numrevs)
2989 + fmt % pcfmt(numsnapdepth[depth], numrevs)
2990 )
2990 )
2991 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2991 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(numdeltas, numrevs))
2992 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
2992 ui.writenoi18n(b'revision size : ' + fmt2 % totalsize)
2993 ui.writenoi18n(
2993 ui.writenoi18n(
2994 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
2994 b' snapshot : ' + fmt % pcfmt(fulltotal + semitotal, totalsize)
2995 )
2995 )
2996 for depth in sorted(numsnapdepth):
2996 for depth in sorted(numsnapdepth):
2997 ui.write(
2997 ui.write(
2998 (b' lvl-%-3d : ' % depth)
2998 (b' lvl-%-3d : ' % depth)
2999 + fmt % pcfmt(snaptotal[depth], totalsize)
2999 + fmt % pcfmt(snaptotal[depth], totalsize)
3000 )
3000 )
3001 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3001 ui.writenoi18n(b' deltas : ' + fmt % pcfmt(deltatotal, totalsize))
3002
3002
3003 def fmtchunktype(chunktype):
3003 def fmtchunktype(chunktype):
3004 if chunktype == b'empty':
3004 if chunktype == b'empty':
3005 return b' %s : ' % chunktype
3005 return b' %s : ' % chunktype
3006 elif chunktype in pycompat.bytestr(string.ascii_letters):
3006 elif chunktype in pycompat.bytestr(string.ascii_letters):
3007 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3007 return b' 0x%s (%s) : ' % (hex(chunktype), chunktype)
3008 else:
3008 else:
3009 return b' 0x%s : ' % hex(chunktype)
3009 return b' 0x%s : ' % hex(chunktype)
3010
3010
3011 ui.write(b'\n')
3011 ui.write(b'\n')
3012 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3012 ui.writenoi18n(b'chunks : ' + fmt2 % numrevs)
3013 for chunktype in sorted(chunktypecounts):
3013 for chunktype in sorted(chunktypecounts):
3014 ui.write(fmtchunktype(chunktype))
3014 ui.write(fmtchunktype(chunktype))
3015 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3015 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
3016 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3016 ui.writenoi18n(b'chunks size : ' + fmt2 % totalsize)
3017 for chunktype in sorted(chunktypecounts):
3017 for chunktype in sorted(chunktypecounts):
3018 ui.write(fmtchunktype(chunktype))
3018 ui.write(fmtchunktype(chunktype))
3019 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3019 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
3020
3020
3021 ui.write(b'\n')
3021 ui.write(b'\n')
3022 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3022 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
3023 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3023 ui.writenoi18n(b'avg chain length : ' + fmt % avgchainlen)
3024 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3024 ui.writenoi18n(b'max chain length : ' + fmt % maxchainlen)
3025 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3025 ui.writenoi18n(b'max chain reach : ' + fmt % maxchainspan)
3026 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3026 ui.writenoi18n(b'compression ratio : ' + fmt % compratio)
3027
3027
3028 if format > 0:
3028 if format > 0:
3029 ui.write(b'\n')
3029 ui.write(b'\n')
3030 ui.writenoi18n(
3030 ui.writenoi18n(
3031 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3031 b'uncompressed data size (min/max/avg) : %d / %d / %d\n'
3032 % tuple(datasize)
3032 % tuple(datasize)
3033 )
3033 )
3034 ui.writenoi18n(
3034 ui.writenoi18n(
3035 b'full revision size (min/max/avg) : %d / %d / %d\n'
3035 b'full revision size (min/max/avg) : %d / %d / %d\n'
3036 % tuple(fullsize)
3036 % tuple(fullsize)
3037 )
3037 )
3038 ui.writenoi18n(
3038 ui.writenoi18n(
3039 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3039 b'inter-snapshot size (min/max/avg) : %d / %d / %d\n'
3040 % tuple(semisize)
3040 % tuple(semisize)
3041 )
3041 )
3042 for depth in sorted(snapsizedepth):
3042 for depth in sorted(snapsizedepth):
3043 if depth == 0:
3043 if depth == 0:
3044 continue
3044 continue
3045 ui.writenoi18n(
3045 ui.writenoi18n(
3046 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3046 b' level-%-3d (min/max/avg) : %d / %d / %d\n'
3047 % ((depth,) + tuple(snapsizedepth[depth]))
3047 % ((depth,) + tuple(snapsizedepth[depth]))
3048 )
3048 )
3049 ui.writenoi18n(
3049 ui.writenoi18n(
3050 b'delta size (min/max/avg) : %d / %d / %d\n'
3050 b'delta size (min/max/avg) : %d / %d / %d\n'
3051 % tuple(deltasize)
3051 % tuple(deltasize)
3052 )
3052 )
3053
3053
3054 if numdeltas > 0:
3054 if numdeltas > 0:
3055 ui.write(b'\n')
3055 ui.write(b'\n')
3056 fmt = pcfmtstr(numdeltas)
3056 fmt = pcfmtstr(numdeltas)
3057 fmt2 = pcfmtstr(numdeltas, 4)
3057 fmt2 = pcfmtstr(numdeltas, 4)
3058 ui.writenoi18n(
3058 ui.writenoi18n(
3059 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3059 b'deltas against prev : ' + fmt % pcfmt(numprev, numdeltas)
3060 )
3060 )
3061 if numprev > 0:
3061 if numprev > 0:
3062 ui.writenoi18n(
3062 ui.writenoi18n(
3063 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3063 b' where prev = p1 : ' + fmt2 % pcfmt(nump1prev, numprev)
3064 )
3064 )
3065 ui.writenoi18n(
3065 ui.writenoi18n(
3066 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3066 b' where prev = p2 : ' + fmt2 % pcfmt(nump2prev, numprev)
3067 )
3067 )
3068 ui.writenoi18n(
3068 ui.writenoi18n(
3069 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3069 b' other : ' + fmt2 % pcfmt(numoprev, numprev)
3070 )
3070 )
3071 if gdelta:
3071 if gdelta:
3072 ui.writenoi18n(
3072 ui.writenoi18n(
3073 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3073 b'deltas against p1 : ' + fmt % pcfmt(nump1, numdeltas)
3074 )
3074 )
3075 ui.writenoi18n(
3075 ui.writenoi18n(
3076 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3076 b'deltas against p2 : ' + fmt % pcfmt(nump2, numdeltas)
3077 )
3077 )
3078 ui.writenoi18n(
3078 ui.writenoi18n(
3079 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3079 b'deltas against other : ' + fmt % pcfmt(numother, numdeltas)
3080 )
3080 )
3081
3081
3082
3082
3083 @command(
3083 @command(
3084 b'debugrevlogindex',
3084 b'debugrevlogindex',
3085 cmdutil.debugrevlogopts
3085 cmdutil.debugrevlogopts
3086 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3086 + [(b'f', b'format', 0, _(b'revlog format'), _(b'FORMAT'))],
3087 _(b'[-f FORMAT] -c|-m|FILE'),
3087 _(b'[-f FORMAT] -c|-m|FILE'),
3088 optionalrepo=True,
3088 optionalrepo=True,
3089 )
3089 )
3090 def debugrevlogindex(ui, repo, file_=None, **opts):
3090 def debugrevlogindex(ui, repo, file_=None, **opts):
3091 """dump the contents of a revlog index"""
3091 """dump the contents of a revlog index"""
3092 opts = pycompat.byteskwargs(opts)
3092 opts = pycompat.byteskwargs(opts)
3093 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3093 r = cmdutil.openrevlog(repo, b'debugrevlogindex', file_, opts)
3094 format = opts.get(b'format', 0)
3094 format = opts.get(b'format', 0)
3095 if format not in (0, 1):
3095 if format not in (0, 1):
3096 raise error.Abort(_(b"unknown format %d") % format)
3096 raise error.Abort(_(b"unknown format %d") % format)
3097
3097
3098 if ui.debugflag:
3098 if ui.debugflag:
3099 shortfn = hex
3099 shortfn = hex
3100 else:
3100 else:
3101 shortfn = short
3101 shortfn = short
3102
3102
3103 # There might not be anything in r, so have a sane default
3103 # There might not be anything in r, so have a sane default
3104 idlen = 12
3104 idlen = 12
3105 for i in r:
3105 for i in r:
3106 idlen = len(shortfn(r.node(i)))
3106 idlen = len(shortfn(r.node(i)))
3107 break
3107 break
3108
3108
3109 if format == 0:
3109 if format == 0:
3110 if ui.verbose:
3110 if ui.verbose:
3111 ui.writenoi18n(
3111 ui.writenoi18n(
3112 b" rev offset length linkrev %s %s p2\n"
3112 b" rev offset length linkrev %s %s p2\n"
3113 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3113 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3114 )
3114 )
3115 else:
3115 else:
3116 ui.writenoi18n(
3116 ui.writenoi18n(
3117 b" rev linkrev %s %s p2\n"
3117 b" rev linkrev %s %s p2\n"
3118 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3118 % (b"nodeid".ljust(idlen), b"p1".ljust(idlen))
3119 )
3119 )
3120 elif format == 1:
3120 elif format == 1:
3121 if ui.verbose:
3121 if ui.verbose:
3122 ui.writenoi18n(
3122 ui.writenoi18n(
3123 (
3123 (
3124 b" rev flag offset length size link p1"
3124 b" rev flag offset length size link p1"
3125 b" p2 %s\n"
3125 b" p2 %s\n"
3126 )
3126 )
3127 % b"nodeid".rjust(idlen)
3127 % b"nodeid".rjust(idlen)
3128 )
3128 )
3129 else:
3129 else:
3130 ui.writenoi18n(
3130 ui.writenoi18n(
3131 b" rev flag size link p1 p2 %s\n"
3131 b" rev flag size link p1 p2 %s\n"
3132 % b"nodeid".rjust(idlen)
3132 % b"nodeid".rjust(idlen)
3133 )
3133 )
3134
3134
3135 for i in r:
3135 for i in r:
3136 node = r.node(i)
3136 node = r.node(i)
3137 if format == 0:
3137 if format == 0:
3138 try:
3138 try:
3139 pp = r.parents(node)
3139 pp = r.parents(node)
3140 except Exception:
3140 except Exception:
3141 pp = [nullid, nullid]
3141 pp = [nullid, nullid]
3142 if ui.verbose:
3142 if ui.verbose:
3143 ui.write(
3143 ui.write(
3144 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3144 b"% 6d % 9d % 7d % 7d %s %s %s\n"
3145 % (
3145 % (
3146 i,
3146 i,
3147 r.start(i),
3147 r.start(i),
3148 r.length(i),
3148 r.length(i),
3149 r.linkrev(i),
3149 r.linkrev(i),
3150 shortfn(node),
3150 shortfn(node),
3151 shortfn(pp[0]),
3151 shortfn(pp[0]),
3152 shortfn(pp[1]),
3152 shortfn(pp[1]),
3153 )
3153 )
3154 )
3154 )
3155 else:
3155 else:
3156 ui.write(
3156 ui.write(
3157 b"% 6d % 7d %s %s %s\n"
3157 b"% 6d % 7d %s %s %s\n"
3158 % (
3158 % (
3159 i,
3159 i,
3160 r.linkrev(i),
3160 r.linkrev(i),
3161 shortfn(node),
3161 shortfn(node),
3162 shortfn(pp[0]),
3162 shortfn(pp[0]),
3163 shortfn(pp[1]),
3163 shortfn(pp[1]),
3164 )
3164 )
3165 )
3165 )
3166 elif format == 1:
3166 elif format == 1:
3167 pr = r.parentrevs(i)
3167 pr = r.parentrevs(i)
3168 if ui.verbose:
3168 if ui.verbose:
3169 ui.write(
3169 ui.write(
3170 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3170 b"% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n"
3171 % (
3171 % (
3172 i,
3172 i,
3173 r.flags(i),
3173 r.flags(i),
3174 r.start(i),
3174 r.start(i),
3175 r.length(i),
3175 r.length(i),
3176 r.rawsize(i),
3176 r.rawsize(i),
3177 r.linkrev(i),
3177 r.linkrev(i),
3178 pr[0],
3178 pr[0],
3179 pr[1],
3179 pr[1],
3180 shortfn(node),
3180 shortfn(node),
3181 )
3181 )
3182 )
3182 )
3183 else:
3183 else:
3184 ui.write(
3184 ui.write(
3185 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3185 b"% 6d %04x % 8d % 6d % 6d % 6d %s\n"
3186 % (
3186 % (
3187 i,
3187 i,
3188 r.flags(i),
3188 r.flags(i),
3189 r.rawsize(i),
3189 r.rawsize(i),
3190 r.linkrev(i),
3190 r.linkrev(i),
3191 pr[0],
3191 pr[0],
3192 pr[1],
3192 pr[1],
3193 shortfn(node),
3193 shortfn(node),
3194 )
3194 )
3195 )
3195 )
3196
3196
3197
3197
3198 @command(
3198 @command(
3199 b'debugrevspec',
3199 b'debugrevspec',
3200 [
3200 [
3201 (
3201 (
3202 b'',
3202 b'',
3203 b'optimize',
3203 b'optimize',
3204 None,
3204 None,
3205 _(b'print parsed tree after optimizing (DEPRECATED)'),
3205 _(b'print parsed tree after optimizing (DEPRECATED)'),
3206 ),
3206 ),
3207 (
3207 (
3208 b'',
3208 b'',
3209 b'show-revs',
3209 b'show-revs',
3210 True,
3210 True,
3211 _(b'print list of result revisions (default)'),
3211 _(b'print list of result revisions (default)'),
3212 ),
3212 ),
3213 (
3213 (
3214 b's',
3214 b's',
3215 b'show-set',
3215 b'show-set',
3216 None,
3216 None,
3217 _(b'print internal representation of result set'),
3217 _(b'print internal representation of result set'),
3218 ),
3218 ),
3219 (
3219 (
3220 b'p',
3220 b'p',
3221 b'show-stage',
3221 b'show-stage',
3222 [],
3222 [],
3223 _(b'print parsed tree at the given stage'),
3223 _(b'print parsed tree at the given stage'),
3224 _(b'NAME'),
3224 _(b'NAME'),
3225 ),
3225 ),
3226 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3226 (b'', b'no-optimized', False, _(b'evaluate tree without optimization')),
3227 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3227 (b'', b'verify-optimized', False, _(b'verify optimized result')),
3228 ],
3228 ],
3229 b'REVSPEC',
3229 b'REVSPEC',
3230 )
3230 )
3231 def debugrevspec(ui, repo, expr, **opts):
3231 def debugrevspec(ui, repo, expr, **opts):
3232 """parse and apply a revision specification
3232 """parse and apply a revision specification
3233
3233
3234 Use -p/--show-stage option to print the parsed tree at the given stages.
3234 Use -p/--show-stage option to print the parsed tree at the given stages.
3235 Use -p all to print tree at every stage.
3235 Use -p all to print tree at every stage.
3236
3236
3237 Use --no-show-revs option with -s or -p to print only the set
3237 Use --no-show-revs option with -s or -p to print only the set
3238 representation or the parsed tree respectively.
3238 representation or the parsed tree respectively.
3239
3239
3240 Use --verify-optimized to compare the optimized result with the unoptimized
3240 Use --verify-optimized to compare the optimized result with the unoptimized
3241 one. Returns 1 if the optimized result differs.
3241 one. Returns 1 if the optimized result differs.
3242 """
3242 """
3243 opts = pycompat.byteskwargs(opts)
3243 opts = pycompat.byteskwargs(opts)
3244 aliases = ui.configitems(b'revsetalias')
3244 aliases = ui.configitems(b'revsetalias')
3245 stages = [
3245 stages = [
3246 (b'parsed', lambda tree: tree),
3246 (b'parsed', lambda tree: tree),
3247 (
3247 (
3248 b'expanded',
3248 b'expanded',
3249 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3249 lambda tree: revsetlang.expandaliases(tree, aliases, ui.warn),
3250 ),
3250 ),
3251 (b'concatenated', revsetlang.foldconcat),
3251 (b'concatenated', revsetlang.foldconcat),
3252 (b'analyzed', revsetlang.analyze),
3252 (b'analyzed', revsetlang.analyze),
3253 (b'optimized', revsetlang.optimize),
3253 (b'optimized', revsetlang.optimize),
3254 ]
3254 ]
3255 if opts[b'no_optimized']:
3255 if opts[b'no_optimized']:
3256 stages = stages[:-1]
3256 stages = stages[:-1]
3257 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3257 if opts[b'verify_optimized'] and opts[b'no_optimized']:
3258 raise error.Abort(
3258 raise error.Abort(
3259 _(b'cannot use --verify-optimized with --no-optimized')
3259 _(b'cannot use --verify-optimized with --no-optimized')
3260 )
3260 )
3261 stagenames = {n for n, f in stages}
3261 stagenames = {n for n, f in stages}
3262
3262
3263 showalways = set()
3263 showalways = set()
3264 showchanged = set()
3264 showchanged = set()
3265 if ui.verbose and not opts[b'show_stage']:
3265 if ui.verbose and not opts[b'show_stage']:
3266 # show parsed tree by --verbose (deprecated)
3266 # show parsed tree by --verbose (deprecated)
3267 showalways.add(b'parsed')
3267 showalways.add(b'parsed')
3268 showchanged.update([b'expanded', b'concatenated'])
3268 showchanged.update([b'expanded', b'concatenated'])
3269 if opts[b'optimize']:
3269 if opts[b'optimize']:
3270 showalways.add(b'optimized')
3270 showalways.add(b'optimized')
3271 if opts[b'show_stage'] and opts[b'optimize']:
3271 if opts[b'show_stage'] and opts[b'optimize']:
3272 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3272 raise error.Abort(_(b'cannot use --optimize with --show-stage'))
3273 if opts[b'show_stage'] == [b'all']:
3273 if opts[b'show_stage'] == [b'all']:
3274 showalways.update(stagenames)
3274 showalways.update(stagenames)
3275 else:
3275 else:
3276 for n in opts[b'show_stage']:
3276 for n in opts[b'show_stage']:
3277 if n not in stagenames:
3277 if n not in stagenames:
3278 raise error.Abort(_(b'invalid stage name: %s') % n)
3278 raise error.Abort(_(b'invalid stage name: %s') % n)
3279 showalways.update(opts[b'show_stage'])
3279 showalways.update(opts[b'show_stage'])
3280
3280
3281 treebystage = {}
3281 treebystage = {}
3282 printedtree = None
3282 printedtree = None
3283 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3283 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
3284 for n, f in stages:
3284 for n, f in stages:
3285 treebystage[n] = tree = f(tree)
3285 treebystage[n] = tree = f(tree)
3286 if n in showalways or (n in showchanged and tree != printedtree):
3286 if n in showalways or (n in showchanged and tree != printedtree):
3287 if opts[b'show_stage'] or n != b'parsed':
3287 if opts[b'show_stage'] or n != b'parsed':
3288 ui.write(b"* %s:\n" % n)
3288 ui.write(b"* %s:\n" % n)
3289 ui.write(revsetlang.prettyformat(tree), b"\n")
3289 ui.write(revsetlang.prettyformat(tree), b"\n")
3290 printedtree = tree
3290 printedtree = tree
3291
3291
3292 if opts[b'verify_optimized']:
3292 if opts[b'verify_optimized']:
3293 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3293 arevs = revset.makematcher(treebystage[b'analyzed'])(repo)
3294 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3294 brevs = revset.makematcher(treebystage[b'optimized'])(repo)
3295 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3295 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3296 ui.writenoi18n(
3296 ui.writenoi18n(
3297 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3297 b"* analyzed set:\n", stringutil.prettyrepr(arevs), b"\n"
3298 )
3298 )
3299 ui.writenoi18n(
3299 ui.writenoi18n(
3300 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3300 b"* optimized set:\n", stringutil.prettyrepr(brevs), b"\n"
3301 )
3301 )
3302 arevs = list(arevs)
3302 arevs = list(arevs)
3303 brevs = list(brevs)
3303 brevs = list(brevs)
3304 if arevs == brevs:
3304 if arevs == brevs:
3305 return 0
3305 return 0
3306 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3306 ui.writenoi18n(b'--- analyzed\n', label=b'diff.file_a')
3307 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3307 ui.writenoi18n(b'+++ optimized\n', label=b'diff.file_b')
3308 sm = difflib.SequenceMatcher(None, arevs, brevs)
3308 sm = difflib.SequenceMatcher(None, arevs, brevs)
3309 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3309 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
3310 if tag in ('delete', 'replace'):
3310 if tag in ('delete', 'replace'):
3311 for c in arevs[alo:ahi]:
3311 for c in arevs[alo:ahi]:
3312 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3312 ui.write(b'-%d\n' % c, label=b'diff.deleted')
3313 if tag in ('insert', 'replace'):
3313 if tag in ('insert', 'replace'):
3314 for c in brevs[blo:bhi]:
3314 for c in brevs[blo:bhi]:
3315 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3315 ui.write(b'+%d\n' % c, label=b'diff.inserted')
3316 if tag == 'equal':
3316 if tag == 'equal':
3317 for c in arevs[alo:ahi]:
3317 for c in arevs[alo:ahi]:
3318 ui.write(b' %d\n' % c)
3318 ui.write(b' %d\n' % c)
3319 return 1
3319 return 1
3320
3320
3321 func = revset.makematcher(tree)
3321 func = revset.makematcher(tree)
3322 revs = func(repo)
3322 revs = func(repo)
3323 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3323 if opts[b'show_set'] or (opts[b'show_set'] is None and ui.verbose):
3324 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3324 ui.writenoi18n(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
3325 if not opts[b'show_revs']:
3325 if not opts[b'show_revs']:
3326 return
3326 return
3327 for c in revs:
3327 for c in revs:
3328 ui.write(b"%d\n" % c)
3328 ui.write(b"%d\n" % c)
3329
3329
3330
3330
3331 @command(
3331 @command(
3332 b'debugserve',
3332 b'debugserve',
3333 [
3333 [
3334 (
3334 (
3335 b'',
3335 b'',
3336 b'sshstdio',
3336 b'sshstdio',
3337 False,
3337 False,
3338 _(b'run an SSH server bound to process handles'),
3338 _(b'run an SSH server bound to process handles'),
3339 ),
3339 ),
3340 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3340 (b'', b'logiofd', b'', _(b'file descriptor to log server I/O to')),
3341 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3341 (b'', b'logiofile', b'', _(b'file to log server I/O to')),
3342 ],
3342 ],
3343 b'',
3343 b'',
3344 )
3344 )
3345 def debugserve(ui, repo, **opts):
3345 def debugserve(ui, repo, **opts):
3346 """run a server with advanced settings
3346 """run a server with advanced settings
3347
3347
3348 This command is similar to :hg:`serve`. It exists partially as a
3348 This command is similar to :hg:`serve`. It exists partially as a
3349 workaround to the fact that ``hg serve --stdio`` must have specific
3349 workaround to the fact that ``hg serve --stdio`` must have specific
3350 arguments for security reasons.
3350 arguments for security reasons.
3351 """
3351 """
3352 opts = pycompat.byteskwargs(opts)
3352 opts = pycompat.byteskwargs(opts)
3353
3353
3354 if not opts[b'sshstdio']:
3354 if not opts[b'sshstdio']:
3355 raise error.Abort(_(b'only --sshstdio is currently supported'))
3355 raise error.Abort(_(b'only --sshstdio is currently supported'))
3356
3356
3357 logfh = None
3357 logfh = None
3358
3358
3359 if opts[b'logiofd'] and opts[b'logiofile']:
3359 if opts[b'logiofd'] and opts[b'logiofile']:
3360 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3360 raise error.Abort(_(b'cannot use both --logiofd and --logiofile'))
3361
3361
3362 if opts[b'logiofd']:
3362 if opts[b'logiofd']:
3363 # Ideally we would be line buffered. But line buffering in binary
3363 # Ideally we would be line buffered. But line buffering in binary
3364 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3364 # mode isn't supported and emits a warning in Python 3.8+. Disabling
3365 # buffering could have performance impacts. But since this isn't
3365 # buffering could have performance impacts. But since this isn't
3366 # performance critical code, it should be fine.
3366 # performance critical code, it should be fine.
3367 try:
3367 try:
3368 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3368 logfh = os.fdopen(int(opts[b'logiofd']), 'ab', 0)
3369 except OSError as e:
3369 except OSError as e:
3370 if e.errno != errno.ESPIPE:
3370 if e.errno != errno.ESPIPE:
3371 raise
3371 raise
3372 # can't seek a pipe, so `ab` mode fails on py3
3372 # can't seek a pipe, so `ab` mode fails on py3
3373 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3373 logfh = os.fdopen(int(opts[b'logiofd']), 'wb', 0)
3374 elif opts[b'logiofile']:
3374 elif opts[b'logiofile']:
3375 logfh = open(opts[b'logiofile'], b'ab', 0)
3375 logfh = open(opts[b'logiofile'], b'ab', 0)
3376
3376
3377 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3377 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
3378 s.serve_forever()
3378 s.serve_forever()
3379
3379
3380
3380
3381 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3381 @command(b'debugsetparents', [], _(b'REV1 [REV2]'))
3382 def debugsetparents(ui, repo, rev1, rev2=None):
3382 def debugsetparents(ui, repo, rev1, rev2=None):
3383 """manually set the parents of the current working directory
3383 """manually set the parents of the current working directory
3384
3384
3385 This is useful for writing repository conversion tools, but should
3385 This is useful for writing repository conversion tools, but should
3386 be used with care. For example, neither the working directory nor the
3386 be used with care. For example, neither the working directory nor the
3387 dirstate is updated, so file status may be incorrect after running this
3387 dirstate is updated, so file status may be incorrect after running this
3388 command.
3388 command.
3389
3389
3390 Returns 0 on success.
3390 Returns 0 on success.
3391 """
3391 """
3392
3392
3393 node1 = scmutil.revsingle(repo, rev1).node()
3393 node1 = scmutil.revsingle(repo, rev1).node()
3394 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3394 node2 = scmutil.revsingle(repo, rev2, b'null').node()
3395
3395
3396 with repo.wlock():
3396 with repo.wlock():
3397 repo.setparents(node1, node2)
3397 repo.setparents(node1, node2)
3398
3398
3399
3399
3400 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3400 @command(b'debugsidedata', cmdutil.debugrevlogopts, _(b'-c|-m|FILE REV'))
3401 def debugsidedata(ui, repo, file_, rev=None, **opts):
3401 def debugsidedata(ui, repo, file_, rev=None, **opts):
3402 """dump the side data for a cl/manifest/file revision
3402 """dump the side data for a cl/manifest/file revision
3403
3403
3404 Use --verbose to dump the sidedata content."""
3404 Use --verbose to dump the sidedata content."""
3405 opts = pycompat.byteskwargs(opts)
3405 opts = pycompat.byteskwargs(opts)
3406 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3406 if opts.get(b'changelog') or opts.get(b'manifest') or opts.get(b'dir'):
3407 if rev is not None:
3407 if rev is not None:
3408 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3408 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3409 file_, rev = None, file_
3409 file_, rev = None, file_
3410 elif rev is None:
3410 elif rev is None:
3411 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3411 raise error.CommandError(b'debugdata', _(b'invalid arguments'))
3412 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3412 r = cmdutil.openstorage(repo, b'debugdata', file_, opts)
3413 r = getattr(r, '_revlog', r)
3413 r = getattr(r, '_revlog', r)
3414 try:
3414 try:
3415 sidedata = r.sidedata(r.lookup(rev))
3415 sidedata = r.sidedata(r.lookup(rev))
3416 except KeyError:
3416 except KeyError:
3417 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3417 raise error.Abort(_(b'invalid revision identifier %s') % rev)
3418 if sidedata:
3418 if sidedata:
3419 sidedata = list(sidedata.items())
3419 sidedata = list(sidedata.items())
3420 sidedata.sort()
3420 sidedata.sort()
3421 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3421 ui.writenoi18n(b'%d sidedata entries\n' % len(sidedata))
3422 for key, value in sidedata:
3422 for key, value in sidedata:
3423 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3423 ui.writenoi18n(b' entry-%04o size %d\n' % (key, len(value)))
3424 if ui.verbose:
3424 if ui.verbose:
3425 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3425 ui.writenoi18n(b' %s\n' % stringutil.pprint(value))
3426
3426
3427
3427
3428 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3428 @command(b'debugssl', [], b'[SOURCE]', optionalrepo=True)
3429 def debugssl(ui, repo, source=None, **opts):
3429 def debugssl(ui, repo, source=None, **opts):
3430 '''test a secure connection to a server
3430 '''test a secure connection to a server
3431
3431
3432 This builds the certificate chain for the server on Windows, installing the
3432 This builds the certificate chain for the server on Windows, installing the
3433 missing intermediates and trusted root via Windows Update if necessary. It
3433 missing intermediates and trusted root via Windows Update if necessary. It
3434 does nothing on other platforms.
3434 does nothing on other platforms.
3435
3435
3436 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3436 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
3437 that server is used. See :hg:`help urls` for more information.
3437 that server is used. See :hg:`help urls` for more information.
3438
3438
3439 If the update succeeds, retry the original operation. Otherwise, the cause
3439 If the update succeeds, retry the original operation. Otherwise, the cause
3440 of the SSL error is likely another issue.
3440 of the SSL error is likely another issue.
3441 '''
3441 '''
3442 if not pycompat.iswindows:
3442 if not pycompat.iswindows:
3443 raise error.Abort(
3443 raise error.Abort(
3444 _(b'certificate chain building is only possible on Windows')
3444 _(b'certificate chain building is only possible on Windows')
3445 )
3445 )
3446
3446
3447 if not source:
3447 if not source:
3448 if not repo:
3448 if not repo:
3449 raise error.Abort(
3449 raise error.Abort(
3450 _(
3450 _(
3451 b"there is no Mercurial repository here, and no "
3451 b"there is no Mercurial repository here, and no "
3452 b"server specified"
3452 b"server specified"
3453 )
3453 )
3454 )
3454 )
3455 source = b"default"
3455 source = b"default"
3456
3456
3457 source, branches = hg.parseurl(ui.expandpath(source))
3457 source, branches = hg.parseurl(ui.expandpath(source))
3458 url = util.url(source)
3458 url = util.url(source)
3459
3459
3460 defaultport = {b'https': 443, b'ssh': 22}
3460 defaultport = {b'https': 443, b'ssh': 22}
3461 if url.scheme in defaultport:
3461 if url.scheme in defaultport:
3462 try:
3462 try:
3463 addr = (url.host, int(url.port or defaultport[url.scheme]))
3463 addr = (url.host, int(url.port or defaultport[url.scheme]))
3464 except ValueError:
3464 except ValueError:
3465 raise error.Abort(_(b"malformed port number in URL"))
3465 raise error.Abort(_(b"malformed port number in URL"))
3466 else:
3466 else:
3467 raise error.Abort(_(b"only https and ssh connections are supported"))
3467 raise error.Abort(_(b"only https and ssh connections are supported"))
3468
3468
3469 from . import win32
3469 from . import win32
3470
3470
3471 s = ssl.wrap_socket(
3471 s = ssl.wrap_socket(
3472 socket.socket(),
3472 socket.socket(),
3473 ssl_version=ssl.PROTOCOL_TLS,
3473 ssl_version=ssl.PROTOCOL_TLS,
3474 cert_reqs=ssl.CERT_NONE,
3474 cert_reqs=ssl.CERT_NONE,
3475 ca_certs=None,
3475 ca_certs=None,
3476 )
3476 )
3477
3477
3478 try:
3478 try:
3479 s.connect(addr)
3479 s.connect(addr)
3480 cert = s.getpeercert(True)
3480 cert = s.getpeercert(True)
3481
3481
3482 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3482 ui.status(_(b'checking the certificate chain for %s\n') % url.host)
3483
3483
3484 complete = win32.checkcertificatechain(cert, build=False)
3484 complete = win32.checkcertificatechain(cert, build=False)
3485
3485
3486 if not complete:
3486 if not complete:
3487 ui.status(_(b'certificate chain is incomplete, updating... '))
3487 ui.status(_(b'certificate chain is incomplete, updating... '))
3488
3488
3489 if not win32.checkcertificatechain(cert):
3489 if not win32.checkcertificatechain(cert):
3490 ui.status(_(b'failed.\n'))
3490 ui.status(_(b'failed.\n'))
3491 else:
3491 else:
3492 ui.status(_(b'done.\n'))
3492 ui.status(_(b'done.\n'))
3493 else:
3493 else:
3494 ui.status(_(b'full certificate chain is available\n'))
3494 ui.status(_(b'full certificate chain is available\n'))
3495 finally:
3495 finally:
3496 s.close()
3496 s.close()
3497
3497
3498
3498
3499 @command(
3499 @command(
3500 b"debugbackupbundle",
3500 b"debugbackupbundle",
3501 [
3501 [
3502 (
3502 (
3503 b"",
3503 b"",
3504 b"recover",
3504 b"recover",
3505 b"",
3505 b"",
3506 b"brings the specified changeset back into the repository",
3506 b"brings the specified changeset back into the repository",
3507 )
3507 )
3508 ]
3508 ]
3509 + cmdutil.logopts,
3509 + cmdutil.logopts,
3510 _(b"hg debugbackupbundle [--recover HASH]"),
3510 _(b"hg debugbackupbundle [--recover HASH]"),
3511 )
3511 )
3512 def debugbackupbundle(ui, repo, *pats, **opts):
3512 def debugbackupbundle(ui, repo, *pats, **opts):
3513 """lists the changesets available in backup bundles
3513 """lists the changesets available in backup bundles
3514
3514
3515 Without any arguments, this command prints a list of the changesets in each
3515 Without any arguments, this command prints a list of the changesets in each
3516 backup bundle.
3516 backup bundle.
3517
3517
3518 --recover takes a changeset hash and unbundles the first bundle that
3518 --recover takes a changeset hash and unbundles the first bundle that
3519 contains that hash, which puts that changeset back in your repository.
3519 contains that hash, which puts that changeset back in your repository.
3520
3520
3521 --verbose will print the entire commit message and the bundle path for that
3521 --verbose will print the entire commit message and the bundle path for that
3522 backup.
3522 backup.
3523 """
3523 """
3524 backups = list(
3524 backups = list(
3525 filter(
3525 filter(
3526 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3526 os.path.isfile, glob.glob(repo.vfs.join(b"strip-backup") + b"/*.hg")
3527 )
3527 )
3528 )
3528 )
3529 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3529 backups.sort(key=lambda x: os.path.getmtime(x), reverse=True)
3530
3530
3531 opts = pycompat.byteskwargs(opts)
3531 opts = pycompat.byteskwargs(opts)
3532 opts[b"bundle"] = b""
3532 opts[b"bundle"] = b""
3533 opts[b"force"] = None
3533 opts[b"force"] = None
3534 limit = logcmdutil.getlimit(opts)
3534 limit = logcmdutil.getlimit(opts)
3535
3535
3536 def display(other, chlist, displayer):
3536 def display(other, chlist, displayer):
3537 if opts.get(b"newest_first"):
3537 if opts.get(b"newest_first"):
3538 chlist.reverse()
3538 chlist.reverse()
3539 count = 0
3539 count = 0
3540 for n in chlist:
3540 for n in chlist:
3541 if limit is not None and count >= limit:
3541 if limit is not None and count >= limit:
3542 break
3542 break
3543 parents = [True for p in other.changelog.parents(n) if p != nullid]
3543 parents = [True for p in other.changelog.parents(n) if p != nullid]
3544 if opts.get(b"no_merges") and len(parents) == 2:
3544 if opts.get(b"no_merges") and len(parents) == 2:
3545 continue
3545 continue
3546 count += 1
3546 count += 1
3547 displayer.show(other[n])
3547 displayer.show(other[n])
3548
3548
3549 recovernode = opts.get(b"recover")
3549 recovernode = opts.get(b"recover")
3550 if recovernode:
3550 if recovernode:
3551 if scmutil.isrevsymbol(repo, recovernode):
3551 if scmutil.isrevsymbol(repo, recovernode):
3552 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3552 ui.warn(_(b"%s already exists in the repo\n") % recovernode)
3553 return
3553 return
3554 elif backups:
3554 elif backups:
3555 msg = _(
3555 msg = _(
3556 b"Recover changesets using: hg debugbackupbundle --recover "
3556 b"Recover changesets using: hg debugbackupbundle --recover "
3557 b"<changeset hash>\n\nAvailable backup changesets:"
3557 b"<changeset hash>\n\nAvailable backup changesets:"
3558 )
3558 )
3559 ui.status(msg, label=b"status.removed")
3559 ui.status(msg, label=b"status.removed")
3560 else:
3560 else:
3561 ui.status(_(b"no backup changesets found\n"))
3561 ui.status(_(b"no backup changesets found\n"))
3562 return
3562 return
3563
3563
3564 for backup in backups:
3564 for backup in backups:
3565 # Much of this is copied from the hg incoming logic
3565 # Much of this is copied from the hg incoming logic
3566 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3566 source = ui.expandpath(os.path.relpath(backup, encoding.getcwd()))
3567 source, branches = hg.parseurl(source, opts.get(b"branch"))
3567 source, branches = hg.parseurl(source, opts.get(b"branch"))
3568 try:
3568 try:
3569 other = hg.peer(repo, opts, source)
3569 other = hg.peer(repo, opts, source)
3570 except error.LookupError as ex:
3570 except error.LookupError as ex:
3571 msg = _(b"\nwarning: unable to open bundle %s") % source
3571 msg = _(b"\nwarning: unable to open bundle %s") % source
3572 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3572 hint = _(b"\n(missing parent rev %s)\n") % short(ex.name)
3573 ui.warn(msg, hint=hint)
3573 ui.warn(msg, hint=hint)
3574 continue
3574 continue
3575 revs, checkout = hg.addbranchrevs(
3575 revs, checkout = hg.addbranchrevs(
3576 repo, other, branches, opts.get(b"rev")
3576 repo, other, branches, opts.get(b"rev")
3577 )
3577 )
3578
3578
3579 if revs:
3579 if revs:
3580 revs = [other.lookup(rev) for rev in revs]
3580 revs = [other.lookup(rev) for rev in revs]
3581
3581
3582 quiet = ui.quiet
3582 quiet = ui.quiet
3583 try:
3583 try:
3584 ui.quiet = True
3584 ui.quiet = True
3585 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3585 other, chlist, cleanupfn = bundlerepo.getremotechanges(
3586 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3586 ui, repo, other, revs, opts[b"bundle"], opts[b"force"]
3587 )
3587 )
3588 except error.LookupError:
3588 except error.LookupError:
3589 continue
3589 continue
3590 finally:
3590 finally:
3591 ui.quiet = quiet
3591 ui.quiet = quiet
3592
3592
3593 try:
3593 try:
3594 if not chlist:
3594 if not chlist:
3595 continue
3595 continue
3596 if recovernode:
3596 if recovernode:
3597 with repo.lock(), repo.transaction(b"unbundle") as tr:
3597 with repo.lock(), repo.transaction(b"unbundle") as tr:
3598 if scmutil.isrevsymbol(other, recovernode):
3598 if scmutil.isrevsymbol(other, recovernode):
3599 ui.status(_(b"Unbundling %s\n") % (recovernode))
3599 ui.status(_(b"Unbundling %s\n") % (recovernode))
3600 f = hg.openpath(ui, source)
3600 f = hg.openpath(ui, source)
3601 gen = exchange.readbundle(ui, f, source)
3601 gen = exchange.readbundle(ui, f, source)
3602 if isinstance(gen, bundle2.unbundle20):
3602 if isinstance(gen, bundle2.unbundle20):
3603 bundle2.applybundle(
3603 bundle2.applybundle(
3604 repo,
3604 repo,
3605 gen,
3605 gen,
3606 tr,
3606 tr,
3607 source=b"unbundle",
3607 source=b"unbundle",
3608 url=b"bundle:" + source,
3608 url=b"bundle:" + source,
3609 )
3609 )
3610 else:
3610 else:
3611 gen.apply(repo, b"unbundle", b"bundle:" + source)
3611 gen.apply(repo, b"unbundle", b"bundle:" + source)
3612 break
3612 break
3613 else:
3613 else:
3614 backupdate = encoding.strtolocal(
3614 backupdate = encoding.strtolocal(
3615 time.strftime(
3615 time.strftime(
3616 "%a %H:%M, %Y-%m-%d",
3616 "%a %H:%M, %Y-%m-%d",
3617 time.localtime(os.path.getmtime(source)),
3617 time.localtime(os.path.getmtime(source)),
3618 )
3618 )
3619 )
3619 )
3620 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3620 ui.status(b"\n%s\n" % (backupdate.ljust(50)))
3621 if ui.verbose:
3621 if ui.verbose:
3622 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3622 ui.status(b"%s%s\n" % (b"bundle:".ljust(13), source))
3623 else:
3623 else:
3624 opts[
3624 opts[
3625 b"template"
3625 b"template"
3626 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3626 ] = b"{label('status.modified', node|short)} {desc|firstline}\n"
3627 displayer = logcmdutil.changesetdisplayer(
3627 displayer = logcmdutil.changesetdisplayer(
3628 ui, other, opts, False
3628 ui, other, opts, False
3629 )
3629 )
3630 display(other, chlist, displayer)
3630 display(other, chlist, displayer)
3631 displayer.close()
3631 displayer.close()
3632 finally:
3632 finally:
3633 cleanupfn()
3633 cleanupfn()
3634
3634
3635
3635
3636 @command(
3636 @command(
3637 b'debugsub',
3637 b'debugsub',
3638 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3638 [(b'r', b'rev', b'', _(b'revision to check'), _(b'REV'))],
3639 _(b'[-r REV] [REV]'),
3639 _(b'[-r REV] [REV]'),
3640 )
3640 )
3641 def debugsub(ui, repo, rev=None):
3641 def debugsub(ui, repo, rev=None):
3642 ctx = scmutil.revsingle(repo, rev, None)
3642 ctx = scmutil.revsingle(repo, rev, None)
3643 for k, v in sorted(ctx.substate.items()):
3643 for k, v in sorted(ctx.substate.items()):
3644 ui.writenoi18n(b'path %s\n' % k)
3644 ui.writenoi18n(b'path %s\n' % k)
3645 ui.writenoi18n(b' source %s\n' % v[0])
3645 ui.writenoi18n(b' source %s\n' % v[0])
3646 ui.writenoi18n(b' revision %s\n' % v[1])
3646 ui.writenoi18n(b' revision %s\n' % v[1])
3647
3647
3648
3648
3649 @command(
3649 @command(
3650 b'debugsuccessorssets',
3650 b'debugsuccessorssets',
3651 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3651 [(b'', b'closest', False, _(b'return closest successors sets only'))],
3652 _(b'[REV]'),
3652 _(b'[REV]'),
3653 )
3653 )
3654 def debugsuccessorssets(ui, repo, *revs, **opts):
3654 def debugsuccessorssets(ui, repo, *revs, **opts):
3655 """show set of successors for revision
3655 """show set of successors for revision
3656
3656
3657 A successors set of changeset A is a consistent group of revisions that
3657 A successors set of changeset A is a consistent group of revisions that
3658 succeed A. It contains non-obsolete changesets only unless closests
3658 succeed A. It contains non-obsolete changesets only unless closests
3659 successors set is set.
3659 successors set is set.
3660
3660
3661 In most cases a changeset A has a single successors set containing a single
3661 In most cases a changeset A has a single successors set containing a single
3662 successor (changeset A replaced by A').
3662 successor (changeset A replaced by A').
3663
3663
3664 A changeset that is made obsolete with no successors are called "pruned".
3664 A changeset that is made obsolete with no successors are called "pruned".
3665 Such changesets have no successors sets at all.
3665 Such changesets have no successors sets at all.
3666
3666
3667 A changeset that has been "split" will have a successors set containing
3667 A changeset that has been "split" will have a successors set containing
3668 more than one successor.
3668 more than one successor.
3669
3669
3670 A changeset that has been rewritten in multiple different ways is called
3670 A changeset that has been rewritten in multiple different ways is called
3671 "divergent". Such changesets have multiple successor sets (each of which
3671 "divergent". Such changesets have multiple successor sets (each of which
3672 may also be split, i.e. have multiple successors).
3672 may also be split, i.e. have multiple successors).
3673
3673
3674 Results are displayed as follows::
3674 Results are displayed as follows::
3675
3675
3676 <rev1>
3676 <rev1>
3677 <successors-1A>
3677 <successors-1A>
3678 <rev2>
3678 <rev2>
3679 <successors-2A>
3679 <successors-2A>
3680 <successors-2B1> <successors-2B2> <successors-2B3>
3680 <successors-2B1> <successors-2B2> <successors-2B3>
3681
3681
3682 Here rev2 has two possible (i.e. divergent) successors sets. The first
3682 Here rev2 has two possible (i.e. divergent) successors sets. The first
3683 holds one element, whereas the second holds three (i.e. the changeset has
3683 holds one element, whereas the second holds three (i.e. the changeset has
3684 been split).
3684 been split).
3685 """
3685 """
3686 # passed to successorssets caching computation from one call to another
3686 # passed to successorssets caching computation from one call to another
3687 cache = {}
3687 cache = {}
3688 ctx2str = bytes
3688 ctx2str = bytes
3689 node2str = short
3689 node2str = short
3690 for rev in scmutil.revrange(repo, revs):
3690 for rev in scmutil.revrange(repo, revs):
3691 ctx = repo[rev]
3691 ctx = repo[rev]
3692 ui.write(b'%s\n' % ctx2str(ctx))
3692 ui.write(b'%s\n' % ctx2str(ctx))
3693 for succsset in obsutil.successorssets(
3693 for succsset in obsutil.successorssets(
3694 repo, ctx.node(), closest=opts['closest'], cache=cache
3694 repo, ctx.node(), closest=opts['closest'], cache=cache
3695 ):
3695 ):
3696 if succsset:
3696 if succsset:
3697 ui.write(b' ')
3697 ui.write(b' ')
3698 ui.write(node2str(succsset[0]))
3698 ui.write(node2str(succsset[0]))
3699 for node in succsset[1:]:
3699 for node in succsset[1:]:
3700 ui.write(b' ')
3700 ui.write(b' ')
3701 ui.write(node2str(node))
3701 ui.write(node2str(node))
3702 ui.write(b'\n')
3702 ui.write(b'\n')
3703
3703
3704
3704
3705 @command(b'debugtagscache', [])
3705 @command(b'debugtagscache', [])
3706 def debugtagscache(ui, repo):
3706 def debugtagscache(ui, repo):
3707 """display the contents of .hg/cache/hgtagsfnodes1"""
3707 """display the contents of .hg/cache/hgtagsfnodes1"""
3708 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3708 cache = tagsmod.hgtagsfnodescache(repo.unfiltered())
3709 for r in repo:
3709 for r in repo:
3710 node = repo[r].node()
3710 node = repo[r].node()
3711 tagsnode = cache.getfnode(node, computemissing=False)
3711 tagsnode = cache.getfnode(node, computemissing=False)
3712 tagsnodedisplay = hex(tagsnode) if tagsnode else b'missing/invalid'
3712 tagsnodedisplay = hex(tagsnode) if tagsnode else b'missing/invalid'
3713 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3713 ui.write(b'%d %s %s\n' % (r, hex(node), tagsnodedisplay))
3714
3714
3715
3715
3716 @command(
3716 @command(
3717 b'debugtemplate',
3717 b'debugtemplate',
3718 [
3718 [
3719 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3719 (b'r', b'rev', [], _(b'apply template on changesets'), _(b'REV')),
3720 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3720 (b'D', b'define', [], _(b'define template keyword'), _(b'KEY=VALUE')),
3721 ],
3721 ],
3722 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3722 _(b'[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
3723 optionalrepo=True,
3723 optionalrepo=True,
3724 )
3724 )
3725 def debugtemplate(ui, repo, tmpl, **opts):
3725 def debugtemplate(ui, repo, tmpl, **opts):
3726 """parse and apply a template
3726 """parse and apply a template
3727
3727
3728 If -r/--rev is given, the template is processed as a log template and
3728 If -r/--rev is given, the template is processed as a log template and
3729 applied to the given changesets. Otherwise, it is processed as a generic
3729 applied to the given changesets. Otherwise, it is processed as a generic
3730 template.
3730 template.
3731
3731
3732 Use --verbose to print the parsed tree.
3732 Use --verbose to print the parsed tree.
3733 """
3733 """
3734 revs = None
3734 revs = None
3735 if opts['rev']:
3735 if opts['rev']:
3736 if repo is None:
3736 if repo is None:
3737 raise error.RepoError(
3737 raise error.RepoError(
3738 _(b'there is no Mercurial repository here (.hg not found)')
3738 _(b'there is no Mercurial repository here (.hg not found)')
3739 )
3739 )
3740 revs = scmutil.revrange(repo, opts['rev'])
3740 revs = scmutil.revrange(repo, opts['rev'])
3741
3741
3742 props = {}
3742 props = {}
3743 for d in opts['define']:
3743 for d in opts['define']:
3744 try:
3744 try:
3745 k, v = (e.strip() for e in d.split(b'=', 1))
3745 k, v = (e.strip() for e in d.split(b'=', 1))
3746 if not k or k == b'ui':
3746 if not k or k == b'ui':
3747 raise ValueError
3747 raise ValueError
3748 props[k] = v
3748 props[k] = v
3749 except ValueError:
3749 except ValueError:
3750 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3750 raise error.Abort(_(b'malformed keyword definition: %s') % d)
3751
3751
3752 if ui.verbose:
3752 if ui.verbose:
3753 aliases = ui.configitems(b'templatealias')
3753 aliases = ui.configitems(b'templatealias')
3754 tree = templater.parse(tmpl)
3754 tree = templater.parse(tmpl)
3755 ui.note(templater.prettyformat(tree), b'\n')
3755 ui.note(templater.prettyformat(tree), b'\n')
3756 newtree = templater.expandaliases(tree, aliases)
3756 newtree = templater.expandaliases(tree, aliases)
3757 if newtree != tree:
3757 if newtree != tree:
3758 ui.notenoi18n(
3758 ui.notenoi18n(
3759 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3759 b"* expanded:\n", templater.prettyformat(newtree), b'\n'
3760 )
3760 )
3761
3761
3762 if revs is None:
3762 if revs is None:
3763 tres = formatter.templateresources(ui, repo)
3763 tres = formatter.templateresources(ui, repo)
3764 t = formatter.maketemplater(ui, tmpl, resources=tres)
3764 t = formatter.maketemplater(ui, tmpl, resources=tres)
3765 if ui.verbose:
3765 if ui.verbose:
3766 kwds, funcs = t.symbolsuseddefault()
3766 kwds, funcs = t.symbolsuseddefault()
3767 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3767 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3768 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3768 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3769 ui.write(t.renderdefault(props))
3769 ui.write(t.renderdefault(props))
3770 else:
3770 else:
3771 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3771 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
3772 if ui.verbose:
3772 if ui.verbose:
3773 kwds, funcs = displayer.t.symbolsuseddefault()
3773 kwds, funcs = displayer.t.symbolsuseddefault()
3774 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3774 ui.writenoi18n(b"* keywords: %s\n" % b', '.join(sorted(kwds)))
3775 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3775 ui.writenoi18n(b"* functions: %s\n" % b', '.join(sorted(funcs)))
3776 for r in revs:
3776 for r in revs:
3777 displayer.show(repo[r], **pycompat.strkwargs(props))
3777 displayer.show(repo[r], **pycompat.strkwargs(props))
3778 displayer.close()
3778 displayer.close()
3779
3779
3780
3780
3781 @command(
3781 @command(
3782 b'debuguigetpass',
3782 b'debuguigetpass',
3783 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3783 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3784 _(b'[-p TEXT]'),
3784 _(b'[-p TEXT]'),
3785 norepo=True,
3785 norepo=True,
3786 )
3786 )
3787 def debuguigetpass(ui, prompt=b''):
3787 def debuguigetpass(ui, prompt=b''):
3788 """show prompt to type password"""
3788 """show prompt to type password"""
3789 r = ui.getpass(prompt)
3789 r = ui.getpass(prompt)
3790 if r is not None:
3791 r = encoding.strtolocal(r)
3792 else:
3793 r = b"<default response>"
3790 ui.writenoi18n(b'response: %s\n' % r)
3794 ui.writenoi18n(b'response: %s\n' % r)
3791
3795
3792
3796
3793 @command(
3797 @command(
3794 b'debuguiprompt',
3798 b'debuguiprompt',
3795 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3799 [(b'p', b'prompt', b'', _(b'prompt text'), _(b'TEXT')),],
3796 _(b'[-p TEXT]'),
3800 _(b'[-p TEXT]'),
3797 norepo=True,
3801 norepo=True,
3798 )
3802 )
3799 def debuguiprompt(ui, prompt=b''):
3803 def debuguiprompt(ui, prompt=b''):
3800 """show plain prompt"""
3804 """show plain prompt"""
3801 r = ui.prompt(prompt)
3805 r = ui.prompt(prompt)
3802 ui.writenoi18n(b'response: %s\n' % r)
3806 ui.writenoi18n(b'response: %s\n' % r)
3803
3807
3804
3808
3805 @command(b'debugupdatecaches', [])
3809 @command(b'debugupdatecaches', [])
3806 def debugupdatecaches(ui, repo, *pats, **opts):
3810 def debugupdatecaches(ui, repo, *pats, **opts):
3807 """warm all known caches in the repository"""
3811 """warm all known caches in the repository"""
3808 with repo.wlock(), repo.lock():
3812 with repo.wlock(), repo.lock():
3809 repo.updatecaches(full=True)
3813 repo.updatecaches(full=True)
3810
3814
3811
3815
3812 @command(
3816 @command(
3813 b'debugupgraderepo',
3817 b'debugupgraderepo',
3814 [
3818 [
3815 (
3819 (
3816 b'o',
3820 b'o',
3817 b'optimize',
3821 b'optimize',
3818 [],
3822 [],
3819 _(b'extra optimization to perform'),
3823 _(b'extra optimization to perform'),
3820 _(b'NAME'),
3824 _(b'NAME'),
3821 ),
3825 ),
3822 (b'', b'run', False, _(b'performs an upgrade')),
3826 (b'', b'run', False, _(b'performs an upgrade')),
3823 (b'', b'backup', True, _(b'keep the old repository content around')),
3827 (b'', b'backup', True, _(b'keep the old repository content around')),
3824 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
3828 (b'', b'changelog', None, _(b'select the changelog for upgrade')),
3825 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
3829 (b'', b'manifest', None, _(b'select the manifest for upgrade')),
3826 ],
3830 ],
3827 )
3831 )
3828 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
3832 def debugupgraderepo(ui, repo, run=False, optimize=None, backup=True, **opts):
3829 """upgrade a repository to use different features
3833 """upgrade a repository to use different features
3830
3834
3831 If no arguments are specified, the repository is evaluated for upgrade
3835 If no arguments are specified, the repository is evaluated for upgrade
3832 and a list of problems and potential optimizations is printed.
3836 and a list of problems and potential optimizations is printed.
3833
3837
3834 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
3838 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
3835 can be influenced via additional arguments. More details will be provided
3839 can be influenced via additional arguments. More details will be provided
3836 by the command output when run without ``--run``.
3840 by the command output when run without ``--run``.
3837
3841
3838 During the upgrade, the repository will be locked and no writes will be
3842 During the upgrade, the repository will be locked and no writes will be
3839 allowed.
3843 allowed.
3840
3844
3841 At the end of the upgrade, the repository may not be readable while new
3845 At the end of the upgrade, the repository may not be readable while new
3842 repository data is swapped in. This window will be as long as it takes to
3846 repository data is swapped in. This window will be as long as it takes to
3843 rename some directories inside the ``.hg`` directory. On most machines, this
3847 rename some directories inside the ``.hg`` directory. On most machines, this
3844 should complete almost instantaneously and the chances of a consumer being
3848 should complete almost instantaneously and the chances of a consumer being
3845 unable to access the repository should be low.
3849 unable to access the repository should be low.
3846
3850
3847 By default, all revlog will be upgraded. You can restrict this using flag
3851 By default, all revlog will be upgraded. You can restrict this using flag
3848 such as `--manifest`:
3852 such as `--manifest`:
3849
3853
3850 * `--manifest`: only optimize the manifest
3854 * `--manifest`: only optimize the manifest
3851 * `--no-manifest`: optimize all revlog but the manifest
3855 * `--no-manifest`: optimize all revlog but the manifest
3852 * `--changelog`: optimize the changelog only
3856 * `--changelog`: optimize the changelog only
3853 * `--no-changelog --no-manifest`: optimize filelogs only
3857 * `--no-changelog --no-manifest`: optimize filelogs only
3854 """
3858 """
3855 return upgrade.upgraderepo(
3859 return upgrade.upgraderepo(
3856 ui, repo, run=run, optimize=optimize, backup=backup, **opts
3860 ui, repo, run=run, optimize=optimize, backup=backup, **opts
3857 )
3861 )
3858
3862
3859
3863
3860 @command(
3864 @command(
3861 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
3865 b'debugwalk', cmdutil.walkopts, _(b'[OPTION]... [FILE]...'), inferrepo=True
3862 )
3866 )
3863 def debugwalk(ui, repo, *pats, **opts):
3867 def debugwalk(ui, repo, *pats, **opts):
3864 """show how files match on given patterns"""
3868 """show how files match on given patterns"""
3865 opts = pycompat.byteskwargs(opts)
3869 opts = pycompat.byteskwargs(opts)
3866 m = scmutil.match(repo[None], pats, opts)
3870 m = scmutil.match(repo[None], pats, opts)
3867 if ui.verbose:
3871 if ui.verbose:
3868 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
3872 ui.writenoi18n(b'* matcher:\n', stringutil.prettyrepr(m), b'\n')
3869 items = list(repo[None].walk(m))
3873 items = list(repo[None].walk(m))
3870 if not items:
3874 if not items:
3871 return
3875 return
3872 f = lambda fn: fn
3876 f = lambda fn: fn
3873 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
3877 if ui.configbool(b'ui', b'slash') and pycompat.ossep != b'/':
3874 f = lambda fn: util.normpath(fn)
3878 f = lambda fn: util.normpath(fn)
3875 fmt = b'f %%-%ds %%-%ds %%s' % (
3879 fmt = b'f %%-%ds %%-%ds %%s' % (
3876 max([len(abs) for abs in items]),
3880 max([len(abs) for abs in items]),
3877 max([len(repo.pathto(abs)) for abs in items]),
3881 max([len(repo.pathto(abs)) for abs in items]),
3878 )
3882 )
3879 for abs in items:
3883 for abs in items:
3880 line = fmt % (
3884 line = fmt % (
3881 abs,
3885 abs,
3882 f(repo.pathto(abs)),
3886 f(repo.pathto(abs)),
3883 m.exact(abs) and b'exact' or b'',
3887 m.exact(abs) and b'exact' or b'',
3884 )
3888 )
3885 ui.write(b"%s\n" % line.rstrip())
3889 ui.write(b"%s\n" % line.rstrip())
3886
3890
3887
3891
3888 @command(b'debugwhyunstable', [], _(b'REV'))
3892 @command(b'debugwhyunstable', [], _(b'REV'))
3889 def debugwhyunstable(ui, repo, rev):
3893 def debugwhyunstable(ui, repo, rev):
3890 """explain instabilities of a changeset"""
3894 """explain instabilities of a changeset"""
3891 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
3895 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
3892 dnodes = b''
3896 dnodes = b''
3893 if entry.get(b'divergentnodes'):
3897 if entry.get(b'divergentnodes'):
3894 dnodes = (
3898 dnodes = (
3895 b' '.join(
3899 b' '.join(
3896 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
3900 b'%s (%s)' % (ctx.hex(), ctx.phasestr())
3897 for ctx in entry[b'divergentnodes']
3901 for ctx in entry[b'divergentnodes']
3898 )
3902 )
3899 + b' '
3903 + b' '
3900 )
3904 )
3901 ui.write(
3905 ui.write(
3902 b'%s: %s%s %s\n'
3906 b'%s: %s%s %s\n'
3903 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
3907 % (entry[b'instability'], dnodes, entry[b'reason'], entry[b'node'])
3904 )
3908 )
3905
3909
3906
3910
3907 @command(
3911 @command(
3908 b'debugwireargs',
3912 b'debugwireargs',
3909 [
3913 [
3910 (b'', b'three', b'', b'three'),
3914 (b'', b'three', b'', b'three'),
3911 (b'', b'four', b'', b'four'),
3915 (b'', b'four', b'', b'four'),
3912 (b'', b'five', b'', b'five'),
3916 (b'', b'five', b'', b'five'),
3913 ]
3917 ]
3914 + cmdutil.remoteopts,
3918 + cmdutil.remoteopts,
3915 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
3919 _(b'REPO [OPTIONS]... [ONE [TWO]]'),
3916 norepo=True,
3920 norepo=True,
3917 )
3921 )
3918 def debugwireargs(ui, repopath, *vals, **opts):
3922 def debugwireargs(ui, repopath, *vals, **opts):
3919 opts = pycompat.byteskwargs(opts)
3923 opts = pycompat.byteskwargs(opts)
3920 repo = hg.peer(ui, opts, repopath)
3924 repo = hg.peer(ui, opts, repopath)
3921 for opt in cmdutil.remoteopts:
3925 for opt in cmdutil.remoteopts:
3922 del opts[opt[1]]
3926 del opts[opt[1]]
3923 args = {}
3927 args = {}
3924 for k, v in pycompat.iteritems(opts):
3928 for k, v in pycompat.iteritems(opts):
3925 if v:
3929 if v:
3926 args[k] = v
3930 args[k] = v
3927 args = pycompat.strkwargs(args)
3931 args = pycompat.strkwargs(args)
3928 # run twice to check that we don't mess up the stream for the next command
3932 # run twice to check that we don't mess up the stream for the next command
3929 res1 = repo.debugwireargs(*vals, **args)
3933 res1 = repo.debugwireargs(*vals, **args)
3930 res2 = repo.debugwireargs(*vals, **args)
3934 res2 = repo.debugwireargs(*vals, **args)
3931 ui.write(b"%s\n" % res1)
3935 ui.write(b"%s\n" % res1)
3932 if res1 != res2:
3936 if res1 != res2:
3933 ui.warn(b"%s\n" % res2)
3937 ui.warn(b"%s\n" % res2)
3934
3938
3935
3939
3936 def _parsewirelangblocks(fh):
3940 def _parsewirelangblocks(fh):
3937 activeaction = None
3941 activeaction = None
3938 blocklines = []
3942 blocklines = []
3939 lastindent = 0
3943 lastindent = 0
3940
3944
3941 for line in fh:
3945 for line in fh:
3942 line = line.rstrip()
3946 line = line.rstrip()
3943 if not line:
3947 if not line:
3944 continue
3948 continue
3945
3949
3946 if line.startswith(b'#'):
3950 if line.startswith(b'#'):
3947 continue
3951 continue
3948
3952
3949 if not line.startswith(b' '):
3953 if not line.startswith(b' '):
3950 # New block. Flush previous one.
3954 # New block. Flush previous one.
3951 if activeaction:
3955 if activeaction:
3952 yield activeaction, blocklines
3956 yield activeaction, blocklines
3953
3957
3954 activeaction = line
3958 activeaction = line
3955 blocklines = []
3959 blocklines = []
3956 lastindent = 0
3960 lastindent = 0
3957 continue
3961 continue
3958
3962
3959 # Else we start with an indent.
3963 # Else we start with an indent.
3960
3964
3961 if not activeaction:
3965 if not activeaction:
3962 raise error.Abort(_(b'indented line outside of block'))
3966 raise error.Abort(_(b'indented line outside of block'))
3963
3967
3964 indent = len(line) - len(line.lstrip())
3968 indent = len(line) - len(line.lstrip())
3965
3969
3966 # If this line is indented more than the last line, concatenate it.
3970 # If this line is indented more than the last line, concatenate it.
3967 if indent > lastindent and blocklines:
3971 if indent > lastindent and blocklines:
3968 blocklines[-1] += line.lstrip()
3972 blocklines[-1] += line.lstrip()
3969 else:
3973 else:
3970 blocklines.append(line)
3974 blocklines.append(line)
3971 lastindent = indent
3975 lastindent = indent
3972
3976
3973 # Flush last block.
3977 # Flush last block.
3974 if activeaction:
3978 if activeaction:
3975 yield activeaction, blocklines
3979 yield activeaction, blocklines
3976
3980
3977
3981
3978 @command(
3982 @command(
3979 b'debugwireproto',
3983 b'debugwireproto',
3980 [
3984 [
3981 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
3985 (b'', b'localssh', False, _(b'start an SSH server for this repo')),
3982 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
3986 (b'', b'peer', b'', _(b'construct a specific version of the peer')),
3983 (
3987 (
3984 b'',
3988 b'',
3985 b'noreadstderr',
3989 b'noreadstderr',
3986 False,
3990 False,
3987 _(b'do not read from stderr of the remote'),
3991 _(b'do not read from stderr of the remote'),
3988 ),
3992 ),
3989 (
3993 (
3990 b'',
3994 b'',
3991 b'nologhandshake',
3995 b'nologhandshake',
3992 False,
3996 False,
3993 _(b'do not log I/O related to the peer handshake'),
3997 _(b'do not log I/O related to the peer handshake'),
3994 ),
3998 ),
3995 ]
3999 ]
3996 + cmdutil.remoteopts,
4000 + cmdutil.remoteopts,
3997 _(b'[PATH]'),
4001 _(b'[PATH]'),
3998 optionalrepo=True,
4002 optionalrepo=True,
3999 )
4003 )
4000 def debugwireproto(ui, repo, path=None, **opts):
4004 def debugwireproto(ui, repo, path=None, **opts):
4001 """send wire protocol commands to a server
4005 """send wire protocol commands to a server
4002
4006
4003 This command can be used to issue wire protocol commands to remote
4007 This command can be used to issue wire protocol commands to remote
4004 peers and to debug the raw data being exchanged.
4008 peers and to debug the raw data being exchanged.
4005
4009
4006 ``--localssh`` will start an SSH server against the current repository
4010 ``--localssh`` will start an SSH server against the current repository
4007 and connect to that. By default, the connection will perform a handshake
4011 and connect to that. By default, the connection will perform a handshake
4008 and establish an appropriate peer instance.
4012 and establish an appropriate peer instance.
4009
4013
4010 ``--peer`` can be used to bypass the handshake protocol and construct a
4014 ``--peer`` can be used to bypass the handshake protocol and construct a
4011 peer instance using the specified class type. Valid values are ``raw``,
4015 peer instance using the specified class type. Valid values are ``raw``,
4012 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4016 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
4013 raw data payloads and don't support higher-level command actions.
4017 raw data payloads and don't support higher-level command actions.
4014
4018
4015 ``--noreadstderr`` can be used to disable automatic reading from stderr
4019 ``--noreadstderr`` can be used to disable automatic reading from stderr
4016 of the peer (for SSH connections only). Disabling automatic reading of
4020 of the peer (for SSH connections only). Disabling automatic reading of
4017 stderr is useful for making output more deterministic.
4021 stderr is useful for making output more deterministic.
4018
4022
4019 Commands are issued via a mini language which is specified via stdin.
4023 Commands are issued via a mini language which is specified via stdin.
4020 The language consists of individual actions to perform. An action is
4024 The language consists of individual actions to perform. An action is
4021 defined by a block. A block is defined as a line with no leading
4025 defined by a block. A block is defined as a line with no leading
4022 space followed by 0 or more lines with leading space. Blocks are
4026 space followed by 0 or more lines with leading space. Blocks are
4023 effectively a high-level command with additional metadata.
4027 effectively a high-level command with additional metadata.
4024
4028
4025 Lines beginning with ``#`` are ignored.
4029 Lines beginning with ``#`` are ignored.
4026
4030
4027 The following sections denote available actions.
4031 The following sections denote available actions.
4028
4032
4029 raw
4033 raw
4030 ---
4034 ---
4031
4035
4032 Send raw data to the server.
4036 Send raw data to the server.
4033
4037
4034 The block payload contains the raw data to send as one atomic send
4038 The block payload contains the raw data to send as one atomic send
4035 operation. The data may not actually be delivered in a single system
4039 operation. The data may not actually be delivered in a single system
4036 call: it depends on the abilities of the transport being used.
4040 call: it depends on the abilities of the transport being used.
4037
4041
4038 Each line in the block is de-indented and concatenated. Then, that
4042 Each line in the block is de-indented and concatenated. Then, that
4039 value is evaluated as a Python b'' literal. This allows the use of
4043 value is evaluated as a Python b'' literal. This allows the use of
4040 backslash escaping, etc.
4044 backslash escaping, etc.
4041
4045
4042 raw+
4046 raw+
4043 ----
4047 ----
4044
4048
4045 Behaves like ``raw`` except flushes output afterwards.
4049 Behaves like ``raw`` except flushes output afterwards.
4046
4050
4047 command <X>
4051 command <X>
4048 -----------
4052 -----------
4049
4053
4050 Send a request to run a named command, whose name follows the ``command``
4054 Send a request to run a named command, whose name follows the ``command``
4051 string.
4055 string.
4052
4056
4053 Arguments to the command are defined as lines in this block. The format of
4057 Arguments to the command are defined as lines in this block. The format of
4054 each line is ``<key> <value>``. e.g.::
4058 each line is ``<key> <value>``. e.g.::
4055
4059
4056 command listkeys
4060 command listkeys
4057 namespace bookmarks
4061 namespace bookmarks
4058
4062
4059 If the value begins with ``eval:``, it will be interpreted as a Python
4063 If the value begins with ``eval:``, it will be interpreted as a Python
4060 literal expression. Otherwise values are interpreted as Python b'' literals.
4064 literal expression. Otherwise values are interpreted as Python b'' literals.
4061 This allows sending complex types and encoding special byte sequences via
4065 This allows sending complex types and encoding special byte sequences via
4062 backslash escaping.
4066 backslash escaping.
4063
4067
4064 The following arguments have special meaning:
4068 The following arguments have special meaning:
4065
4069
4066 ``PUSHFILE``
4070 ``PUSHFILE``
4067 When defined, the *push* mechanism of the peer will be used instead
4071 When defined, the *push* mechanism of the peer will be used instead
4068 of the static request-response mechanism and the content of the
4072 of the static request-response mechanism and the content of the
4069 file specified in the value of this argument will be sent as the
4073 file specified in the value of this argument will be sent as the
4070 command payload.
4074 command payload.
4071
4075
4072 This can be used to submit a local bundle file to the remote.
4076 This can be used to submit a local bundle file to the remote.
4073
4077
4074 batchbegin
4078 batchbegin
4075 ----------
4079 ----------
4076
4080
4077 Instruct the peer to begin a batched send.
4081 Instruct the peer to begin a batched send.
4078
4082
4079 All ``command`` blocks are queued for execution until the next
4083 All ``command`` blocks are queued for execution until the next
4080 ``batchsubmit`` block.
4084 ``batchsubmit`` block.
4081
4085
4082 batchsubmit
4086 batchsubmit
4083 -----------
4087 -----------
4084
4088
4085 Submit previously queued ``command`` blocks as a batch request.
4089 Submit previously queued ``command`` blocks as a batch request.
4086
4090
4087 This action MUST be paired with a ``batchbegin`` action.
4091 This action MUST be paired with a ``batchbegin`` action.
4088
4092
4089 httprequest <method> <path>
4093 httprequest <method> <path>
4090 ---------------------------
4094 ---------------------------
4091
4095
4092 (HTTP peer only)
4096 (HTTP peer only)
4093
4097
4094 Send an HTTP request to the peer.
4098 Send an HTTP request to the peer.
4095
4099
4096 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4100 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
4097
4101
4098 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4102 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
4099 headers to add to the request. e.g. ``Accept: foo``.
4103 headers to add to the request. e.g. ``Accept: foo``.
4100
4104
4101 The following arguments are special:
4105 The following arguments are special:
4102
4106
4103 ``BODYFILE``
4107 ``BODYFILE``
4104 The content of the file defined as the value to this argument will be
4108 The content of the file defined as the value to this argument will be
4105 transferred verbatim as the HTTP request body.
4109 transferred verbatim as the HTTP request body.
4106
4110
4107 ``frame <type> <flags> <payload>``
4111 ``frame <type> <flags> <payload>``
4108 Send a unified protocol frame as part of the request body.
4112 Send a unified protocol frame as part of the request body.
4109
4113
4110 All frames will be collected and sent as the body to the HTTP
4114 All frames will be collected and sent as the body to the HTTP
4111 request.
4115 request.
4112
4116
4113 close
4117 close
4114 -----
4118 -----
4115
4119
4116 Close the connection to the server.
4120 Close the connection to the server.
4117
4121
4118 flush
4122 flush
4119 -----
4123 -----
4120
4124
4121 Flush data written to the server.
4125 Flush data written to the server.
4122
4126
4123 readavailable
4127 readavailable
4124 -------------
4128 -------------
4125
4129
4126 Close the write end of the connection and read all available data from
4130 Close the write end of the connection and read all available data from
4127 the server.
4131 the server.
4128
4132
4129 If the connection to the server encompasses multiple pipes, we poll both
4133 If the connection to the server encompasses multiple pipes, we poll both
4130 pipes and read available data.
4134 pipes and read available data.
4131
4135
4132 readline
4136 readline
4133 --------
4137 --------
4134
4138
4135 Read a line of output from the server. If there are multiple output
4139 Read a line of output from the server. If there are multiple output
4136 pipes, reads only the main pipe.
4140 pipes, reads only the main pipe.
4137
4141
4138 ereadline
4142 ereadline
4139 ---------
4143 ---------
4140
4144
4141 Like ``readline``, but read from the stderr pipe, if available.
4145 Like ``readline``, but read from the stderr pipe, if available.
4142
4146
4143 read <X>
4147 read <X>
4144 --------
4148 --------
4145
4149
4146 ``read()`` N bytes from the server's main output pipe.
4150 ``read()`` N bytes from the server's main output pipe.
4147
4151
4148 eread <X>
4152 eread <X>
4149 ---------
4153 ---------
4150
4154
4151 ``read()`` N bytes from the server's stderr pipe, if available.
4155 ``read()`` N bytes from the server's stderr pipe, if available.
4152
4156
4153 Specifying Unified Frame-Based Protocol Frames
4157 Specifying Unified Frame-Based Protocol Frames
4154 ----------------------------------------------
4158 ----------------------------------------------
4155
4159
4156 It is possible to emit a *Unified Frame-Based Protocol* by using special
4160 It is possible to emit a *Unified Frame-Based Protocol* by using special
4157 syntax.
4161 syntax.
4158
4162
4159 A frame is composed as a type, flags, and payload. These can be parsed
4163 A frame is composed as a type, flags, and payload. These can be parsed
4160 from a string of the form:
4164 from a string of the form:
4161
4165
4162 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4166 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
4163
4167
4164 ``request-id`` and ``stream-id`` are integers defining the request and
4168 ``request-id`` and ``stream-id`` are integers defining the request and
4165 stream identifiers.
4169 stream identifiers.
4166
4170
4167 ``type`` can be an integer value for the frame type or the string name
4171 ``type`` can be an integer value for the frame type or the string name
4168 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4172 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
4169 ``command-name``.
4173 ``command-name``.
4170
4174
4171 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4175 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
4172 components. Each component (and there can be just one) can be an integer
4176 components. Each component (and there can be just one) can be an integer
4173 or a flag name for stream flags or frame flags, respectively. Values are
4177 or a flag name for stream flags or frame flags, respectively. Values are
4174 resolved to integers and then bitwise OR'd together.
4178 resolved to integers and then bitwise OR'd together.
4175
4179
4176 ``payload`` represents the raw frame payload. If it begins with
4180 ``payload`` represents the raw frame payload. If it begins with
4177 ``cbor:``, the following string is evaluated as Python code and the
4181 ``cbor:``, the following string is evaluated as Python code and the
4178 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4182 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
4179 as a Python byte string literal.
4183 as a Python byte string literal.
4180 """
4184 """
4181 opts = pycompat.byteskwargs(opts)
4185 opts = pycompat.byteskwargs(opts)
4182
4186
4183 if opts[b'localssh'] and not repo:
4187 if opts[b'localssh'] and not repo:
4184 raise error.Abort(_(b'--localssh requires a repository'))
4188 raise error.Abort(_(b'--localssh requires a repository'))
4185
4189
4186 if opts[b'peer'] and opts[b'peer'] not in (
4190 if opts[b'peer'] and opts[b'peer'] not in (
4187 b'raw',
4191 b'raw',
4188 b'http2',
4192 b'http2',
4189 b'ssh1',
4193 b'ssh1',
4190 b'ssh2',
4194 b'ssh2',
4191 ):
4195 ):
4192 raise error.Abort(
4196 raise error.Abort(
4193 _(b'invalid value for --peer'),
4197 _(b'invalid value for --peer'),
4194 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4198 hint=_(b'valid values are "raw", "ssh1", and "ssh2"'),
4195 )
4199 )
4196
4200
4197 if path and opts[b'localssh']:
4201 if path and opts[b'localssh']:
4198 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4202 raise error.Abort(_(b'cannot specify --localssh with an explicit path'))
4199
4203
4200 if ui.interactive():
4204 if ui.interactive():
4201 ui.write(_(b'(waiting for commands on stdin)\n'))
4205 ui.write(_(b'(waiting for commands on stdin)\n'))
4202
4206
4203 blocks = list(_parsewirelangblocks(ui.fin))
4207 blocks = list(_parsewirelangblocks(ui.fin))
4204
4208
4205 proc = None
4209 proc = None
4206 stdin = None
4210 stdin = None
4207 stdout = None
4211 stdout = None
4208 stderr = None
4212 stderr = None
4209 opener = None
4213 opener = None
4210
4214
4211 if opts[b'localssh']:
4215 if opts[b'localssh']:
4212 # We start the SSH server in its own process so there is process
4216 # We start the SSH server in its own process so there is process
4213 # separation. This prevents a whole class of potential bugs around
4217 # separation. This prevents a whole class of potential bugs around
4214 # shared state from interfering with server operation.
4218 # shared state from interfering with server operation.
4215 args = procutil.hgcmd() + [
4219 args = procutil.hgcmd() + [
4216 b'-R',
4220 b'-R',
4217 repo.root,
4221 repo.root,
4218 b'debugserve',
4222 b'debugserve',
4219 b'--sshstdio',
4223 b'--sshstdio',
4220 ]
4224 ]
4221 proc = subprocess.Popen(
4225 proc = subprocess.Popen(
4222 pycompat.rapply(procutil.tonativestr, args),
4226 pycompat.rapply(procutil.tonativestr, args),
4223 stdin=subprocess.PIPE,
4227 stdin=subprocess.PIPE,
4224 stdout=subprocess.PIPE,
4228 stdout=subprocess.PIPE,
4225 stderr=subprocess.PIPE,
4229 stderr=subprocess.PIPE,
4226 bufsize=0,
4230 bufsize=0,
4227 )
4231 )
4228
4232
4229 stdin = proc.stdin
4233 stdin = proc.stdin
4230 stdout = proc.stdout
4234 stdout = proc.stdout
4231 stderr = proc.stderr
4235 stderr = proc.stderr
4232
4236
4233 # We turn the pipes into observers so we can log I/O.
4237 # We turn the pipes into observers so we can log I/O.
4234 if ui.verbose or opts[b'peer'] == b'raw':
4238 if ui.verbose or opts[b'peer'] == b'raw':
4235 stdin = util.makeloggingfileobject(
4239 stdin = util.makeloggingfileobject(
4236 ui, proc.stdin, b'i', logdata=True
4240 ui, proc.stdin, b'i', logdata=True
4237 )
4241 )
4238 stdout = util.makeloggingfileobject(
4242 stdout = util.makeloggingfileobject(
4239 ui, proc.stdout, b'o', logdata=True
4243 ui, proc.stdout, b'o', logdata=True
4240 )
4244 )
4241 stderr = util.makeloggingfileobject(
4245 stderr = util.makeloggingfileobject(
4242 ui, proc.stderr, b'e', logdata=True
4246 ui, proc.stderr, b'e', logdata=True
4243 )
4247 )
4244
4248
4245 # --localssh also implies the peer connection settings.
4249 # --localssh also implies the peer connection settings.
4246
4250
4247 url = b'ssh://localserver'
4251 url = b'ssh://localserver'
4248 autoreadstderr = not opts[b'noreadstderr']
4252 autoreadstderr = not opts[b'noreadstderr']
4249
4253
4250 if opts[b'peer'] == b'ssh1':
4254 if opts[b'peer'] == b'ssh1':
4251 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4255 ui.write(_(b'creating ssh peer for wire protocol version 1\n'))
4252 peer = sshpeer.sshv1peer(
4256 peer = sshpeer.sshv1peer(
4253 ui,
4257 ui,
4254 url,
4258 url,
4255 proc,
4259 proc,
4256 stdin,
4260 stdin,
4257 stdout,
4261 stdout,
4258 stderr,
4262 stderr,
4259 None,
4263 None,
4260 autoreadstderr=autoreadstderr,
4264 autoreadstderr=autoreadstderr,
4261 )
4265 )
4262 elif opts[b'peer'] == b'ssh2':
4266 elif opts[b'peer'] == b'ssh2':
4263 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4267 ui.write(_(b'creating ssh peer for wire protocol version 2\n'))
4264 peer = sshpeer.sshv2peer(
4268 peer = sshpeer.sshv2peer(
4265 ui,
4269 ui,
4266 url,
4270 url,
4267 proc,
4271 proc,
4268 stdin,
4272 stdin,
4269 stdout,
4273 stdout,
4270 stderr,
4274 stderr,
4271 None,
4275 None,
4272 autoreadstderr=autoreadstderr,
4276 autoreadstderr=autoreadstderr,
4273 )
4277 )
4274 elif opts[b'peer'] == b'raw':
4278 elif opts[b'peer'] == b'raw':
4275 ui.write(_(b'using raw connection to peer\n'))
4279 ui.write(_(b'using raw connection to peer\n'))
4276 peer = None
4280 peer = None
4277 else:
4281 else:
4278 ui.write(_(b'creating ssh peer from handshake results\n'))
4282 ui.write(_(b'creating ssh peer from handshake results\n'))
4279 peer = sshpeer.makepeer(
4283 peer = sshpeer.makepeer(
4280 ui,
4284 ui,
4281 url,
4285 url,
4282 proc,
4286 proc,
4283 stdin,
4287 stdin,
4284 stdout,
4288 stdout,
4285 stderr,
4289 stderr,
4286 autoreadstderr=autoreadstderr,
4290 autoreadstderr=autoreadstderr,
4287 )
4291 )
4288
4292
4289 elif path:
4293 elif path:
4290 # We bypass hg.peer() so we can proxy the sockets.
4294 # We bypass hg.peer() so we can proxy the sockets.
4291 # TODO consider not doing this because we skip
4295 # TODO consider not doing this because we skip
4292 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4296 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
4293 u = util.url(path)
4297 u = util.url(path)
4294 if u.scheme != b'http':
4298 if u.scheme != b'http':
4295 raise error.Abort(_(b'only http:// paths are currently supported'))
4299 raise error.Abort(_(b'only http:// paths are currently supported'))
4296
4300
4297 url, authinfo = u.authinfo()
4301 url, authinfo = u.authinfo()
4298 openerargs = {
4302 openerargs = {
4299 'useragent': b'Mercurial debugwireproto',
4303 'useragent': b'Mercurial debugwireproto',
4300 }
4304 }
4301
4305
4302 # Turn pipes/sockets into observers so we can log I/O.
4306 # Turn pipes/sockets into observers so we can log I/O.
4303 if ui.verbose:
4307 if ui.verbose:
4304 openerargs.update(
4308 openerargs.update(
4305 {
4309 {
4306 'loggingfh': ui,
4310 'loggingfh': ui,
4307 'loggingname': b's',
4311 'loggingname': b's',
4308 'loggingopts': {'logdata': True, 'logdataapis': False,},
4312 'loggingopts': {'logdata': True, 'logdataapis': False,},
4309 }
4313 }
4310 )
4314 )
4311
4315
4312 if ui.debugflag:
4316 if ui.debugflag:
4313 openerargs['loggingopts']['logdataapis'] = True
4317 openerargs['loggingopts']['logdataapis'] = True
4314
4318
4315 # Don't send default headers when in raw mode. This allows us to
4319 # Don't send default headers when in raw mode. This allows us to
4316 # bypass most of the behavior of our URL handling code so we can
4320 # bypass most of the behavior of our URL handling code so we can
4317 # have near complete control over what's sent on the wire.
4321 # have near complete control over what's sent on the wire.
4318 if opts[b'peer'] == b'raw':
4322 if opts[b'peer'] == b'raw':
4319 openerargs['sendaccept'] = False
4323 openerargs['sendaccept'] = False
4320
4324
4321 opener = urlmod.opener(ui, authinfo, **openerargs)
4325 opener = urlmod.opener(ui, authinfo, **openerargs)
4322
4326
4323 if opts[b'peer'] == b'http2':
4327 if opts[b'peer'] == b'http2':
4324 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4328 ui.write(_(b'creating http peer for wire protocol version 2\n'))
4325 # We go through makepeer() because we need an API descriptor for
4329 # We go through makepeer() because we need an API descriptor for
4326 # the peer instance to be useful.
4330 # the peer instance to be useful.
4327 with ui.configoverride(
4331 with ui.configoverride(
4328 {(b'experimental', b'httppeer.advertise-v2'): True}
4332 {(b'experimental', b'httppeer.advertise-v2'): True}
4329 ):
4333 ):
4330 if opts[b'nologhandshake']:
4334 if opts[b'nologhandshake']:
4331 ui.pushbuffer()
4335 ui.pushbuffer()
4332
4336
4333 peer = httppeer.makepeer(ui, path, opener=opener)
4337 peer = httppeer.makepeer(ui, path, opener=opener)
4334
4338
4335 if opts[b'nologhandshake']:
4339 if opts[b'nologhandshake']:
4336 ui.popbuffer()
4340 ui.popbuffer()
4337
4341
4338 if not isinstance(peer, httppeer.httpv2peer):
4342 if not isinstance(peer, httppeer.httpv2peer):
4339 raise error.Abort(
4343 raise error.Abort(
4340 _(
4344 _(
4341 b'could not instantiate HTTP peer for '
4345 b'could not instantiate HTTP peer for '
4342 b'wire protocol version 2'
4346 b'wire protocol version 2'
4343 ),
4347 ),
4344 hint=_(
4348 hint=_(
4345 b'the server may not have the feature '
4349 b'the server may not have the feature '
4346 b'enabled or is not allowing this '
4350 b'enabled or is not allowing this '
4347 b'client version'
4351 b'client version'
4348 ),
4352 ),
4349 )
4353 )
4350
4354
4351 elif opts[b'peer'] == b'raw':
4355 elif opts[b'peer'] == b'raw':
4352 ui.write(_(b'using raw connection to peer\n'))
4356 ui.write(_(b'using raw connection to peer\n'))
4353 peer = None
4357 peer = None
4354 elif opts[b'peer']:
4358 elif opts[b'peer']:
4355 raise error.Abort(
4359 raise error.Abort(
4356 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4360 _(b'--peer %s not supported with HTTP peers') % opts[b'peer']
4357 )
4361 )
4358 else:
4362 else:
4359 peer = httppeer.makepeer(ui, path, opener=opener)
4363 peer = httppeer.makepeer(ui, path, opener=opener)
4360
4364
4361 # We /could/ populate stdin/stdout with sock.makefile()...
4365 # We /could/ populate stdin/stdout with sock.makefile()...
4362 else:
4366 else:
4363 raise error.Abort(_(b'unsupported connection configuration'))
4367 raise error.Abort(_(b'unsupported connection configuration'))
4364
4368
4365 batchedcommands = None
4369 batchedcommands = None
4366
4370
4367 # Now perform actions based on the parsed wire language instructions.
4371 # Now perform actions based on the parsed wire language instructions.
4368 for action, lines in blocks:
4372 for action, lines in blocks:
4369 if action in (b'raw', b'raw+'):
4373 if action in (b'raw', b'raw+'):
4370 if not stdin:
4374 if not stdin:
4371 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4375 raise error.Abort(_(b'cannot call raw/raw+ on this peer'))
4372
4376
4373 # Concatenate the data together.
4377 # Concatenate the data together.
4374 data = b''.join(l.lstrip() for l in lines)
4378 data = b''.join(l.lstrip() for l in lines)
4375 data = stringutil.unescapestr(data)
4379 data = stringutil.unescapestr(data)
4376 stdin.write(data)
4380 stdin.write(data)
4377
4381
4378 if action == b'raw+':
4382 if action == b'raw+':
4379 stdin.flush()
4383 stdin.flush()
4380 elif action == b'flush':
4384 elif action == b'flush':
4381 if not stdin:
4385 if not stdin:
4382 raise error.Abort(_(b'cannot call flush on this peer'))
4386 raise error.Abort(_(b'cannot call flush on this peer'))
4383 stdin.flush()
4387 stdin.flush()
4384 elif action.startswith(b'command'):
4388 elif action.startswith(b'command'):
4385 if not peer:
4389 if not peer:
4386 raise error.Abort(
4390 raise error.Abort(
4387 _(
4391 _(
4388 b'cannot send commands unless peer instance '
4392 b'cannot send commands unless peer instance '
4389 b'is available'
4393 b'is available'
4390 )
4394 )
4391 )
4395 )
4392
4396
4393 command = action.split(b' ', 1)[1]
4397 command = action.split(b' ', 1)[1]
4394
4398
4395 args = {}
4399 args = {}
4396 for line in lines:
4400 for line in lines:
4397 # We need to allow empty values.
4401 # We need to allow empty values.
4398 fields = line.lstrip().split(b' ', 1)
4402 fields = line.lstrip().split(b' ', 1)
4399 if len(fields) == 1:
4403 if len(fields) == 1:
4400 key = fields[0]
4404 key = fields[0]
4401 value = b''
4405 value = b''
4402 else:
4406 else:
4403 key, value = fields
4407 key, value = fields
4404
4408
4405 if value.startswith(b'eval:'):
4409 if value.startswith(b'eval:'):
4406 value = stringutil.evalpythonliteral(value[5:])
4410 value = stringutil.evalpythonliteral(value[5:])
4407 else:
4411 else:
4408 value = stringutil.unescapestr(value)
4412 value = stringutil.unescapestr(value)
4409
4413
4410 args[key] = value
4414 args[key] = value
4411
4415
4412 if batchedcommands is not None:
4416 if batchedcommands is not None:
4413 batchedcommands.append((command, args))
4417 batchedcommands.append((command, args))
4414 continue
4418 continue
4415
4419
4416 ui.status(_(b'sending %s command\n') % command)
4420 ui.status(_(b'sending %s command\n') % command)
4417
4421
4418 if b'PUSHFILE' in args:
4422 if b'PUSHFILE' in args:
4419 with open(args[b'PUSHFILE'], 'rb') as fh:
4423 with open(args[b'PUSHFILE'], 'rb') as fh:
4420 del args[b'PUSHFILE']
4424 del args[b'PUSHFILE']
4421 res, output = peer._callpush(
4425 res, output = peer._callpush(
4422 command, fh, **pycompat.strkwargs(args)
4426 command, fh, **pycompat.strkwargs(args)
4423 )
4427 )
4424 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4428 ui.status(_(b'result: %s\n') % stringutil.escapestr(res))
4425 ui.status(
4429 ui.status(
4426 _(b'remote output: %s\n') % stringutil.escapestr(output)
4430 _(b'remote output: %s\n') % stringutil.escapestr(output)
4427 )
4431 )
4428 else:
4432 else:
4429 with peer.commandexecutor() as e:
4433 with peer.commandexecutor() as e:
4430 res = e.callcommand(command, args).result()
4434 res = e.callcommand(command, args).result()
4431
4435
4432 if isinstance(res, wireprotov2peer.commandresponse):
4436 if isinstance(res, wireprotov2peer.commandresponse):
4433 val = res.objects()
4437 val = res.objects()
4434 ui.status(
4438 ui.status(
4435 _(b'response: %s\n')
4439 _(b'response: %s\n')
4436 % stringutil.pprint(val, bprefix=True, indent=2)
4440 % stringutil.pprint(val, bprefix=True, indent=2)
4437 )
4441 )
4438 else:
4442 else:
4439 ui.status(
4443 ui.status(
4440 _(b'response: %s\n')
4444 _(b'response: %s\n')
4441 % stringutil.pprint(res, bprefix=True, indent=2)
4445 % stringutil.pprint(res, bprefix=True, indent=2)
4442 )
4446 )
4443
4447
4444 elif action == b'batchbegin':
4448 elif action == b'batchbegin':
4445 if batchedcommands is not None:
4449 if batchedcommands is not None:
4446 raise error.Abort(_(b'nested batchbegin not allowed'))
4450 raise error.Abort(_(b'nested batchbegin not allowed'))
4447
4451
4448 batchedcommands = []
4452 batchedcommands = []
4449 elif action == b'batchsubmit':
4453 elif action == b'batchsubmit':
4450 # There is a batching API we could go through. But it would be
4454 # There is a batching API we could go through. But it would be
4451 # difficult to normalize requests into function calls. It is easier
4455 # difficult to normalize requests into function calls. It is easier
4452 # to bypass this layer and normalize to commands + args.
4456 # to bypass this layer and normalize to commands + args.
4453 ui.status(
4457 ui.status(
4454 _(b'sending batch with %d sub-commands\n')
4458 _(b'sending batch with %d sub-commands\n')
4455 % len(batchedcommands)
4459 % len(batchedcommands)
4456 )
4460 )
4457 assert peer is not None
4461 assert peer is not None
4458 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4462 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
4459 ui.status(
4463 ui.status(
4460 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4464 _(b'response #%d: %s\n') % (i, stringutil.escapestr(chunk))
4461 )
4465 )
4462
4466
4463 batchedcommands = None
4467 batchedcommands = None
4464
4468
4465 elif action.startswith(b'httprequest '):
4469 elif action.startswith(b'httprequest '):
4466 if not opener:
4470 if not opener:
4467 raise error.Abort(
4471 raise error.Abort(
4468 _(b'cannot use httprequest without an HTTP peer')
4472 _(b'cannot use httprequest without an HTTP peer')
4469 )
4473 )
4470
4474
4471 request = action.split(b' ', 2)
4475 request = action.split(b' ', 2)
4472 if len(request) != 3:
4476 if len(request) != 3:
4473 raise error.Abort(
4477 raise error.Abort(
4474 _(
4478 _(
4475 b'invalid httprequest: expected format is '
4479 b'invalid httprequest: expected format is '
4476 b'"httprequest <method> <path>'
4480 b'"httprequest <method> <path>'
4477 )
4481 )
4478 )
4482 )
4479
4483
4480 method, httppath = request[1:]
4484 method, httppath = request[1:]
4481 headers = {}
4485 headers = {}
4482 body = None
4486 body = None
4483 frames = []
4487 frames = []
4484 for line in lines:
4488 for line in lines:
4485 line = line.lstrip()
4489 line = line.lstrip()
4486 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4490 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
4487 if m:
4491 if m:
4488 # Headers need to use native strings.
4492 # Headers need to use native strings.
4489 key = pycompat.strurl(m.group(1))
4493 key = pycompat.strurl(m.group(1))
4490 value = pycompat.strurl(m.group(2))
4494 value = pycompat.strurl(m.group(2))
4491 headers[key] = value
4495 headers[key] = value
4492 continue
4496 continue
4493
4497
4494 if line.startswith(b'BODYFILE '):
4498 if line.startswith(b'BODYFILE '):
4495 with open(line.split(b' ', 1), b'rb') as fh:
4499 with open(line.split(b' ', 1), b'rb') as fh:
4496 body = fh.read()
4500 body = fh.read()
4497 elif line.startswith(b'frame '):
4501 elif line.startswith(b'frame '):
4498 frame = wireprotoframing.makeframefromhumanstring(
4502 frame = wireprotoframing.makeframefromhumanstring(
4499 line[len(b'frame ') :]
4503 line[len(b'frame ') :]
4500 )
4504 )
4501
4505
4502 frames.append(frame)
4506 frames.append(frame)
4503 else:
4507 else:
4504 raise error.Abort(
4508 raise error.Abort(
4505 _(b'unknown argument to httprequest: %s') % line
4509 _(b'unknown argument to httprequest: %s') % line
4506 )
4510 )
4507
4511
4508 url = path + httppath
4512 url = path + httppath
4509
4513
4510 if frames:
4514 if frames:
4511 body = b''.join(bytes(f) for f in frames)
4515 body = b''.join(bytes(f) for f in frames)
4512
4516
4513 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4517 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
4514
4518
4515 # urllib.Request insists on using has_data() as a proxy for
4519 # urllib.Request insists on using has_data() as a proxy for
4516 # determining the request method. Override that to use our
4520 # determining the request method. Override that to use our
4517 # explicitly requested method.
4521 # explicitly requested method.
4518 req.get_method = lambda: pycompat.sysstr(method)
4522 req.get_method = lambda: pycompat.sysstr(method)
4519
4523
4520 try:
4524 try:
4521 res = opener.open(req)
4525 res = opener.open(req)
4522 body = res.read()
4526 body = res.read()
4523 except util.urlerr.urlerror as e:
4527 except util.urlerr.urlerror as e:
4524 # read() method must be called, but only exists in Python 2
4528 # read() method must be called, but only exists in Python 2
4525 getattr(e, 'read', lambda: None)()
4529 getattr(e, 'read', lambda: None)()
4526 continue
4530 continue
4527
4531
4528 ct = res.headers.get('Content-Type')
4532 ct = res.headers.get('Content-Type')
4529 if ct == 'application/mercurial-cbor':
4533 if ct == 'application/mercurial-cbor':
4530 ui.write(
4534 ui.write(
4531 _(b'cbor> %s\n')
4535 _(b'cbor> %s\n')
4532 % stringutil.pprint(
4536 % stringutil.pprint(
4533 cborutil.decodeall(body), bprefix=True, indent=2
4537 cborutil.decodeall(body), bprefix=True, indent=2
4534 )
4538 )
4535 )
4539 )
4536
4540
4537 elif action == b'close':
4541 elif action == b'close':
4538 assert peer is not None
4542 assert peer is not None
4539 peer.close()
4543 peer.close()
4540 elif action == b'readavailable':
4544 elif action == b'readavailable':
4541 if not stdout or not stderr:
4545 if not stdout or not stderr:
4542 raise error.Abort(
4546 raise error.Abort(
4543 _(b'readavailable not available on this peer')
4547 _(b'readavailable not available on this peer')
4544 )
4548 )
4545
4549
4546 stdin.close()
4550 stdin.close()
4547 stdout.read()
4551 stdout.read()
4548 stderr.read()
4552 stderr.read()
4549
4553
4550 elif action == b'readline':
4554 elif action == b'readline':
4551 if not stdout:
4555 if not stdout:
4552 raise error.Abort(_(b'readline not available on this peer'))
4556 raise error.Abort(_(b'readline not available on this peer'))
4553 stdout.readline()
4557 stdout.readline()
4554 elif action == b'ereadline':
4558 elif action == b'ereadline':
4555 if not stderr:
4559 if not stderr:
4556 raise error.Abort(_(b'ereadline not available on this peer'))
4560 raise error.Abort(_(b'ereadline not available on this peer'))
4557 stderr.readline()
4561 stderr.readline()
4558 elif action.startswith(b'read '):
4562 elif action.startswith(b'read '):
4559 count = int(action.split(b' ', 1)[1])
4563 count = int(action.split(b' ', 1)[1])
4560 if not stdout:
4564 if not stdout:
4561 raise error.Abort(_(b'read not available on this peer'))
4565 raise error.Abort(_(b'read not available on this peer'))
4562 stdout.read(count)
4566 stdout.read(count)
4563 elif action.startswith(b'eread '):
4567 elif action.startswith(b'eread '):
4564 count = int(action.split(b' ', 1)[1])
4568 count = int(action.split(b' ', 1)[1])
4565 if not stderr:
4569 if not stderr:
4566 raise error.Abort(_(b'eread not available on this peer'))
4570 raise error.Abort(_(b'eread not available on this peer'))
4567 stderr.read(count)
4571 stderr.read(count)
4568 else:
4572 else:
4569 raise error.Abort(_(b'unknown action: %s') % action)
4573 raise error.Abort(_(b'unknown action: %s') % action)
4570
4574
4571 if batchedcommands is not None:
4575 if batchedcommands is not None:
4572 raise error.Abort(_(b'unclosed "batchbegin" request'))
4576 raise error.Abort(_(b'unclosed "batchbegin" request'))
4573
4577
4574 if peer:
4578 if peer:
4575 peer.close()
4579 peer.close()
4576
4580
4577 if proc:
4581 if proc:
4578 proc.kill()
4582 proc.kill()
@@ -1,517 +1,519 b''
1 # mail.py - mail sending bits for mercurial
1 # mail.py - mail sending bits for mercurial
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 email
10 import email
11 import email.charset
11 import email.charset
12 import email.generator
12 import email.generator
13 import email.header
13 import email.header
14 import email.message
14 import email.message
15 import email.parser
15 import email.parser
16 import io
16 import io
17 import os
17 import os
18 import smtplib
18 import smtplib
19 import socket
19 import socket
20 import time
20 import time
21
21
22 from .i18n import _
22 from .i18n import _
23 from .pycompat import (
23 from .pycompat import (
24 getattr,
24 getattr,
25 open,
25 open,
26 )
26 )
27 from . import (
27 from . import (
28 encoding,
28 encoding,
29 error,
29 error,
30 pycompat,
30 pycompat,
31 sslutil,
31 sslutil,
32 util,
32 util,
33 )
33 )
34 from .utils import (
34 from .utils import (
35 procutil,
35 procutil,
36 stringutil,
36 stringutil,
37 )
37 )
38
38
39 if pycompat.TYPE_CHECKING:
39 if pycompat.TYPE_CHECKING:
40 from typing import Any, List, Tuple, Union
40 from typing import Any, List, Tuple, Union
41
41
42 # keep pyflakes happy
42 # keep pyflakes happy
43 assert all((Any, List, Tuple, Union))
43 assert all((Any, List, Tuple, Union))
44
44
45
45
46 class STARTTLS(smtplib.SMTP):
46 class STARTTLS(smtplib.SMTP):
47 '''Derived class to verify the peer certificate for STARTTLS.
47 '''Derived class to verify the peer certificate for STARTTLS.
48
48
49 This class allows to pass any keyword arguments to SSL socket creation.
49 This class allows to pass any keyword arguments to SSL socket creation.
50 '''
50 '''
51
51
52 def __init__(self, ui, host=None, **kwargs):
52 def __init__(self, ui, host=None, **kwargs):
53 smtplib.SMTP.__init__(self, **kwargs)
53 smtplib.SMTP.__init__(self, **kwargs)
54 self._ui = ui
54 self._ui = ui
55 self._host = host
55 self._host = host
56
56
57 def starttls(self, keyfile=None, certfile=None):
57 def starttls(self, keyfile=None, certfile=None):
58 if not self.has_extn("starttls"):
58 if not self.has_extn("starttls"):
59 msg = b"STARTTLS extension not supported by server"
59 msg = b"STARTTLS extension not supported by server"
60 raise smtplib.SMTPException(msg)
60 raise smtplib.SMTPException(msg)
61 (resp, reply) = self.docmd("STARTTLS")
61 (resp, reply) = self.docmd("STARTTLS")
62 if resp == 220:
62 if resp == 220:
63 self.sock = sslutil.wrapsocket(
63 self.sock = sslutil.wrapsocket(
64 self.sock,
64 self.sock,
65 keyfile,
65 keyfile,
66 certfile,
66 certfile,
67 ui=self._ui,
67 ui=self._ui,
68 serverhostname=self._host,
68 serverhostname=self._host,
69 )
69 )
70 self.file = self.sock.makefile("rb")
70 self.file = self.sock.makefile("rb")
71 self.helo_resp = None
71 self.helo_resp = None
72 self.ehlo_resp = None
72 self.ehlo_resp = None
73 self.esmtp_features = {}
73 self.esmtp_features = {}
74 self.does_esmtp = 0
74 self.does_esmtp = 0
75 return (resp, reply)
75 return (resp, reply)
76
76
77
77
78 class SMTPS(smtplib.SMTP):
78 class SMTPS(smtplib.SMTP):
79 '''Derived class to verify the peer certificate for SMTPS.
79 '''Derived class to verify the peer certificate for SMTPS.
80
80
81 This class allows to pass any keyword arguments to SSL socket creation.
81 This class allows to pass any keyword arguments to SSL socket creation.
82 '''
82 '''
83
83
84 def __init__(self, ui, keyfile=None, certfile=None, host=None, **kwargs):
84 def __init__(self, ui, keyfile=None, certfile=None, host=None, **kwargs):
85 self.keyfile = keyfile
85 self.keyfile = keyfile
86 self.certfile = certfile
86 self.certfile = certfile
87 smtplib.SMTP.__init__(self, **kwargs)
87 smtplib.SMTP.__init__(self, **kwargs)
88 self._host = host
88 self._host = host
89 self.default_port = smtplib.SMTP_SSL_PORT
89 self.default_port = smtplib.SMTP_SSL_PORT
90 self._ui = ui
90 self._ui = ui
91
91
92 def _get_socket(self, host, port, timeout):
92 def _get_socket(self, host, port, timeout):
93 if self.debuglevel > 0:
93 if self.debuglevel > 0:
94 self._ui.debug(b'connect: %r\n' % ((host, port),))
94 self._ui.debug(b'connect: %r\n' % ((host, port),))
95 new_socket = socket.create_connection((host, port), timeout)
95 new_socket = socket.create_connection((host, port), timeout)
96 new_socket = sslutil.wrapsocket(
96 new_socket = sslutil.wrapsocket(
97 new_socket,
97 new_socket,
98 self.keyfile,
98 self.keyfile,
99 self.certfile,
99 self.certfile,
100 ui=self._ui,
100 ui=self._ui,
101 serverhostname=self._host,
101 serverhostname=self._host,
102 )
102 )
103 self.file = new_socket.makefile('rb')
103 self.file = new_socket.makefile('rb')
104 return new_socket
104 return new_socket
105
105
106
106
107 def _pyhastls():
107 def _pyhastls():
108 # type: () -> bool
108 # type: () -> bool
109 """Returns true iff Python has TLS support, false otherwise."""
109 """Returns true iff Python has TLS support, false otherwise."""
110 try:
110 try:
111 import ssl
111 import ssl
112
112
113 getattr(ssl, 'HAS_TLS', False)
113 getattr(ssl, 'HAS_TLS', False)
114 return True
114 return True
115 except ImportError:
115 except ImportError:
116 return False
116 return False
117
117
118
118
119 def _smtp(ui):
119 def _smtp(ui):
120 '''build an smtp connection and return a function to send mail'''
120 '''build an smtp connection and return a function to send mail'''
121 local_hostname = ui.config(b'smtp', b'local_hostname')
121 local_hostname = ui.config(b'smtp', b'local_hostname')
122 tls = ui.config(b'smtp', b'tls')
122 tls = ui.config(b'smtp', b'tls')
123 # backward compatible: when tls = true, we use starttls.
123 # backward compatible: when tls = true, we use starttls.
124 starttls = tls == b'starttls' or stringutil.parsebool(tls)
124 starttls = tls == b'starttls' or stringutil.parsebool(tls)
125 smtps = tls == b'smtps'
125 smtps = tls == b'smtps'
126 if (starttls or smtps) and not _pyhastls():
126 if (starttls or smtps) and not _pyhastls():
127 raise error.Abort(_(b"can't use TLS: Python SSL support not installed"))
127 raise error.Abort(_(b"can't use TLS: Python SSL support not installed"))
128 mailhost = ui.config(b'smtp', b'host')
128 mailhost = ui.config(b'smtp', b'host')
129 if not mailhost:
129 if not mailhost:
130 raise error.Abort(_(b'smtp.host not configured - cannot send mail'))
130 raise error.Abort(_(b'smtp.host not configured - cannot send mail'))
131 if smtps:
131 if smtps:
132 ui.note(_(b'(using smtps)\n'))
132 ui.note(_(b'(using smtps)\n'))
133 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
133 s = SMTPS(ui, local_hostname=local_hostname, host=mailhost)
134 elif starttls:
134 elif starttls:
135 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
135 s = STARTTLS(ui, local_hostname=local_hostname, host=mailhost)
136 else:
136 else:
137 s = smtplib.SMTP(local_hostname=local_hostname)
137 s = smtplib.SMTP(local_hostname=local_hostname)
138 if smtps:
138 if smtps:
139 defaultport = 465
139 defaultport = 465
140 else:
140 else:
141 defaultport = 25
141 defaultport = 25
142 mailport = util.getport(ui.config(b'smtp', b'port', defaultport))
142 mailport = util.getport(ui.config(b'smtp', b'port', defaultport))
143 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
143 ui.note(_(b'sending mail: smtp host %s, port %d\n') % (mailhost, mailport))
144 s.connect(host=mailhost, port=mailport)
144 s.connect(host=mailhost, port=mailport)
145 if starttls:
145 if starttls:
146 ui.note(_(b'(using starttls)\n'))
146 ui.note(_(b'(using starttls)\n'))
147 s.ehlo()
147 s.ehlo()
148 s.starttls()
148 s.starttls()
149 s.ehlo()
149 s.ehlo()
150 if starttls or smtps:
150 if starttls or smtps:
151 ui.note(_(b'(verifying remote certificate)\n'))
151 ui.note(_(b'(verifying remote certificate)\n'))
152 sslutil.validatesocket(s.sock)
152 sslutil.validatesocket(s.sock)
153 username = ui.config(b'smtp', b'username')
153 username = ui.config(b'smtp', b'username')
154 password = ui.config(b'smtp', b'password')
154 password = ui.config(b'smtp', b'password')
155 if username:
155 if username:
156 if password:
156 if password:
157 password = encoding.strfromlocal(password)
157 password = encoding.strfromlocal(password)
158 else:
158 else:
159 password = ui.getpass()
159 password = ui.getpass()
160 if password is not None:
161 password = encoding.strfromlocal(password)
160 if username and password:
162 if username and password:
161 ui.note(_(b'(authenticating to mail server as %s)\n') % username)
163 ui.note(_(b'(authenticating to mail server as %s)\n') % username)
162 username = encoding.strfromlocal(username)
164 username = encoding.strfromlocal(username)
163 try:
165 try:
164 s.login(username, password)
166 s.login(username, password)
165 except smtplib.SMTPException as inst:
167 except smtplib.SMTPException as inst:
166 raise error.Abort(inst)
168 raise error.Abort(inst)
167
169
168 def send(sender, recipients, msg):
170 def send(sender, recipients, msg):
169 try:
171 try:
170 return s.sendmail(sender, recipients, msg)
172 return s.sendmail(sender, recipients, msg)
171 except smtplib.SMTPRecipientsRefused as inst:
173 except smtplib.SMTPRecipientsRefused as inst:
172 recipients = [r[1] for r in inst.recipients.values()]
174 recipients = [r[1] for r in inst.recipients.values()]
173 raise error.Abort(b'\n' + b'\n'.join(recipients))
175 raise error.Abort(b'\n' + b'\n'.join(recipients))
174 except smtplib.SMTPException as inst:
176 except smtplib.SMTPException as inst:
175 raise error.Abort(inst)
177 raise error.Abort(inst)
176
178
177 return send
179 return send
178
180
179
181
180 def _sendmail(ui, sender, recipients, msg):
182 def _sendmail(ui, sender, recipients, msg):
181 '''send mail using sendmail.'''
183 '''send mail using sendmail.'''
182 program = ui.config(b'email', b'method')
184 program = ui.config(b'email', b'method')
183
185
184 def stremail(x):
186 def stremail(x):
185 return procutil.shellquote(stringutil.email(encoding.strtolocal(x)))
187 return procutil.shellquote(stringutil.email(encoding.strtolocal(x)))
186
188
187 cmdline = b'%s -f %s %s' % (
189 cmdline = b'%s -f %s %s' % (
188 program,
190 program,
189 stremail(sender),
191 stremail(sender),
190 b' '.join(map(stremail, recipients)),
192 b' '.join(map(stremail, recipients)),
191 )
193 )
192 ui.note(_(b'sending mail: %s\n') % cmdline)
194 ui.note(_(b'sending mail: %s\n') % cmdline)
193 fp = procutil.popen(cmdline, b'wb')
195 fp = procutil.popen(cmdline, b'wb')
194 fp.write(util.tonativeeol(msg))
196 fp.write(util.tonativeeol(msg))
195 ret = fp.close()
197 ret = fp.close()
196 if ret:
198 if ret:
197 raise error.Abort(
199 raise error.Abort(
198 b'%s %s'
200 b'%s %s'
199 % (
201 % (
200 os.path.basename(procutil.shellsplit(program)[0]),
202 os.path.basename(procutil.shellsplit(program)[0]),
201 procutil.explainexit(ret),
203 procutil.explainexit(ret),
202 )
204 )
203 )
205 )
204
206
205
207
206 def _mbox(mbox, sender, recipients, msg):
208 def _mbox(mbox, sender, recipients, msg):
207 '''write mails to mbox'''
209 '''write mails to mbox'''
208 fp = open(mbox, b'ab+')
210 fp = open(mbox, b'ab+')
209 # Should be time.asctime(), but Windows prints 2-characters day
211 # Should be time.asctime(), but Windows prints 2-characters day
210 # of month instead of one. Make them print the same thing.
212 # of month instead of one. Make them print the same thing.
211 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
213 date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
212 fp.write(
214 fp.write(
213 b'From %s %s\n'
215 b'From %s %s\n'
214 % (encoding.strtolocal(sender), encoding.strtolocal(date))
216 % (encoding.strtolocal(sender), encoding.strtolocal(date))
215 )
217 )
216 fp.write(msg)
218 fp.write(msg)
217 fp.write(b'\n\n')
219 fp.write(b'\n\n')
218 fp.close()
220 fp.close()
219
221
220
222
221 def connect(ui, mbox=None):
223 def connect(ui, mbox=None):
222 '''make a mail connection. return a function to send mail.
224 '''make a mail connection. return a function to send mail.
223 call as sendmail(sender, list-of-recipients, msg).'''
225 call as sendmail(sender, list-of-recipients, msg).'''
224 if mbox:
226 if mbox:
225 open(mbox, b'wb').close()
227 open(mbox, b'wb').close()
226 return lambda s, r, m: _mbox(mbox, s, r, m)
228 return lambda s, r, m: _mbox(mbox, s, r, m)
227 if ui.config(b'email', b'method') == b'smtp':
229 if ui.config(b'email', b'method') == b'smtp':
228 return _smtp(ui)
230 return _smtp(ui)
229 return lambda s, r, m: _sendmail(ui, s, r, m)
231 return lambda s, r, m: _sendmail(ui, s, r, m)
230
232
231
233
232 def sendmail(ui, sender, recipients, msg, mbox=None):
234 def sendmail(ui, sender, recipients, msg, mbox=None):
233 send = connect(ui, mbox=mbox)
235 send = connect(ui, mbox=mbox)
234 return send(sender, recipients, msg)
236 return send(sender, recipients, msg)
235
237
236
238
237 def validateconfig(ui):
239 def validateconfig(ui):
238 '''determine if we have enough config data to try sending email.'''
240 '''determine if we have enough config data to try sending email.'''
239 method = ui.config(b'email', b'method')
241 method = ui.config(b'email', b'method')
240 if method == b'smtp':
242 if method == b'smtp':
241 if not ui.config(b'smtp', b'host'):
243 if not ui.config(b'smtp', b'host'):
242 raise error.Abort(
244 raise error.Abort(
243 _(
245 _(
244 b'smtp specified as email transport, '
246 b'smtp specified as email transport, '
245 b'but no smtp host configured'
247 b'but no smtp host configured'
246 )
248 )
247 )
249 )
248 else:
250 else:
249 if not procutil.findexe(method):
251 if not procutil.findexe(method):
250 raise error.Abort(
252 raise error.Abort(
251 _(b'%r specified as email transport, but not in PATH') % method
253 _(b'%r specified as email transport, but not in PATH') % method
252 )
254 )
253
255
254
256
255 def codec2iana(cs):
257 def codec2iana(cs):
256 # type: (str) -> str
258 # type: (str) -> str
257 ''''''
259 ''''''
258 cs = email.charset.Charset(cs).input_charset.lower()
260 cs = email.charset.Charset(cs).input_charset.lower()
259
261
260 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
262 # "latin1" normalizes to "iso8859-1", standard calls for "iso-8859-1"
261 if cs.startswith("iso") and not cs.startswith("iso-"):
263 if cs.startswith("iso") and not cs.startswith("iso-"):
262 return "iso-" + cs[3:]
264 return "iso-" + cs[3:]
263 return cs
265 return cs
264
266
265
267
266 def mimetextpatch(s, subtype='plain', display=False):
268 def mimetextpatch(s, subtype='plain', display=False):
267 # type: (bytes, str, bool) -> email.message.Message
269 # type: (bytes, str, bool) -> email.message.Message
268 '''Return MIME message suitable for a patch.
270 '''Return MIME message suitable for a patch.
269 Charset will be detected by first trying to decode as us-ascii, then utf-8,
271 Charset will be detected by first trying to decode as us-ascii, then utf-8,
270 and finally the global encodings. If all those fail, fall back to
272 and finally the global encodings. If all those fail, fall back to
271 ISO-8859-1, an encoding with that allows all byte sequences.
273 ISO-8859-1, an encoding with that allows all byte sequences.
272 Transfer encodings will be used if necessary.'''
274 Transfer encodings will be used if necessary.'''
273
275
274 cs = [
276 cs = [
275 'us-ascii',
277 'us-ascii',
276 'utf-8',
278 'utf-8',
277 pycompat.sysstr(encoding.encoding),
279 pycompat.sysstr(encoding.encoding),
278 pycompat.sysstr(encoding.fallbackencoding),
280 pycompat.sysstr(encoding.fallbackencoding),
279 ]
281 ]
280 if display:
282 if display:
281 cs = ['us-ascii']
283 cs = ['us-ascii']
282 for charset in cs:
284 for charset in cs:
283 try:
285 try:
284 s.decode(charset)
286 s.decode(charset)
285 return mimetextqp(s, subtype, codec2iana(charset))
287 return mimetextqp(s, subtype, codec2iana(charset))
286 except UnicodeDecodeError:
288 except UnicodeDecodeError:
287 pass
289 pass
288
290
289 return mimetextqp(s, subtype, "iso-8859-1")
291 return mimetextqp(s, subtype, "iso-8859-1")
290
292
291
293
292 def mimetextqp(body, subtype, charset):
294 def mimetextqp(body, subtype, charset):
293 # type: (bytes, str, str) -> email.message.Message
295 # type: (bytes, str, str) -> email.message.Message
294 '''Return MIME message.
296 '''Return MIME message.
295 Quoted-printable transfer encoding will be used if necessary.
297 Quoted-printable transfer encoding will be used if necessary.
296 '''
298 '''
297 cs = email.charset.Charset(charset)
299 cs = email.charset.Charset(charset)
298 msg = email.message.Message()
300 msg = email.message.Message()
299 msg.set_type('text/' + subtype)
301 msg.set_type('text/' + subtype)
300
302
301 for line in body.splitlines():
303 for line in body.splitlines():
302 if len(line) > 950:
304 if len(line) > 950:
303 cs.body_encoding = email.charset.QP
305 cs.body_encoding = email.charset.QP
304 break
306 break
305
307
306 # On Python 2, this simply assigns a value. Python 3 inspects
308 # On Python 2, this simply assigns a value. Python 3 inspects
307 # body and does different things depending on whether it has
309 # body and does different things depending on whether it has
308 # encode() or decode() attributes. We can get the old behavior
310 # encode() or decode() attributes. We can get the old behavior
309 # if we pass a str and charset is None and we call set_charset().
311 # if we pass a str and charset is None and we call set_charset().
310 # But we may get into trouble later due to Python attempting to
312 # But we may get into trouble later due to Python attempting to
311 # encode/decode using the registered charset (or attempting to
313 # encode/decode using the registered charset (or attempting to
312 # use ascii in the absence of a charset).
314 # use ascii in the absence of a charset).
313 msg.set_payload(body, cs)
315 msg.set_payload(body, cs)
314
316
315 return msg
317 return msg
316
318
317
319
318 def _charsets(ui):
320 def _charsets(ui):
319 # type: (Any) -> List[str]
321 # type: (Any) -> List[str]
320 '''Obtains charsets to send mail parts not containing patches.'''
322 '''Obtains charsets to send mail parts not containing patches.'''
321 charsets = [
323 charsets = [
322 pycompat.sysstr(cs.lower())
324 pycompat.sysstr(cs.lower())
323 for cs in ui.configlist(b'email', b'charsets')
325 for cs in ui.configlist(b'email', b'charsets')
324 ]
326 ]
325 fallbacks = [
327 fallbacks = [
326 pycompat.sysstr(encoding.fallbackencoding.lower()),
328 pycompat.sysstr(encoding.fallbackencoding.lower()),
327 pycompat.sysstr(encoding.encoding.lower()),
329 pycompat.sysstr(encoding.encoding.lower()),
328 'utf-8',
330 'utf-8',
329 ]
331 ]
330 for cs in fallbacks: # find unique charsets while keeping order
332 for cs in fallbacks: # find unique charsets while keeping order
331 if cs not in charsets:
333 if cs not in charsets:
332 charsets.append(cs)
334 charsets.append(cs)
333 return [cs for cs in charsets if not cs.endswith('ascii')]
335 return [cs for cs in charsets if not cs.endswith('ascii')]
334
336
335
337
336 def _encode(ui, s, charsets):
338 def _encode(ui, s, charsets):
337 # type: (Any, bytes, List[str]) -> Tuple[bytes, str]
339 # type: (Any, bytes, List[str]) -> Tuple[bytes, str]
338 '''Returns (converted) string, charset tuple.
340 '''Returns (converted) string, charset tuple.
339 Finds out best charset by cycling through sendcharsets in descending
341 Finds out best charset by cycling through sendcharsets in descending
340 order. Tries both encoding and fallbackencoding for input. Only as
342 order. Tries both encoding and fallbackencoding for input. Only as
341 last resort send as is in fake ascii.
343 last resort send as is in fake ascii.
342 Caveat: Do not use for mail parts containing patches!'''
344 Caveat: Do not use for mail parts containing patches!'''
343 sendcharsets = charsets or _charsets(ui)
345 sendcharsets = charsets or _charsets(ui)
344 if not isinstance(s, bytes):
346 if not isinstance(s, bytes):
345 # We have unicode data, which we need to try and encode to
347 # We have unicode data, which we need to try and encode to
346 # some reasonable-ish encoding. Try the encodings the user
348 # some reasonable-ish encoding. Try the encodings the user
347 # wants, and fall back to garbage-in-ascii.
349 # wants, and fall back to garbage-in-ascii.
348 for ocs in sendcharsets:
350 for ocs in sendcharsets:
349 try:
351 try:
350 return s.encode(ocs), ocs
352 return s.encode(ocs), ocs
351 except UnicodeEncodeError:
353 except UnicodeEncodeError:
352 pass
354 pass
353 except LookupError:
355 except LookupError:
354 ui.warn(
356 ui.warn(
355 _(b'ignoring invalid sendcharset: %s\n')
357 _(b'ignoring invalid sendcharset: %s\n')
356 % pycompat.sysbytes(ocs)
358 % pycompat.sysbytes(ocs)
357 )
359 )
358 else:
360 else:
359 # Everything failed, ascii-armor what we've got and send it.
361 # Everything failed, ascii-armor what we've got and send it.
360 return s.encode('ascii', 'backslashreplace'), 'us-ascii'
362 return s.encode('ascii', 'backslashreplace'), 'us-ascii'
361 # We have a bytes of unknown encoding. We'll try and guess a valid
363 # We have a bytes of unknown encoding. We'll try and guess a valid
362 # encoding, falling back to pretending we had ascii even though we
364 # encoding, falling back to pretending we had ascii even though we
363 # know that's wrong.
365 # know that's wrong.
364 try:
366 try:
365 s.decode('ascii')
367 s.decode('ascii')
366 except UnicodeDecodeError:
368 except UnicodeDecodeError:
367 for ics in (encoding.encoding, encoding.fallbackencoding):
369 for ics in (encoding.encoding, encoding.fallbackencoding):
368 ics = pycompat.sysstr(ics)
370 ics = pycompat.sysstr(ics)
369 try:
371 try:
370 u = s.decode(ics)
372 u = s.decode(ics)
371 except UnicodeDecodeError:
373 except UnicodeDecodeError:
372 continue
374 continue
373 for ocs in sendcharsets:
375 for ocs in sendcharsets:
374 try:
376 try:
375 return u.encode(ocs), ocs
377 return u.encode(ocs), ocs
376 except UnicodeEncodeError:
378 except UnicodeEncodeError:
377 pass
379 pass
378 except LookupError:
380 except LookupError:
379 ui.warn(
381 ui.warn(
380 _(b'ignoring invalid sendcharset: %s\n')
382 _(b'ignoring invalid sendcharset: %s\n')
381 % pycompat.sysbytes(ocs)
383 % pycompat.sysbytes(ocs)
382 )
384 )
383 # if ascii, or all conversion attempts fail, send (broken) ascii
385 # if ascii, or all conversion attempts fail, send (broken) ascii
384 return s, 'us-ascii'
386 return s, 'us-ascii'
385
387
386
388
387 def headencode(ui, s, charsets=None, display=False):
389 def headencode(ui, s, charsets=None, display=False):
388 # type: (Any, Union[bytes, str], List[str], bool) -> str
390 # type: (Any, Union[bytes, str], List[str], bool) -> str
389 '''Returns RFC-2047 compliant header from given string.'''
391 '''Returns RFC-2047 compliant header from given string.'''
390 if not display:
392 if not display:
391 # split into words?
393 # split into words?
392 s, cs = _encode(ui, s, charsets)
394 s, cs = _encode(ui, s, charsets)
393 return email.header.Header(s, cs).encode()
395 return email.header.Header(s, cs).encode()
394 return encoding.strfromlocal(s)
396 return encoding.strfromlocal(s)
395
397
396
398
397 def _addressencode(ui, name, addr, charsets=None):
399 def _addressencode(ui, name, addr, charsets=None):
398 # type: (Any, str, str, List[str]) -> str
400 # type: (Any, str, str, List[str]) -> str
399 addr = encoding.strtolocal(addr)
401 addr = encoding.strtolocal(addr)
400 name = headencode(ui, name, charsets)
402 name = headencode(ui, name, charsets)
401 try:
403 try:
402 acc, dom = addr.split(b'@')
404 acc, dom = addr.split(b'@')
403 acc.decode('ascii')
405 acc.decode('ascii')
404 dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna')
406 dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna')
405 addr = b'%s@%s' % (acc, dom)
407 addr = b'%s@%s' % (acc, dom)
406 except UnicodeDecodeError:
408 except UnicodeDecodeError:
407 raise error.Abort(_(b'invalid email address: %s') % addr)
409 raise error.Abort(_(b'invalid email address: %s') % addr)
408 except ValueError:
410 except ValueError:
409 try:
411 try:
410 # too strict?
412 # too strict?
411 addr.decode('ascii')
413 addr.decode('ascii')
412 except UnicodeDecodeError:
414 except UnicodeDecodeError:
413 raise error.Abort(_(b'invalid local address: %s') % addr)
415 raise error.Abort(_(b'invalid local address: %s') % addr)
414 return email.utils.formataddr((name, encoding.strfromlocal(addr)))
416 return email.utils.formataddr((name, encoding.strfromlocal(addr)))
415
417
416
418
417 def addressencode(ui, address, charsets=None, display=False):
419 def addressencode(ui, address, charsets=None, display=False):
418 # type: (Any, bytes, List[str], bool) -> str
420 # type: (Any, bytes, List[str], bool) -> str
419 '''Turns address into RFC-2047 compliant header.'''
421 '''Turns address into RFC-2047 compliant header.'''
420 if display or not address:
422 if display or not address:
421 return encoding.strfromlocal(address or b'')
423 return encoding.strfromlocal(address or b'')
422 name, addr = email.utils.parseaddr(encoding.strfromlocal(address))
424 name, addr = email.utils.parseaddr(encoding.strfromlocal(address))
423 return _addressencode(ui, name, addr, charsets)
425 return _addressencode(ui, name, addr, charsets)
424
426
425
427
426 def addrlistencode(ui, addrs, charsets=None, display=False):
428 def addrlistencode(ui, addrs, charsets=None, display=False):
427 # type: (Any, List[bytes], List[str], bool) -> List[str]
429 # type: (Any, List[bytes], List[str], bool) -> List[str]
428 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
430 '''Turns a list of addresses into a list of RFC-2047 compliant headers.
429 A single element of input list may contain multiple addresses, but output
431 A single element of input list may contain multiple addresses, but output
430 always has one address per item'''
432 always has one address per item'''
431 straddrs = []
433 straddrs = []
432 for a in addrs:
434 for a in addrs:
433 assert isinstance(a, bytes), '%r unexpectedly not a bytestr' % a
435 assert isinstance(a, bytes), '%r unexpectedly not a bytestr' % a
434 straddrs.append(encoding.strfromlocal(a))
436 straddrs.append(encoding.strfromlocal(a))
435 if display:
437 if display:
436 return [a.strip() for a in straddrs if a.strip()]
438 return [a.strip() for a in straddrs if a.strip()]
437
439
438 result = []
440 result = []
439 for name, addr in email.utils.getaddresses(straddrs):
441 for name, addr in email.utils.getaddresses(straddrs):
440 if name or addr:
442 if name or addr:
441 r = _addressencode(ui, name, addr, charsets)
443 r = _addressencode(ui, name, addr, charsets)
442 result.append(r)
444 result.append(r)
443 return result
445 return result
444
446
445
447
446 def mimeencode(ui, s, charsets=None, display=False):
448 def mimeencode(ui, s, charsets=None, display=False):
447 # type: (Any, bytes, List[str], bool) -> email.message.Message
449 # type: (Any, bytes, List[str], bool) -> email.message.Message
448 '''creates mime text object, encodes it if needed, and sets
450 '''creates mime text object, encodes it if needed, and sets
449 charset and transfer-encoding accordingly.'''
451 charset and transfer-encoding accordingly.'''
450 cs = 'us-ascii'
452 cs = 'us-ascii'
451 if not display:
453 if not display:
452 s, cs = _encode(ui, s, charsets)
454 s, cs = _encode(ui, s, charsets)
453 return mimetextqp(s, 'plain', cs)
455 return mimetextqp(s, 'plain', cs)
454
456
455
457
456 if pycompat.ispy3:
458 if pycompat.ispy3:
457
459
458 Generator = email.generator.BytesGenerator
460 Generator = email.generator.BytesGenerator
459
461
460 def parse(fp):
462 def parse(fp):
461 # type: (Any) -> email.message.Message
463 # type: (Any) -> email.message.Message
462 ep = email.parser.Parser()
464 ep = email.parser.Parser()
463 # disable the "universal newlines" mode, which isn't binary safe.
465 # disable the "universal newlines" mode, which isn't binary safe.
464 # I have no idea if ascii/surrogateescape is correct, but that's
466 # I have no idea if ascii/surrogateescape is correct, but that's
465 # what the standard Python email parser does.
467 # what the standard Python email parser does.
466 fp = io.TextIOWrapper(
468 fp = io.TextIOWrapper(
467 fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
469 fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
468 )
470 )
469 try:
471 try:
470 return ep.parse(fp)
472 return ep.parse(fp)
471 finally:
473 finally:
472 fp.detach()
474 fp.detach()
473
475
474 def parsebytes(data):
476 def parsebytes(data):
475 # type: (bytes) -> email.message.Message
477 # type: (bytes) -> email.message.Message
476 ep = email.parser.BytesParser()
478 ep = email.parser.BytesParser()
477 return ep.parsebytes(data)
479 return ep.parsebytes(data)
478
480
479
481
480 else:
482 else:
481
483
482 Generator = email.generator.Generator
484 Generator = email.generator.Generator
483
485
484 def parse(fp):
486 def parse(fp):
485 # type: (Any) -> email.message.Message
487 # type: (Any) -> email.message.Message
486 ep = email.parser.Parser()
488 ep = email.parser.Parser()
487 return ep.parse(fp)
489 return ep.parse(fp)
488
490
489 def parsebytes(data):
491 def parsebytes(data):
490 # type: (str) -> email.message.Message
492 # type: (str) -> email.message.Message
491 ep = email.parser.Parser()
493 ep = email.parser.Parser()
492 return ep.parsestr(data)
494 return ep.parsestr(data)
493
495
494
496
495 def headdecode(s):
497 def headdecode(s):
496 # type: (Union[email.header.Header, bytes]) -> bytes
498 # type: (Union[email.header.Header, bytes]) -> bytes
497 '''Decodes RFC-2047 header'''
499 '''Decodes RFC-2047 header'''
498 uparts = []
500 uparts = []
499 for part, charset in email.header.decode_header(s):
501 for part, charset in email.header.decode_header(s):
500 if charset is not None:
502 if charset is not None:
501 try:
503 try:
502 uparts.append(part.decode(charset))
504 uparts.append(part.decode(charset))
503 continue
505 continue
504 except (UnicodeDecodeError, LookupError):
506 except (UnicodeDecodeError, LookupError):
505 pass
507 pass
506 # On Python 3, decode_header() may return either bytes or unicode
508 # On Python 3, decode_header() may return either bytes or unicode
507 # depending on whether the header has =?<charset>? or not
509 # depending on whether the header has =?<charset>? or not
508 if isinstance(part, type(u'')):
510 if isinstance(part, type(u'')):
509 uparts.append(part)
511 uparts.append(part)
510 continue
512 continue
511 try:
513 try:
512 uparts.append(part.decode('UTF-8'))
514 uparts.append(part.decode('UTF-8'))
513 continue
515 continue
514 except UnicodeDecodeError:
516 except UnicodeDecodeError:
515 pass
517 pass
516 uparts.append(part.decode('ISO-8859-1'))
518 uparts.append(part.decode('ISO-8859-1'))
517 return encoding.unitolocal(u' '.join(uparts))
519 return encoding.unitolocal(u' '.join(uparts))
@@ -1,2370 +1,2370 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 datetime
12 import datetime
13 import errno
13 import errno
14 import getpass
14 import getpass
15 import inspect
15 import inspect
16 import os
16 import os
17 import re
17 import re
18 import signal
18 import signal
19 import socket
19 import socket
20 import subprocess
20 import subprocess
21 import sys
21 import sys
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 from .pycompat import (
26 from .pycompat import (
27 getattr,
27 getattr,
28 open,
28 open,
29 setattr,
29 setattr,
30 )
30 )
31
31
32 from . import (
32 from . import (
33 color,
33 color,
34 config,
34 config,
35 configitems,
35 configitems,
36 encoding,
36 encoding,
37 error,
37 error,
38 formatter,
38 formatter,
39 loggingutil,
39 loggingutil,
40 progress,
40 progress,
41 pycompat,
41 pycompat,
42 rcutil,
42 rcutil,
43 scmutil,
43 scmutil,
44 util,
44 util,
45 )
45 )
46 from .utils import (
46 from .utils import (
47 dateutil,
47 dateutil,
48 procutil,
48 procutil,
49 resourceutil,
49 resourceutil,
50 stringutil,
50 stringutil,
51 )
51 )
52
52
53 urlreq = util.urlreq
53 urlreq = util.urlreq
54
54
55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
55 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
56 _keepalnum = b''.join(
56 _keepalnum = b''.join(
57 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
57 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
58 )
58 )
59
59
60 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
60 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
61 tweakrc = b"""
61 tweakrc = b"""
62 [ui]
62 [ui]
63 # The rollback command is dangerous. As a rule, don't use it.
63 # The rollback command is dangerous. As a rule, don't use it.
64 rollback = False
64 rollback = False
65 # Make `hg status` report copy information
65 # Make `hg status` report copy information
66 statuscopies = yes
66 statuscopies = yes
67 # Prefer curses UIs when available. Revert to plain-text with `text`.
67 # Prefer curses UIs when available. Revert to plain-text with `text`.
68 interface = curses
68 interface = curses
69 # Make compatible commands emit cwd-relative paths by default.
69 # Make compatible commands emit cwd-relative paths by default.
70 relative-paths = yes
70 relative-paths = yes
71
71
72 [commands]
72 [commands]
73 # Grep working directory by default.
73 # Grep working directory by default.
74 grep.all-files = True
74 grep.all-files = True
75 # Refuse to perform an `hg update` that would cause a file content merge
75 # Refuse to perform an `hg update` that would cause a file content merge
76 update.check = noconflict
76 update.check = noconflict
77 # Show conflicts information in `hg status`
77 # Show conflicts information in `hg status`
78 status.verbose = True
78 status.verbose = True
79 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
79 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
80 resolve.explicit-re-merge = True
80 resolve.explicit-re-merge = True
81
81
82 [diff]
82 [diff]
83 git = 1
83 git = 1
84 showfunc = 1
84 showfunc = 1
85 word-diff = 1
85 word-diff = 1
86 """
86 """
87
87
88 samplehgrcs = {
88 samplehgrcs = {
89 b'user': b"""# example user config (see 'hg help config' for more info)
89 b'user': b"""# example user config (see 'hg help config' for more info)
90 [ui]
90 [ui]
91 # name and email, e.g.
91 # name and email, e.g.
92 # username = Jane Doe <jdoe@example.com>
92 # username = Jane Doe <jdoe@example.com>
93 username =
93 username =
94
94
95 # We recommend enabling tweakdefaults to get slight improvements to
95 # We recommend enabling tweakdefaults to get slight improvements to
96 # the UI over time. Make sure to set HGPLAIN in the environment when
96 # the UI over time. Make sure to set HGPLAIN in the environment when
97 # writing scripts!
97 # writing scripts!
98 # tweakdefaults = True
98 # tweakdefaults = True
99
99
100 # uncomment to disable color in command output
100 # uncomment to disable color in command output
101 # (see 'hg help color' for details)
101 # (see 'hg help color' for details)
102 # color = never
102 # color = never
103
103
104 # uncomment to disable command output pagination
104 # uncomment to disable command output pagination
105 # (see 'hg help pager' for details)
105 # (see 'hg help pager' for details)
106 # paginate = never
106 # paginate = never
107
107
108 [extensions]
108 [extensions]
109 # uncomment the lines below to enable some popular extensions
109 # uncomment the lines below to enable some popular extensions
110 # (see 'hg help extensions' for more info)
110 # (see 'hg help extensions' for more info)
111 #
111 #
112 # histedit =
112 # histedit =
113 # rebase =
113 # rebase =
114 # uncommit =
114 # uncommit =
115 """,
115 """,
116 b'cloned': b"""# example repository config (see 'hg help config' for more info)
116 b'cloned': b"""# example repository config (see 'hg help config' for more info)
117 [paths]
117 [paths]
118 default = %s
118 default = %s
119
119
120 # path aliases to other clones of this repo in URLs or filesystem paths
120 # path aliases to other clones of this repo in URLs or filesystem paths
121 # (see 'hg help config.paths' for more info)
121 # (see 'hg help config.paths' for more info)
122 #
122 #
123 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
123 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
124 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
124 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
125 # my-clone = /home/jdoe/jdoes-clone
125 # my-clone = /home/jdoe/jdoes-clone
126
126
127 [ui]
127 [ui]
128 # name and email (local to this repository, optional), e.g.
128 # name and email (local to this repository, optional), e.g.
129 # username = Jane Doe <jdoe@example.com>
129 # username = Jane Doe <jdoe@example.com>
130 """,
130 """,
131 b'local': b"""# example repository config (see 'hg help config' for more info)
131 b'local': b"""# example repository config (see 'hg help config' for more info)
132 [paths]
132 [paths]
133 # path aliases to other clones of this repo in URLs or filesystem paths
133 # path aliases to other clones of this repo in URLs or filesystem paths
134 # (see 'hg help config.paths' for more info)
134 # (see 'hg help config.paths' for more info)
135 #
135 #
136 # default = http://example.com/hg/example-repo
136 # default = http://example.com/hg/example-repo
137 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
137 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
138 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
138 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
139 # my-clone = /home/jdoe/jdoes-clone
139 # my-clone = /home/jdoe/jdoes-clone
140
140
141 [ui]
141 [ui]
142 # name and email (local to this repository, optional), e.g.
142 # name and email (local to this repository, optional), e.g.
143 # username = Jane Doe <jdoe@example.com>
143 # username = Jane Doe <jdoe@example.com>
144 """,
144 """,
145 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
145 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
146
146
147 [ui]
147 [ui]
148 # uncomment to disable color in command output
148 # uncomment to disable color in command output
149 # (see 'hg help color' for details)
149 # (see 'hg help color' for details)
150 # color = never
150 # color = never
151
151
152 # uncomment to disable command output pagination
152 # uncomment to disable command output pagination
153 # (see 'hg help pager' for details)
153 # (see 'hg help pager' for details)
154 # paginate = never
154 # paginate = never
155
155
156 [extensions]
156 [extensions]
157 # uncomment the lines below to enable some popular extensions
157 # uncomment the lines below to enable some popular extensions
158 # (see 'hg help extensions' for more info)
158 # (see 'hg help extensions' for more info)
159 #
159 #
160 # blackbox =
160 # blackbox =
161 # churn =
161 # churn =
162 """,
162 """,
163 }
163 }
164
164
165
165
166 def _maybestrurl(maybebytes):
166 def _maybestrurl(maybebytes):
167 return pycompat.rapply(pycompat.strurl, maybebytes)
167 return pycompat.rapply(pycompat.strurl, maybebytes)
168
168
169
169
170 def _maybebytesurl(maybestr):
170 def _maybebytesurl(maybestr):
171 return pycompat.rapply(pycompat.bytesurl, maybestr)
171 return pycompat.rapply(pycompat.bytesurl, maybestr)
172
172
173
173
174 class httppasswordmgrdbproxy(object):
174 class httppasswordmgrdbproxy(object):
175 """Delays loading urllib2 until it's needed."""
175 """Delays loading urllib2 until it's needed."""
176
176
177 def __init__(self):
177 def __init__(self):
178 self._mgr = None
178 self._mgr = None
179
179
180 def _get_mgr(self):
180 def _get_mgr(self):
181 if self._mgr is None:
181 if self._mgr is None:
182 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
182 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
183 return self._mgr
183 return self._mgr
184
184
185 def add_password(self, realm, uris, user, passwd):
185 def add_password(self, realm, uris, user, passwd):
186 return self._get_mgr().add_password(
186 return self._get_mgr().add_password(
187 _maybestrurl(realm),
187 _maybestrurl(realm),
188 _maybestrurl(uris),
188 _maybestrurl(uris),
189 _maybestrurl(user),
189 _maybestrurl(user),
190 _maybestrurl(passwd),
190 _maybestrurl(passwd),
191 )
191 )
192
192
193 def find_user_password(self, realm, uri):
193 def find_user_password(self, realm, uri):
194 mgr = self._get_mgr()
194 mgr = self._get_mgr()
195 return _maybebytesurl(
195 return _maybebytesurl(
196 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
196 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
197 )
197 )
198
198
199
199
200 def _catchterm(*args):
200 def _catchterm(*args):
201 raise error.SignalInterrupt
201 raise error.SignalInterrupt
202
202
203
203
204 # unique object used to detect no default value has been provided when
204 # unique object used to detect no default value has been provided when
205 # retrieving configuration value.
205 # retrieving configuration value.
206 _unset = object()
206 _unset = object()
207
207
208 # _reqexithandlers: callbacks run at the end of a request
208 # _reqexithandlers: callbacks run at the end of a request
209 _reqexithandlers = []
209 _reqexithandlers = []
210
210
211
211
212 class ui(object):
212 class ui(object):
213 def __init__(self, src=None):
213 def __init__(self, src=None):
214 """Create a fresh new ui object if no src given
214 """Create a fresh new ui object if no src given
215
215
216 Use uimod.ui.load() to create a ui which knows global and user configs.
216 Use uimod.ui.load() to create a ui which knows global and user configs.
217 In most cases, you should use ui.copy() to create a copy of an existing
217 In most cases, you should use ui.copy() to create a copy of an existing
218 ui object.
218 ui object.
219 """
219 """
220 # _buffers: used for temporary capture of output
220 # _buffers: used for temporary capture of output
221 self._buffers = []
221 self._buffers = []
222 # 3-tuple describing how each buffer in the stack behaves.
222 # 3-tuple describing how each buffer in the stack behaves.
223 # Values are (capture stderr, capture subprocesses, apply labels).
223 # Values are (capture stderr, capture subprocesses, apply labels).
224 self._bufferstates = []
224 self._bufferstates = []
225 # When a buffer is active, defines whether we are expanding labels.
225 # When a buffer is active, defines whether we are expanding labels.
226 # This exists to prevent an extra list lookup.
226 # This exists to prevent an extra list lookup.
227 self._bufferapplylabels = None
227 self._bufferapplylabels = None
228 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
228 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
229 self._reportuntrusted = True
229 self._reportuntrusted = True
230 self._knownconfig = configitems.coreitems
230 self._knownconfig = configitems.coreitems
231 self._ocfg = config.config() # overlay
231 self._ocfg = config.config() # overlay
232 self._tcfg = config.config() # trusted
232 self._tcfg = config.config() # trusted
233 self._ucfg = config.config() # untrusted
233 self._ucfg = config.config() # untrusted
234 self._trustusers = set()
234 self._trustusers = set()
235 self._trustgroups = set()
235 self._trustgroups = set()
236 self.callhooks = True
236 self.callhooks = True
237 # Insecure server connections requested.
237 # Insecure server connections requested.
238 self.insecureconnections = False
238 self.insecureconnections = False
239 # Blocked time
239 # Blocked time
240 self.logblockedtimes = False
240 self.logblockedtimes = False
241 # color mode: see mercurial/color.py for possible value
241 # color mode: see mercurial/color.py for possible value
242 self._colormode = None
242 self._colormode = None
243 self._terminfoparams = {}
243 self._terminfoparams = {}
244 self._styles = {}
244 self._styles = {}
245 self._uninterruptible = False
245 self._uninterruptible = False
246 self.showtimestamp = False
246 self.showtimestamp = False
247
247
248 if src:
248 if src:
249 self._fout = src._fout
249 self._fout = src._fout
250 self._ferr = src._ferr
250 self._ferr = src._ferr
251 self._fin = src._fin
251 self._fin = src._fin
252 self._fmsg = src._fmsg
252 self._fmsg = src._fmsg
253 self._fmsgout = src._fmsgout
253 self._fmsgout = src._fmsgout
254 self._fmsgerr = src._fmsgerr
254 self._fmsgerr = src._fmsgerr
255 self._finoutredirected = src._finoutredirected
255 self._finoutredirected = src._finoutredirected
256 self._loggers = src._loggers.copy()
256 self._loggers = src._loggers.copy()
257 self.pageractive = src.pageractive
257 self.pageractive = src.pageractive
258 self._disablepager = src._disablepager
258 self._disablepager = src._disablepager
259 self._tweaked = src._tweaked
259 self._tweaked = src._tweaked
260
260
261 self._tcfg = src._tcfg.copy()
261 self._tcfg = src._tcfg.copy()
262 self._ucfg = src._ucfg.copy()
262 self._ucfg = src._ucfg.copy()
263 self._ocfg = src._ocfg.copy()
263 self._ocfg = src._ocfg.copy()
264 self._trustusers = src._trustusers.copy()
264 self._trustusers = src._trustusers.copy()
265 self._trustgroups = src._trustgroups.copy()
265 self._trustgroups = src._trustgroups.copy()
266 self.environ = src.environ
266 self.environ = src.environ
267 self.callhooks = src.callhooks
267 self.callhooks = src.callhooks
268 self.insecureconnections = src.insecureconnections
268 self.insecureconnections = src.insecureconnections
269 self._colormode = src._colormode
269 self._colormode = src._colormode
270 self._terminfoparams = src._terminfoparams.copy()
270 self._terminfoparams = src._terminfoparams.copy()
271 self._styles = src._styles.copy()
271 self._styles = src._styles.copy()
272
272
273 self.fixconfig()
273 self.fixconfig()
274
274
275 self.httppasswordmgrdb = src.httppasswordmgrdb
275 self.httppasswordmgrdb = src.httppasswordmgrdb
276 self._blockedtimes = src._blockedtimes
276 self._blockedtimes = src._blockedtimes
277 else:
277 else:
278 self._fout = procutil.stdout
278 self._fout = procutil.stdout
279 self._ferr = procutil.stderr
279 self._ferr = procutil.stderr
280 self._fin = procutil.stdin
280 self._fin = procutil.stdin
281 self._fmsg = None
281 self._fmsg = None
282 self._fmsgout = self.fout # configurable
282 self._fmsgout = self.fout # configurable
283 self._fmsgerr = self.ferr # configurable
283 self._fmsgerr = self.ferr # configurable
284 self._finoutredirected = False
284 self._finoutredirected = False
285 self._loggers = {}
285 self._loggers = {}
286 self.pageractive = False
286 self.pageractive = False
287 self._disablepager = False
287 self._disablepager = False
288 self._tweaked = False
288 self._tweaked = False
289
289
290 # shared read-only environment
290 # shared read-only environment
291 self.environ = encoding.environ
291 self.environ = encoding.environ
292
292
293 self.httppasswordmgrdb = httppasswordmgrdbproxy()
293 self.httppasswordmgrdb = httppasswordmgrdbproxy()
294 self._blockedtimes = collections.defaultdict(int)
294 self._blockedtimes = collections.defaultdict(int)
295
295
296 allowed = self.configlist(b'experimental', b'exportableenviron')
296 allowed = self.configlist(b'experimental', b'exportableenviron')
297 if b'*' in allowed:
297 if b'*' in allowed:
298 self._exportableenviron = self.environ
298 self._exportableenviron = self.environ
299 else:
299 else:
300 self._exportableenviron = {}
300 self._exportableenviron = {}
301 for k in allowed:
301 for k in allowed:
302 if k in self.environ:
302 if k in self.environ:
303 self._exportableenviron[k] = self.environ[k]
303 self._exportableenviron[k] = self.environ[k]
304
304
305 @classmethod
305 @classmethod
306 def load(cls):
306 def load(cls):
307 """Create a ui and load global and user configs"""
307 """Create a ui and load global and user configs"""
308 u = cls()
308 u = cls()
309 # we always trust global config files and environment variables
309 # we always trust global config files and environment variables
310 for t, f in rcutil.rccomponents():
310 for t, f in rcutil.rccomponents():
311 if t == b'path':
311 if t == b'path':
312 u.readconfig(f, trust=True)
312 u.readconfig(f, trust=True)
313 elif t == b'resource':
313 elif t == b'resource':
314 u.read_resource_config(f, trust=True)
314 u.read_resource_config(f, trust=True)
315 elif t == b'items':
315 elif t == b'items':
316 sections = set()
316 sections = set()
317 for section, name, value, source in f:
317 for section, name, value, source in f:
318 # do not set u._ocfg
318 # do not set u._ocfg
319 # XXX clean this up once immutable config object is a thing
319 # XXX clean this up once immutable config object is a thing
320 u._tcfg.set(section, name, value, source)
320 u._tcfg.set(section, name, value, source)
321 u._ucfg.set(section, name, value, source)
321 u._ucfg.set(section, name, value, source)
322 sections.add(section)
322 sections.add(section)
323 for section in sections:
323 for section in sections:
324 u.fixconfig(section=section)
324 u.fixconfig(section=section)
325 else:
325 else:
326 raise error.ProgrammingError(b'unknown rctype: %s' % t)
326 raise error.ProgrammingError(b'unknown rctype: %s' % t)
327 u._maybetweakdefaults()
327 u._maybetweakdefaults()
328 return u
328 return u
329
329
330 def _maybetweakdefaults(self):
330 def _maybetweakdefaults(self):
331 if not self.configbool(b'ui', b'tweakdefaults'):
331 if not self.configbool(b'ui', b'tweakdefaults'):
332 return
332 return
333 if self._tweaked or self.plain(b'tweakdefaults'):
333 if self._tweaked or self.plain(b'tweakdefaults'):
334 return
334 return
335
335
336 # Note: it is SUPER IMPORTANT that you set self._tweaked to
336 # Note: it is SUPER IMPORTANT that you set self._tweaked to
337 # True *before* any calls to setconfig(), otherwise you'll get
337 # True *before* any calls to setconfig(), otherwise you'll get
338 # infinite recursion between setconfig and this method.
338 # infinite recursion between setconfig and this method.
339 #
339 #
340 # TODO: We should extract an inner method in setconfig() to
340 # TODO: We should extract an inner method in setconfig() to
341 # avoid this weirdness.
341 # avoid this weirdness.
342 self._tweaked = True
342 self._tweaked = True
343 tmpcfg = config.config()
343 tmpcfg = config.config()
344 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
344 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
345 for section in tmpcfg:
345 for section in tmpcfg:
346 for name, value in tmpcfg.items(section):
346 for name, value in tmpcfg.items(section):
347 if not self.hasconfig(section, name):
347 if not self.hasconfig(section, name):
348 self.setconfig(section, name, value, b"<tweakdefaults>")
348 self.setconfig(section, name, value, b"<tweakdefaults>")
349
349
350 def copy(self):
350 def copy(self):
351 return self.__class__(self)
351 return self.__class__(self)
352
352
353 def resetstate(self):
353 def resetstate(self):
354 """Clear internal state that shouldn't persist across commands"""
354 """Clear internal state that shouldn't persist across commands"""
355 if self._progbar:
355 if self._progbar:
356 self._progbar.resetstate() # reset last-print time of progress bar
356 self._progbar.resetstate() # reset last-print time of progress bar
357 self.httppasswordmgrdb = httppasswordmgrdbproxy()
357 self.httppasswordmgrdb = httppasswordmgrdbproxy()
358
358
359 @contextlib.contextmanager
359 @contextlib.contextmanager
360 def timeblockedsection(self, key):
360 def timeblockedsection(self, key):
361 # this is open-coded below - search for timeblockedsection to find them
361 # this is open-coded below - search for timeblockedsection to find them
362 starttime = util.timer()
362 starttime = util.timer()
363 try:
363 try:
364 yield
364 yield
365 finally:
365 finally:
366 self._blockedtimes[key + b'_blocked'] += (
366 self._blockedtimes[key + b'_blocked'] += (
367 util.timer() - starttime
367 util.timer() - starttime
368 ) * 1000
368 ) * 1000
369
369
370 @contextlib.contextmanager
370 @contextlib.contextmanager
371 def uninterruptible(self):
371 def uninterruptible(self):
372 """Mark an operation as unsafe.
372 """Mark an operation as unsafe.
373
373
374 Most operations on a repository are safe to interrupt, but a
374 Most operations on a repository are safe to interrupt, but a
375 few are risky (for example repair.strip). This context manager
375 few are risky (for example repair.strip). This context manager
376 lets you advise Mercurial that something risky is happening so
376 lets you advise Mercurial that something risky is happening so
377 that control-C etc can be blocked if desired.
377 that control-C etc can be blocked if desired.
378 """
378 """
379 enabled = self.configbool(b'experimental', b'nointerrupt')
379 enabled = self.configbool(b'experimental', b'nointerrupt')
380 if enabled and self.configbool(
380 if enabled and self.configbool(
381 b'experimental', b'nointerrupt-interactiveonly'
381 b'experimental', b'nointerrupt-interactiveonly'
382 ):
382 ):
383 enabled = self.interactive()
383 enabled = self.interactive()
384 if self._uninterruptible or not enabled:
384 if self._uninterruptible or not enabled:
385 # if nointerrupt support is turned off, the process isn't
385 # if nointerrupt support is turned off, the process isn't
386 # interactive, or we're already in an uninterruptible
386 # interactive, or we're already in an uninterruptible
387 # block, do nothing.
387 # block, do nothing.
388 yield
388 yield
389 return
389 return
390
390
391 def warn():
391 def warn():
392 self.warn(_(b"shutting down cleanly\n"))
392 self.warn(_(b"shutting down cleanly\n"))
393 self.warn(
393 self.warn(
394 _(b"press ^C again to terminate immediately (dangerous)\n")
394 _(b"press ^C again to terminate immediately (dangerous)\n")
395 )
395 )
396 return True
396 return True
397
397
398 with procutil.uninterruptible(warn):
398 with procutil.uninterruptible(warn):
399 try:
399 try:
400 self._uninterruptible = True
400 self._uninterruptible = True
401 yield
401 yield
402 finally:
402 finally:
403 self._uninterruptible = False
403 self._uninterruptible = False
404
404
405 def formatter(self, topic, opts):
405 def formatter(self, topic, opts):
406 return formatter.formatter(self, self, topic, opts)
406 return formatter.formatter(self, self, topic, opts)
407
407
408 def _trusted(self, fp, f):
408 def _trusted(self, fp, f):
409 st = util.fstat(fp)
409 st = util.fstat(fp)
410 if util.isowner(st):
410 if util.isowner(st):
411 return True
411 return True
412
412
413 tusers, tgroups = self._trustusers, self._trustgroups
413 tusers, tgroups = self._trustusers, self._trustgroups
414 if b'*' in tusers or b'*' in tgroups:
414 if b'*' in tusers or b'*' in tgroups:
415 return True
415 return True
416
416
417 user = util.username(st.st_uid)
417 user = util.username(st.st_uid)
418 group = util.groupname(st.st_gid)
418 group = util.groupname(st.st_gid)
419 if user in tusers or group in tgroups or user == util.username():
419 if user in tusers or group in tgroups or user == util.username():
420 return True
420 return True
421
421
422 if self._reportuntrusted:
422 if self._reportuntrusted:
423 self.warn(
423 self.warn(
424 _(
424 _(
425 b'not trusting file %s from untrusted '
425 b'not trusting file %s from untrusted '
426 b'user %s, group %s\n'
426 b'user %s, group %s\n'
427 )
427 )
428 % (f, user, group)
428 % (f, user, group)
429 )
429 )
430 return False
430 return False
431
431
432 def read_resource_config(
432 def read_resource_config(
433 self, name, root=None, trust=False, sections=None, remap=None
433 self, name, root=None, trust=False, sections=None, remap=None
434 ):
434 ):
435 try:
435 try:
436 fp = resourceutil.open_resource(name[0], name[1])
436 fp = resourceutil.open_resource(name[0], name[1])
437 except IOError:
437 except IOError:
438 if not sections: # ignore unless we were looking for something
438 if not sections: # ignore unless we were looking for something
439 return
439 return
440 raise
440 raise
441
441
442 self._readconfig(
442 self._readconfig(
443 b'resource:%s.%s' % name, fp, root, trust, sections, remap
443 b'resource:%s.%s' % name, fp, root, trust, sections, remap
444 )
444 )
445
445
446 def readconfig(
446 def readconfig(
447 self, filename, root=None, trust=False, sections=None, remap=None
447 self, filename, root=None, trust=False, sections=None, remap=None
448 ):
448 ):
449 try:
449 try:
450 fp = open(filename, 'rb')
450 fp = open(filename, 'rb')
451 except IOError:
451 except IOError:
452 if not sections: # ignore unless we were looking for something
452 if not sections: # ignore unless we were looking for something
453 return
453 return
454 raise
454 raise
455
455
456 self._readconfig(filename, fp, root, trust, sections, remap)
456 self._readconfig(filename, fp, root, trust, sections, remap)
457
457
458 def _readconfig(
458 def _readconfig(
459 self, filename, fp, root=None, trust=False, sections=None, remap=None
459 self, filename, fp, root=None, trust=False, sections=None, remap=None
460 ):
460 ):
461 with fp:
461 with fp:
462 cfg = config.config()
462 cfg = config.config()
463 trusted = sections or trust or self._trusted(fp, filename)
463 trusted = sections or trust or self._trusted(fp, filename)
464
464
465 try:
465 try:
466 cfg.read(filename, fp, sections=sections, remap=remap)
466 cfg.read(filename, fp, sections=sections, remap=remap)
467 except error.ParseError as inst:
467 except error.ParseError as inst:
468 if trusted:
468 if trusted:
469 raise
469 raise
470 self.warn(_(b'ignored: %s\n') % stringutil.forcebytestr(inst))
470 self.warn(_(b'ignored: %s\n') % stringutil.forcebytestr(inst))
471
471
472 self._applyconfig(cfg, trusted, root)
472 self._applyconfig(cfg, trusted, root)
473
473
474 def applyconfig(self, configitems, source=b"", root=None):
474 def applyconfig(self, configitems, source=b"", root=None):
475 """Add configitems from a non-file source. Unlike with ``setconfig()``,
475 """Add configitems from a non-file source. Unlike with ``setconfig()``,
476 they can be overridden by subsequent config file reads. The items are
476 they can be overridden by subsequent config file reads. The items are
477 in the same format as ``configoverride()``, namely a dict of the
477 in the same format as ``configoverride()``, namely a dict of the
478 following structures: {(section, name) : value}
478 following structures: {(section, name) : value}
479
479
480 Typically this is used by extensions that inject themselves into the
480 Typically this is used by extensions that inject themselves into the
481 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
481 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
482 """
482 """
483 cfg = config.config()
483 cfg = config.config()
484
484
485 for (section, name), value in configitems.items():
485 for (section, name), value in configitems.items():
486 cfg.set(section, name, value, source)
486 cfg.set(section, name, value, source)
487
487
488 self._applyconfig(cfg, True, root)
488 self._applyconfig(cfg, True, root)
489
489
490 def _applyconfig(self, cfg, trusted, root):
490 def _applyconfig(self, cfg, trusted, root):
491 if self.plain():
491 if self.plain():
492 for k in (
492 for k in (
493 b'debug',
493 b'debug',
494 b'fallbackencoding',
494 b'fallbackencoding',
495 b'quiet',
495 b'quiet',
496 b'slash',
496 b'slash',
497 b'logtemplate',
497 b'logtemplate',
498 b'message-output',
498 b'message-output',
499 b'statuscopies',
499 b'statuscopies',
500 b'style',
500 b'style',
501 b'traceback',
501 b'traceback',
502 b'verbose',
502 b'verbose',
503 ):
503 ):
504 if k in cfg[b'ui']:
504 if k in cfg[b'ui']:
505 del cfg[b'ui'][k]
505 del cfg[b'ui'][k]
506 for k, v in cfg.items(b'defaults'):
506 for k, v in cfg.items(b'defaults'):
507 del cfg[b'defaults'][k]
507 del cfg[b'defaults'][k]
508 for k, v in cfg.items(b'commands'):
508 for k, v in cfg.items(b'commands'):
509 del cfg[b'commands'][k]
509 del cfg[b'commands'][k]
510 # Don't remove aliases from the configuration if in the exceptionlist
510 # Don't remove aliases from the configuration if in the exceptionlist
511 if self.plain(b'alias'):
511 if self.plain(b'alias'):
512 for k, v in cfg.items(b'alias'):
512 for k, v in cfg.items(b'alias'):
513 del cfg[b'alias'][k]
513 del cfg[b'alias'][k]
514 if self.plain(b'revsetalias'):
514 if self.plain(b'revsetalias'):
515 for k, v in cfg.items(b'revsetalias'):
515 for k, v in cfg.items(b'revsetalias'):
516 del cfg[b'revsetalias'][k]
516 del cfg[b'revsetalias'][k]
517 if self.plain(b'templatealias'):
517 if self.plain(b'templatealias'):
518 for k, v in cfg.items(b'templatealias'):
518 for k, v in cfg.items(b'templatealias'):
519 del cfg[b'templatealias'][k]
519 del cfg[b'templatealias'][k]
520
520
521 if trusted:
521 if trusted:
522 self._tcfg.update(cfg)
522 self._tcfg.update(cfg)
523 self._tcfg.update(self._ocfg)
523 self._tcfg.update(self._ocfg)
524 self._ucfg.update(cfg)
524 self._ucfg.update(cfg)
525 self._ucfg.update(self._ocfg)
525 self._ucfg.update(self._ocfg)
526
526
527 if root is None:
527 if root is None:
528 root = os.path.expanduser(b'~')
528 root = os.path.expanduser(b'~')
529 self.fixconfig(root=root)
529 self.fixconfig(root=root)
530
530
531 def fixconfig(self, root=None, section=None):
531 def fixconfig(self, root=None, section=None):
532 if section in (None, b'paths'):
532 if section in (None, b'paths'):
533 # expand vars and ~
533 # expand vars and ~
534 # translate paths relative to root (or home) into absolute paths
534 # translate paths relative to root (or home) into absolute paths
535 root = root or encoding.getcwd()
535 root = root or encoding.getcwd()
536 for c in self._tcfg, self._ucfg, self._ocfg:
536 for c in self._tcfg, self._ucfg, self._ocfg:
537 for n, p in c.items(b'paths'):
537 for n, p in c.items(b'paths'):
538 # Ignore sub-options.
538 # Ignore sub-options.
539 if b':' in n:
539 if b':' in n:
540 continue
540 continue
541 if not p:
541 if not p:
542 continue
542 continue
543 if b'%%' in p:
543 if b'%%' in p:
544 s = self.configsource(b'paths', n) or b'none'
544 s = self.configsource(b'paths', n) or b'none'
545 self.warn(
545 self.warn(
546 _(b"(deprecated '%%' in path %s=%s from %s)\n")
546 _(b"(deprecated '%%' in path %s=%s from %s)\n")
547 % (n, p, s)
547 % (n, p, s)
548 )
548 )
549 p = p.replace(b'%%', b'%')
549 p = p.replace(b'%%', b'%')
550 p = util.expandpath(p)
550 p = util.expandpath(p)
551 if not util.hasscheme(p) and not os.path.isabs(p):
551 if not util.hasscheme(p) and not os.path.isabs(p):
552 p = os.path.normpath(os.path.join(root, p))
552 p = os.path.normpath(os.path.join(root, p))
553 c.set(b"paths", n, p)
553 c.set(b"paths", n, p)
554
554
555 if section in (None, b'ui'):
555 if section in (None, b'ui'):
556 # update ui options
556 # update ui options
557 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
557 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
558 self.debugflag = self.configbool(b'ui', b'debug')
558 self.debugflag = self.configbool(b'ui', b'debug')
559 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
559 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
560 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
560 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
561 if self.verbose and self.quiet:
561 if self.verbose and self.quiet:
562 self.quiet = self.verbose = False
562 self.quiet = self.verbose = False
563 self._reportuntrusted = self.debugflag or self.configbool(
563 self._reportuntrusted = self.debugflag or self.configbool(
564 b"ui", b"report_untrusted"
564 b"ui", b"report_untrusted"
565 )
565 )
566 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
566 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
567 self.tracebackflag = self.configbool(b'ui', b'traceback')
567 self.tracebackflag = self.configbool(b'ui', b'traceback')
568 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
568 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
569
569
570 if section in (None, b'trusted'):
570 if section in (None, b'trusted'):
571 # update trust information
571 # update trust information
572 self._trustusers.update(self.configlist(b'trusted', b'users'))
572 self._trustusers.update(self.configlist(b'trusted', b'users'))
573 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
573 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
574
574
575 if section in (None, b'devel', b'ui') and self.debugflag:
575 if section in (None, b'devel', b'ui') and self.debugflag:
576 tracked = set()
576 tracked = set()
577 if self.configbool(b'devel', b'debug.extensions'):
577 if self.configbool(b'devel', b'debug.extensions'):
578 tracked.add(b'extension')
578 tracked.add(b'extension')
579 if tracked:
579 if tracked:
580 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
580 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
581 self.setlogger(b'debug', logger)
581 self.setlogger(b'debug', logger)
582
582
583 def backupconfig(self, section, item):
583 def backupconfig(self, section, item):
584 return (
584 return (
585 self._ocfg.backup(section, item),
585 self._ocfg.backup(section, item),
586 self._tcfg.backup(section, item),
586 self._tcfg.backup(section, item),
587 self._ucfg.backup(section, item),
587 self._ucfg.backup(section, item),
588 )
588 )
589
589
590 def restoreconfig(self, data):
590 def restoreconfig(self, data):
591 self._ocfg.restore(data[0])
591 self._ocfg.restore(data[0])
592 self._tcfg.restore(data[1])
592 self._tcfg.restore(data[1])
593 self._ucfg.restore(data[2])
593 self._ucfg.restore(data[2])
594
594
595 def setconfig(self, section, name, value, source=b''):
595 def setconfig(self, section, name, value, source=b''):
596 for cfg in (self._ocfg, self._tcfg, self._ucfg):
596 for cfg in (self._ocfg, self._tcfg, self._ucfg):
597 cfg.set(section, name, value, source)
597 cfg.set(section, name, value, source)
598 self.fixconfig(section=section)
598 self.fixconfig(section=section)
599 self._maybetweakdefaults()
599 self._maybetweakdefaults()
600
600
601 def _data(self, untrusted):
601 def _data(self, untrusted):
602 return untrusted and self._ucfg or self._tcfg
602 return untrusted and self._ucfg or self._tcfg
603
603
604 def configsource(self, section, name, untrusted=False):
604 def configsource(self, section, name, untrusted=False):
605 return self._data(untrusted).source(section, name)
605 return self._data(untrusted).source(section, name)
606
606
607 def config(self, section, name, default=_unset, untrusted=False):
607 def config(self, section, name, default=_unset, untrusted=False):
608 """return the plain string version of a config"""
608 """return the plain string version of a config"""
609 value = self._config(
609 value = self._config(
610 section, name, default=default, untrusted=untrusted
610 section, name, default=default, untrusted=untrusted
611 )
611 )
612 if value is _unset:
612 if value is _unset:
613 return None
613 return None
614 return value
614 return value
615
615
616 def _config(self, section, name, default=_unset, untrusted=False):
616 def _config(self, section, name, default=_unset, untrusted=False):
617 value = itemdefault = default
617 value = itemdefault = default
618 item = self._knownconfig.get(section, {}).get(name)
618 item = self._knownconfig.get(section, {}).get(name)
619 alternates = [(section, name)]
619 alternates = [(section, name)]
620
620
621 if item is not None:
621 if item is not None:
622 alternates.extend(item.alias)
622 alternates.extend(item.alias)
623 if callable(item.default):
623 if callable(item.default):
624 itemdefault = item.default()
624 itemdefault = item.default()
625 else:
625 else:
626 itemdefault = item.default
626 itemdefault = item.default
627 else:
627 else:
628 msg = b"accessing unregistered config item: '%s.%s'"
628 msg = b"accessing unregistered config item: '%s.%s'"
629 msg %= (section, name)
629 msg %= (section, name)
630 self.develwarn(msg, 2, b'warn-config-unknown')
630 self.develwarn(msg, 2, b'warn-config-unknown')
631
631
632 if default is _unset:
632 if default is _unset:
633 if item is None:
633 if item is None:
634 value = default
634 value = default
635 elif item.default is configitems.dynamicdefault:
635 elif item.default is configitems.dynamicdefault:
636 value = None
636 value = None
637 msg = b"config item requires an explicit default value: '%s.%s'"
637 msg = b"config item requires an explicit default value: '%s.%s'"
638 msg %= (section, name)
638 msg %= (section, name)
639 self.develwarn(msg, 2, b'warn-config-default')
639 self.develwarn(msg, 2, b'warn-config-default')
640 else:
640 else:
641 value = itemdefault
641 value = itemdefault
642 elif (
642 elif (
643 item is not None
643 item is not None
644 and item.default is not configitems.dynamicdefault
644 and item.default is not configitems.dynamicdefault
645 and default != itemdefault
645 and default != itemdefault
646 ):
646 ):
647 msg = (
647 msg = (
648 b"specifying a mismatched default value for a registered "
648 b"specifying a mismatched default value for a registered "
649 b"config item: '%s.%s' '%s'"
649 b"config item: '%s.%s' '%s'"
650 )
650 )
651 msg %= (section, name, pycompat.bytestr(default))
651 msg %= (section, name, pycompat.bytestr(default))
652 self.develwarn(msg, 2, b'warn-config-default')
652 self.develwarn(msg, 2, b'warn-config-default')
653
653
654 for s, n in alternates:
654 for s, n in alternates:
655 candidate = self._data(untrusted).get(s, n, None)
655 candidate = self._data(untrusted).get(s, n, None)
656 if candidate is not None:
656 if candidate is not None:
657 value = candidate
657 value = candidate
658 break
658 break
659
659
660 if self.debugflag and not untrusted and self._reportuntrusted:
660 if self.debugflag and not untrusted and self._reportuntrusted:
661 for s, n in alternates:
661 for s, n in alternates:
662 uvalue = self._ucfg.get(s, n)
662 uvalue = self._ucfg.get(s, n)
663 if uvalue is not None and uvalue != value:
663 if uvalue is not None and uvalue != value:
664 self.debug(
664 self.debug(
665 b"ignoring untrusted configuration option "
665 b"ignoring untrusted configuration option "
666 b"%s.%s = %s\n" % (s, n, uvalue)
666 b"%s.%s = %s\n" % (s, n, uvalue)
667 )
667 )
668 return value
668 return value
669
669
670 def configsuboptions(self, section, name, default=_unset, untrusted=False):
670 def configsuboptions(self, section, name, default=_unset, untrusted=False):
671 """Get a config option and all sub-options.
671 """Get a config option and all sub-options.
672
672
673 Some config options have sub-options that are declared with the
673 Some config options have sub-options that are declared with the
674 format "key:opt = value". This method is used to return the main
674 format "key:opt = value". This method is used to return the main
675 option and all its declared sub-options.
675 option and all its declared sub-options.
676
676
677 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
677 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
678 is a dict of defined sub-options where keys and values are strings.
678 is a dict of defined sub-options where keys and values are strings.
679 """
679 """
680 main = self.config(section, name, default, untrusted=untrusted)
680 main = self.config(section, name, default, untrusted=untrusted)
681 data = self._data(untrusted)
681 data = self._data(untrusted)
682 sub = {}
682 sub = {}
683 prefix = b'%s:' % name
683 prefix = b'%s:' % name
684 for k, v in data.items(section):
684 for k, v in data.items(section):
685 if k.startswith(prefix):
685 if k.startswith(prefix):
686 sub[k[len(prefix) :]] = v
686 sub[k[len(prefix) :]] = v
687
687
688 if self.debugflag and not untrusted and self._reportuntrusted:
688 if self.debugflag and not untrusted and self._reportuntrusted:
689 for k, v in sub.items():
689 for k, v in sub.items():
690 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
690 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
691 if uvalue is not None and uvalue != v:
691 if uvalue is not None and uvalue != v:
692 self.debug(
692 self.debug(
693 b'ignoring untrusted configuration option '
693 b'ignoring untrusted configuration option '
694 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
694 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
695 )
695 )
696
696
697 return main, sub
697 return main, sub
698
698
699 def configpath(self, section, name, default=_unset, untrusted=False):
699 def configpath(self, section, name, default=_unset, untrusted=False):
700 """get a path config item, expanded relative to repo root or config
700 """get a path config item, expanded relative to repo root or config
701 file"""
701 file"""
702 v = self.config(section, name, default, untrusted)
702 v = self.config(section, name, default, untrusted)
703 if v is None:
703 if v is None:
704 return None
704 return None
705 if not os.path.isabs(v) or b"://" not in v:
705 if not os.path.isabs(v) or b"://" not in v:
706 src = self.configsource(section, name, untrusted)
706 src = self.configsource(section, name, untrusted)
707 if b':' in src:
707 if b':' in src:
708 base = os.path.dirname(src.rsplit(b':')[0])
708 base = os.path.dirname(src.rsplit(b':')[0])
709 v = os.path.join(base, os.path.expanduser(v))
709 v = os.path.join(base, os.path.expanduser(v))
710 return v
710 return v
711
711
712 def configbool(self, section, name, default=_unset, untrusted=False):
712 def configbool(self, section, name, default=_unset, untrusted=False):
713 """parse a configuration element as a boolean
713 """parse a configuration element as a boolean
714
714
715 >>> u = ui(); s = b'foo'
715 >>> u = ui(); s = b'foo'
716 >>> u.setconfig(s, b'true', b'yes')
716 >>> u.setconfig(s, b'true', b'yes')
717 >>> u.configbool(s, b'true')
717 >>> u.configbool(s, b'true')
718 True
718 True
719 >>> u.setconfig(s, b'false', b'no')
719 >>> u.setconfig(s, b'false', b'no')
720 >>> u.configbool(s, b'false')
720 >>> u.configbool(s, b'false')
721 False
721 False
722 >>> u.configbool(s, b'unknown')
722 >>> u.configbool(s, b'unknown')
723 False
723 False
724 >>> u.configbool(s, b'unknown', True)
724 >>> u.configbool(s, b'unknown', True)
725 True
725 True
726 >>> u.setconfig(s, b'invalid', b'somevalue')
726 >>> u.setconfig(s, b'invalid', b'somevalue')
727 >>> u.configbool(s, b'invalid')
727 >>> u.configbool(s, b'invalid')
728 Traceback (most recent call last):
728 Traceback (most recent call last):
729 ...
729 ...
730 ConfigError: foo.invalid is not a boolean ('somevalue')
730 ConfigError: foo.invalid is not a boolean ('somevalue')
731 """
731 """
732
732
733 v = self._config(section, name, default, untrusted=untrusted)
733 v = self._config(section, name, default, untrusted=untrusted)
734 if v is None:
734 if v is None:
735 return v
735 return v
736 if v is _unset:
736 if v is _unset:
737 if default is _unset:
737 if default is _unset:
738 return False
738 return False
739 return default
739 return default
740 if isinstance(v, bool):
740 if isinstance(v, bool):
741 return v
741 return v
742 b = stringutil.parsebool(v)
742 b = stringutil.parsebool(v)
743 if b is None:
743 if b is None:
744 raise error.ConfigError(
744 raise error.ConfigError(
745 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
745 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
746 )
746 )
747 return b
747 return b
748
748
749 def configwith(
749 def configwith(
750 self, convert, section, name, default=_unset, desc=None, untrusted=False
750 self, convert, section, name, default=_unset, desc=None, untrusted=False
751 ):
751 ):
752 """parse a configuration element with a conversion function
752 """parse a configuration element with a conversion function
753
753
754 >>> u = ui(); s = b'foo'
754 >>> u = ui(); s = b'foo'
755 >>> u.setconfig(s, b'float1', b'42')
755 >>> u.setconfig(s, b'float1', b'42')
756 >>> u.configwith(float, s, b'float1')
756 >>> u.configwith(float, s, b'float1')
757 42.0
757 42.0
758 >>> u.setconfig(s, b'float2', b'-4.25')
758 >>> u.setconfig(s, b'float2', b'-4.25')
759 >>> u.configwith(float, s, b'float2')
759 >>> u.configwith(float, s, b'float2')
760 -4.25
760 -4.25
761 >>> u.configwith(float, s, b'unknown', 7)
761 >>> u.configwith(float, s, b'unknown', 7)
762 7.0
762 7.0
763 >>> u.setconfig(s, b'invalid', b'somevalue')
763 >>> u.setconfig(s, b'invalid', b'somevalue')
764 >>> u.configwith(float, s, b'invalid')
764 >>> u.configwith(float, s, b'invalid')
765 Traceback (most recent call last):
765 Traceback (most recent call last):
766 ...
766 ...
767 ConfigError: foo.invalid is not a valid float ('somevalue')
767 ConfigError: foo.invalid is not a valid float ('somevalue')
768 >>> u.configwith(float, s, b'invalid', desc=b'womble')
768 >>> u.configwith(float, s, b'invalid', desc=b'womble')
769 Traceback (most recent call last):
769 Traceback (most recent call last):
770 ...
770 ...
771 ConfigError: foo.invalid is not a valid womble ('somevalue')
771 ConfigError: foo.invalid is not a valid womble ('somevalue')
772 """
772 """
773
773
774 v = self.config(section, name, default, untrusted)
774 v = self.config(section, name, default, untrusted)
775 if v is None:
775 if v is None:
776 return v # do not attempt to convert None
776 return v # do not attempt to convert None
777 try:
777 try:
778 return convert(v)
778 return convert(v)
779 except (ValueError, error.ParseError):
779 except (ValueError, error.ParseError):
780 if desc is None:
780 if desc is None:
781 desc = pycompat.sysbytes(convert.__name__)
781 desc = pycompat.sysbytes(convert.__name__)
782 raise error.ConfigError(
782 raise error.ConfigError(
783 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
783 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
784 )
784 )
785
785
786 def configint(self, section, name, default=_unset, untrusted=False):
786 def configint(self, section, name, default=_unset, untrusted=False):
787 """parse a configuration element as an integer
787 """parse a configuration element as an integer
788
788
789 >>> u = ui(); s = b'foo'
789 >>> u = ui(); s = b'foo'
790 >>> u.setconfig(s, b'int1', b'42')
790 >>> u.setconfig(s, b'int1', b'42')
791 >>> u.configint(s, b'int1')
791 >>> u.configint(s, b'int1')
792 42
792 42
793 >>> u.setconfig(s, b'int2', b'-42')
793 >>> u.setconfig(s, b'int2', b'-42')
794 >>> u.configint(s, b'int2')
794 >>> u.configint(s, b'int2')
795 -42
795 -42
796 >>> u.configint(s, b'unknown', 7)
796 >>> u.configint(s, b'unknown', 7)
797 7
797 7
798 >>> u.setconfig(s, b'invalid', b'somevalue')
798 >>> u.setconfig(s, b'invalid', b'somevalue')
799 >>> u.configint(s, b'invalid')
799 >>> u.configint(s, b'invalid')
800 Traceback (most recent call last):
800 Traceback (most recent call last):
801 ...
801 ...
802 ConfigError: foo.invalid is not a valid integer ('somevalue')
802 ConfigError: foo.invalid is not a valid integer ('somevalue')
803 """
803 """
804
804
805 return self.configwith(
805 return self.configwith(
806 int, section, name, default, b'integer', untrusted
806 int, section, name, default, b'integer', untrusted
807 )
807 )
808
808
809 def configbytes(self, section, name, default=_unset, untrusted=False):
809 def configbytes(self, section, name, default=_unset, untrusted=False):
810 """parse a configuration element as a quantity in bytes
810 """parse a configuration element as a quantity in bytes
811
811
812 Units can be specified as b (bytes), k or kb (kilobytes), m or
812 Units can be specified as b (bytes), k or kb (kilobytes), m or
813 mb (megabytes), g or gb (gigabytes).
813 mb (megabytes), g or gb (gigabytes).
814
814
815 >>> u = ui(); s = b'foo'
815 >>> u = ui(); s = b'foo'
816 >>> u.setconfig(s, b'val1', b'42')
816 >>> u.setconfig(s, b'val1', b'42')
817 >>> u.configbytes(s, b'val1')
817 >>> u.configbytes(s, b'val1')
818 42
818 42
819 >>> u.setconfig(s, b'val2', b'42.5 kb')
819 >>> u.setconfig(s, b'val2', b'42.5 kb')
820 >>> u.configbytes(s, b'val2')
820 >>> u.configbytes(s, b'val2')
821 43520
821 43520
822 >>> u.configbytes(s, b'unknown', b'7 MB')
822 >>> u.configbytes(s, b'unknown', b'7 MB')
823 7340032
823 7340032
824 >>> u.setconfig(s, b'invalid', b'somevalue')
824 >>> u.setconfig(s, b'invalid', b'somevalue')
825 >>> u.configbytes(s, b'invalid')
825 >>> u.configbytes(s, b'invalid')
826 Traceback (most recent call last):
826 Traceback (most recent call last):
827 ...
827 ...
828 ConfigError: foo.invalid is not a byte quantity ('somevalue')
828 ConfigError: foo.invalid is not a byte quantity ('somevalue')
829 """
829 """
830
830
831 value = self._config(section, name, default, untrusted)
831 value = self._config(section, name, default, untrusted)
832 if value is _unset:
832 if value is _unset:
833 if default is _unset:
833 if default is _unset:
834 default = 0
834 default = 0
835 value = default
835 value = default
836 if not isinstance(value, bytes):
836 if not isinstance(value, bytes):
837 return value
837 return value
838 try:
838 try:
839 return util.sizetoint(value)
839 return util.sizetoint(value)
840 except error.ParseError:
840 except error.ParseError:
841 raise error.ConfigError(
841 raise error.ConfigError(
842 _(b"%s.%s is not a byte quantity ('%s')")
842 _(b"%s.%s is not a byte quantity ('%s')")
843 % (section, name, value)
843 % (section, name, value)
844 )
844 )
845
845
846 def configlist(self, section, name, default=_unset, untrusted=False):
846 def configlist(self, section, name, default=_unset, untrusted=False):
847 """parse a configuration element as a list of comma/space separated
847 """parse a configuration element as a list of comma/space separated
848 strings
848 strings
849
849
850 >>> u = ui(); s = b'foo'
850 >>> u = ui(); s = b'foo'
851 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
851 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
852 >>> u.configlist(s, b'list1')
852 >>> u.configlist(s, b'list1')
853 ['this', 'is', 'a small', 'test']
853 ['this', 'is', 'a small', 'test']
854 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
854 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
855 >>> u.configlist(s, b'list2')
855 >>> u.configlist(s, b'list2')
856 ['this', 'is', 'a small', 'test']
856 ['this', 'is', 'a small', 'test']
857 """
857 """
858 # default is not always a list
858 # default is not always a list
859 v = self.configwith(
859 v = self.configwith(
860 config.parselist, section, name, default, b'list', untrusted
860 config.parselist, section, name, default, b'list', untrusted
861 )
861 )
862 if isinstance(v, bytes):
862 if isinstance(v, bytes):
863 return config.parselist(v)
863 return config.parselist(v)
864 elif v is None:
864 elif v is None:
865 return []
865 return []
866 return v
866 return v
867
867
868 def configdate(self, section, name, default=_unset, untrusted=False):
868 def configdate(self, section, name, default=_unset, untrusted=False):
869 """parse a configuration element as a tuple of ints
869 """parse a configuration element as a tuple of ints
870
870
871 >>> u = ui(); s = b'foo'
871 >>> u = ui(); s = b'foo'
872 >>> u.setconfig(s, b'date', b'0 0')
872 >>> u.setconfig(s, b'date', b'0 0')
873 >>> u.configdate(s, b'date')
873 >>> u.configdate(s, b'date')
874 (0, 0)
874 (0, 0)
875 """
875 """
876 if self.config(section, name, default, untrusted):
876 if self.config(section, name, default, untrusted):
877 return self.configwith(
877 return self.configwith(
878 dateutil.parsedate, section, name, default, b'date', untrusted
878 dateutil.parsedate, section, name, default, b'date', untrusted
879 )
879 )
880 if default is _unset:
880 if default is _unset:
881 return None
881 return None
882 return default
882 return default
883
883
884 def configdefault(self, section, name):
884 def configdefault(self, section, name):
885 """returns the default value of the config item"""
885 """returns the default value of the config item"""
886 item = self._knownconfig.get(section, {}).get(name)
886 item = self._knownconfig.get(section, {}).get(name)
887 itemdefault = None
887 itemdefault = None
888 if item is not None:
888 if item is not None:
889 if callable(item.default):
889 if callable(item.default):
890 itemdefault = item.default()
890 itemdefault = item.default()
891 else:
891 else:
892 itemdefault = item.default
892 itemdefault = item.default
893 return itemdefault
893 return itemdefault
894
894
895 def hasconfig(self, section, name, untrusted=False):
895 def hasconfig(self, section, name, untrusted=False):
896 return self._data(untrusted).hasitem(section, name)
896 return self._data(untrusted).hasitem(section, name)
897
897
898 def has_section(self, section, untrusted=False):
898 def has_section(self, section, untrusted=False):
899 '''tell whether section exists in config.'''
899 '''tell whether section exists in config.'''
900 return section in self._data(untrusted)
900 return section in self._data(untrusted)
901
901
902 def configitems(self, section, untrusted=False, ignoresub=False):
902 def configitems(self, section, untrusted=False, ignoresub=False):
903 items = self._data(untrusted).items(section)
903 items = self._data(untrusted).items(section)
904 if ignoresub:
904 if ignoresub:
905 items = [i for i in items if b':' not in i[0]]
905 items = [i for i in items if b':' not in i[0]]
906 if self.debugflag and not untrusted and self._reportuntrusted:
906 if self.debugflag and not untrusted and self._reportuntrusted:
907 for k, v in self._ucfg.items(section):
907 for k, v in self._ucfg.items(section):
908 if self._tcfg.get(section, k) != v:
908 if self._tcfg.get(section, k) != v:
909 self.debug(
909 self.debug(
910 b"ignoring untrusted configuration option "
910 b"ignoring untrusted configuration option "
911 b"%s.%s = %s\n" % (section, k, v)
911 b"%s.%s = %s\n" % (section, k, v)
912 )
912 )
913 return items
913 return items
914
914
915 def walkconfig(self, untrusted=False):
915 def walkconfig(self, untrusted=False):
916 cfg = self._data(untrusted)
916 cfg = self._data(untrusted)
917 for section in cfg.sections():
917 for section in cfg.sections():
918 for name, value in self.configitems(section, untrusted):
918 for name, value in self.configitems(section, untrusted):
919 yield section, name, value
919 yield section, name, value
920
920
921 def plain(self, feature=None):
921 def plain(self, feature=None):
922 '''is plain mode active?
922 '''is plain mode active?
923
923
924 Plain mode means that all configuration variables which affect
924 Plain mode means that all configuration variables which affect
925 the behavior and output of Mercurial should be
925 the behavior and output of Mercurial should be
926 ignored. Additionally, the output should be stable,
926 ignored. Additionally, the output should be stable,
927 reproducible and suitable for use in scripts or applications.
927 reproducible and suitable for use in scripts or applications.
928
928
929 The only way to trigger plain mode is by setting either the
929 The only way to trigger plain mode is by setting either the
930 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
930 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
931
931
932 The return value can either be
932 The return value can either be
933 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
933 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
934 - False if feature is disabled by default and not included in HGPLAIN
934 - False if feature is disabled by default and not included in HGPLAIN
935 - True otherwise
935 - True otherwise
936 '''
936 '''
937 if (
937 if (
938 b'HGPLAIN' not in encoding.environ
938 b'HGPLAIN' not in encoding.environ
939 and b'HGPLAINEXCEPT' not in encoding.environ
939 and b'HGPLAINEXCEPT' not in encoding.environ
940 ):
940 ):
941 return False
941 return False
942 exceptions = (
942 exceptions = (
943 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
943 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
944 )
944 )
945 # TODO: add support for HGPLAIN=+feature,-feature syntax
945 # TODO: add support for HGPLAIN=+feature,-feature syntax
946 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
946 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
947 b','
947 b','
948 ):
948 ):
949 exceptions.append(b'strictflags')
949 exceptions.append(b'strictflags')
950 if feature and exceptions:
950 if feature and exceptions:
951 return feature not in exceptions
951 return feature not in exceptions
952 return True
952 return True
953
953
954 def username(self, acceptempty=False):
954 def username(self, acceptempty=False):
955 """Return default username to be used in commits.
955 """Return default username to be used in commits.
956
956
957 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
957 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
958 and stop searching if one of these is set.
958 and stop searching if one of these is set.
959 If not found and acceptempty is True, returns None.
959 If not found and acceptempty is True, returns None.
960 If not found and ui.askusername is True, ask the user, else use
960 If not found and ui.askusername is True, ask the user, else use
961 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
961 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
962 If no username could be found, raise an Abort error.
962 If no username could be found, raise an Abort error.
963 """
963 """
964 user = encoding.environ.get(b"HGUSER")
964 user = encoding.environ.get(b"HGUSER")
965 if user is None:
965 if user is None:
966 user = self.config(b"ui", b"username")
966 user = self.config(b"ui", b"username")
967 if user is not None:
967 if user is not None:
968 user = os.path.expandvars(user)
968 user = os.path.expandvars(user)
969 if user is None:
969 if user is None:
970 user = encoding.environ.get(b"EMAIL")
970 user = encoding.environ.get(b"EMAIL")
971 if user is None and acceptempty:
971 if user is None and acceptempty:
972 return user
972 return user
973 if user is None and self.configbool(b"ui", b"askusername"):
973 if user is None and self.configbool(b"ui", b"askusername"):
974 user = self.prompt(_(b"enter a commit username:"), default=None)
974 user = self.prompt(_(b"enter a commit username:"), default=None)
975 if user is None and not self.interactive():
975 if user is None and not self.interactive():
976 try:
976 try:
977 user = b'%s@%s' % (
977 user = b'%s@%s' % (
978 procutil.getuser(),
978 procutil.getuser(),
979 encoding.strtolocal(socket.getfqdn()),
979 encoding.strtolocal(socket.getfqdn()),
980 )
980 )
981 self.warn(_(b"no username found, using '%s' instead\n") % user)
981 self.warn(_(b"no username found, using '%s' instead\n") % user)
982 except KeyError:
982 except KeyError:
983 pass
983 pass
984 if not user:
984 if not user:
985 raise error.Abort(
985 raise error.Abort(
986 _(b'no username supplied'),
986 _(b'no username supplied'),
987 hint=_(b"use 'hg config --edit' " b'to set your username'),
987 hint=_(b"use 'hg config --edit' " b'to set your username'),
988 )
988 )
989 if b"\n" in user:
989 if b"\n" in user:
990 raise error.Abort(
990 raise error.Abort(
991 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
991 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
992 )
992 )
993 return user
993 return user
994
994
995 def shortuser(self, user):
995 def shortuser(self, user):
996 """Return a short representation of a user name or email address."""
996 """Return a short representation of a user name or email address."""
997 if not self.verbose:
997 if not self.verbose:
998 user = stringutil.shortuser(user)
998 user = stringutil.shortuser(user)
999 return user
999 return user
1000
1000
1001 def expandpath(self, loc, default=None):
1001 def expandpath(self, loc, default=None):
1002 """Return repository location relative to cwd or from [paths]"""
1002 """Return repository location relative to cwd or from [paths]"""
1003 try:
1003 try:
1004 p = self.paths.getpath(loc)
1004 p = self.paths.getpath(loc)
1005 if p:
1005 if p:
1006 return p.rawloc
1006 return p.rawloc
1007 except error.RepoError:
1007 except error.RepoError:
1008 pass
1008 pass
1009
1009
1010 if default:
1010 if default:
1011 try:
1011 try:
1012 p = self.paths.getpath(default)
1012 p = self.paths.getpath(default)
1013 if p:
1013 if p:
1014 return p.rawloc
1014 return p.rawloc
1015 except error.RepoError:
1015 except error.RepoError:
1016 pass
1016 pass
1017
1017
1018 return loc
1018 return loc
1019
1019
1020 @util.propertycache
1020 @util.propertycache
1021 def paths(self):
1021 def paths(self):
1022 return paths(self)
1022 return paths(self)
1023
1023
1024 @property
1024 @property
1025 def fout(self):
1025 def fout(self):
1026 return self._fout
1026 return self._fout
1027
1027
1028 @fout.setter
1028 @fout.setter
1029 def fout(self, f):
1029 def fout(self, f):
1030 self._fout = f
1030 self._fout = f
1031 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1031 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1032
1032
1033 @property
1033 @property
1034 def ferr(self):
1034 def ferr(self):
1035 return self._ferr
1035 return self._ferr
1036
1036
1037 @ferr.setter
1037 @ferr.setter
1038 def ferr(self, f):
1038 def ferr(self, f):
1039 self._ferr = f
1039 self._ferr = f
1040 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1040 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1041
1041
1042 @property
1042 @property
1043 def fin(self):
1043 def fin(self):
1044 return self._fin
1044 return self._fin
1045
1045
1046 @fin.setter
1046 @fin.setter
1047 def fin(self, f):
1047 def fin(self, f):
1048 self._fin = f
1048 self._fin = f
1049
1049
1050 @property
1050 @property
1051 def fmsg(self):
1051 def fmsg(self):
1052 """Stream dedicated for status/error messages; may be None if
1052 """Stream dedicated for status/error messages; may be None if
1053 fout/ferr are used"""
1053 fout/ferr are used"""
1054 return self._fmsg
1054 return self._fmsg
1055
1055
1056 @fmsg.setter
1056 @fmsg.setter
1057 def fmsg(self, f):
1057 def fmsg(self, f):
1058 self._fmsg = f
1058 self._fmsg = f
1059 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1059 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1060
1060
1061 def pushbuffer(self, error=False, subproc=False, labeled=False):
1061 def pushbuffer(self, error=False, subproc=False, labeled=False):
1062 """install a buffer to capture standard output of the ui object
1062 """install a buffer to capture standard output of the ui object
1063
1063
1064 If error is True, the error output will be captured too.
1064 If error is True, the error output will be captured too.
1065
1065
1066 If subproc is True, output from subprocesses (typically hooks) will be
1066 If subproc is True, output from subprocesses (typically hooks) will be
1067 captured too.
1067 captured too.
1068
1068
1069 If labeled is True, any labels associated with buffered
1069 If labeled is True, any labels associated with buffered
1070 output will be handled. By default, this has no effect
1070 output will be handled. By default, this has no effect
1071 on the output returned, but extensions and GUI tools may
1071 on the output returned, but extensions and GUI tools may
1072 handle this argument and returned styled output. If output
1072 handle this argument and returned styled output. If output
1073 is being buffered so it can be captured and parsed or
1073 is being buffered so it can be captured and parsed or
1074 processed, labeled should not be set to True.
1074 processed, labeled should not be set to True.
1075 """
1075 """
1076 self._buffers.append([])
1076 self._buffers.append([])
1077 self._bufferstates.append((error, subproc, labeled))
1077 self._bufferstates.append((error, subproc, labeled))
1078 self._bufferapplylabels = labeled
1078 self._bufferapplylabels = labeled
1079
1079
1080 def popbuffer(self):
1080 def popbuffer(self):
1081 '''pop the last buffer and return the buffered output'''
1081 '''pop the last buffer and return the buffered output'''
1082 self._bufferstates.pop()
1082 self._bufferstates.pop()
1083 if self._bufferstates:
1083 if self._bufferstates:
1084 self._bufferapplylabels = self._bufferstates[-1][2]
1084 self._bufferapplylabels = self._bufferstates[-1][2]
1085 else:
1085 else:
1086 self._bufferapplylabels = None
1086 self._bufferapplylabels = None
1087
1087
1088 return b"".join(self._buffers.pop())
1088 return b"".join(self._buffers.pop())
1089
1089
1090 def _isbuffered(self, dest):
1090 def _isbuffered(self, dest):
1091 if dest is self._fout:
1091 if dest is self._fout:
1092 return bool(self._buffers)
1092 return bool(self._buffers)
1093 if dest is self._ferr:
1093 if dest is self._ferr:
1094 return bool(self._bufferstates and self._bufferstates[-1][0])
1094 return bool(self._bufferstates and self._bufferstates[-1][0])
1095 return False
1095 return False
1096
1096
1097 def canwritewithoutlabels(self):
1097 def canwritewithoutlabels(self):
1098 '''check if write skips the label'''
1098 '''check if write skips the label'''
1099 if self._buffers and not self._bufferapplylabels:
1099 if self._buffers and not self._bufferapplylabels:
1100 return True
1100 return True
1101 return self._colormode is None
1101 return self._colormode is None
1102
1102
1103 def canbatchlabeledwrites(self):
1103 def canbatchlabeledwrites(self):
1104 '''check if write calls with labels are batchable'''
1104 '''check if write calls with labels are batchable'''
1105 # Windows color printing is special, see ``write``.
1105 # Windows color printing is special, see ``write``.
1106 return self._colormode != b'win32'
1106 return self._colormode != b'win32'
1107
1107
1108 def write(self, *args, **opts):
1108 def write(self, *args, **opts):
1109 '''write args to output
1109 '''write args to output
1110
1110
1111 By default, this method simply writes to the buffer or stdout.
1111 By default, this method simply writes to the buffer or stdout.
1112 Color mode can be set on the UI class to have the output decorated
1112 Color mode can be set on the UI class to have the output decorated
1113 with color modifier before being written to stdout.
1113 with color modifier before being written to stdout.
1114
1114
1115 The color used is controlled by an optional keyword argument, "label".
1115 The color used is controlled by an optional keyword argument, "label".
1116 This should be a string containing label names separated by space.
1116 This should be a string containing label names separated by space.
1117 Label names take the form of "topic.type". For example, ui.debug()
1117 Label names take the form of "topic.type". For example, ui.debug()
1118 issues a label of "ui.debug".
1118 issues a label of "ui.debug".
1119
1119
1120 Progress reports via stderr are normally cleared before writing as
1120 Progress reports via stderr are normally cleared before writing as
1121 stdout and stderr go to the same terminal. This can be skipped with
1121 stdout and stderr go to the same terminal. This can be skipped with
1122 the optional keyword argument "keepprogressbar". The progress bar
1122 the optional keyword argument "keepprogressbar". The progress bar
1123 will continue to occupy a partial line on stderr in that case.
1123 will continue to occupy a partial line on stderr in that case.
1124 This functionality is intended when Mercurial acts as data source
1124 This functionality is intended when Mercurial acts as data source
1125 in a pipe.
1125 in a pipe.
1126
1126
1127 When labeling output for a specific command, a label of
1127 When labeling output for a specific command, a label of
1128 "cmdname.type" is recommended. For example, status issues
1128 "cmdname.type" is recommended. For example, status issues
1129 a label of "status.modified" for modified files.
1129 a label of "status.modified" for modified files.
1130 '''
1130 '''
1131 dest = self._fout
1131 dest = self._fout
1132
1132
1133 # inlined _write() for speed
1133 # inlined _write() for speed
1134 if self._buffers:
1134 if self._buffers:
1135 label = opts.get('label', b'')
1135 label = opts.get('label', b'')
1136 if label and self._bufferapplylabels:
1136 if label and self._bufferapplylabels:
1137 self._buffers[-1].extend(self.label(a, label) for a in args)
1137 self._buffers[-1].extend(self.label(a, label) for a in args)
1138 else:
1138 else:
1139 self._buffers[-1].extend(args)
1139 self._buffers[-1].extend(args)
1140 return
1140 return
1141
1141
1142 # inlined _writenobuf() for speed
1142 # inlined _writenobuf() for speed
1143 if not opts.get('keepprogressbar', False):
1143 if not opts.get('keepprogressbar', False):
1144 self._progclear()
1144 self._progclear()
1145 msg = b''.join(args)
1145 msg = b''.join(args)
1146
1146
1147 # opencode timeblockedsection because this is a critical path
1147 # opencode timeblockedsection because this is a critical path
1148 starttime = util.timer()
1148 starttime = util.timer()
1149 try:
1149 try:
1150 if self._colormode == b'win32':
1150 if self._colormode == b'win32':
1151 # windows color printing is its own can of crab, defer to
1151 # windows color printing is its own can of crab, defer to
1152 # the color module and that is it.
1152 # the color module and that is it.
1153 color.win32print(self, dest.write, msg, **opts)
1153 color.win32print(self, dest.write, msg, **opts)
1154 else:
1154 else:
1155 if self._colormode is not None:
1155 if self._colormode is not None:
1156 label = opts.get('label', b'')
1156 label = opts.get('label', b'')
1157 msg = self.label(msg, label)
1157 msg = self.label(msg, label)
1158 dest.write(msg)
1158 dest.write(msg)
1159 except IOError as err:
1159 except IOError as err:
1160 raise error.StdioError(err)
1160 raise error.StdioError(err)
1161 finally:
1161 finally:
1162 self._blockedtimes[b'stdio_blocked'] += (
1162 self._blockedtimes[b'stdio_blocked'] += (
1163 util.timer() - starttime
1163 util.timer() - starttime
1164 ) * 1000
1164 ) * 1000
1165
1165
1166 def write_err(self, *args, **opts):
1166 def write_err(self, *args, **opts):
1167 self._write(self._ferr, *args, **opts)
1167 self._write(self._ferr, *args, **opts)
1168
1168
1169 def _write(self, dest, *args, **opts):
1169 def _write(self, dest, *args, **opts):
1170 # update write() as well if you touch this code
1170 # update write() as well if you touch this code
1171 if self._isbuffered(dest):
1171 if self._isbuffered(dest):
1172 label = opts.get('label', b'')
1172 label = opts.get('label', b'')
1173 if label and self._bufferapplylabels:
1173 if label and self._bufferapplylabels:
1174 self._buffers[-1].extend(self.label(a, label) for a in args)
1174 self._buffers[-1].extend(self.label(a, label) for a in args)
1175 else:
1175 else:
1176 self._buffers[-1].extend(args)
1176 self._buffers[-1].extend(args)
1177 else:
1177 else:
1178 self._writenobuf(dest, *args, **opts)
1178 self._writenobuf(dest, *args, **opts)
1179
1179
1180 def _writenobuf(self, dest, *args, **opts):
1180 def _writenobuf(self, dest, *args, **opts):
1181 # update write() as well if you touch this code
1181 # update write() as well if you touch this code
1182 if not opts.get('keepprogressbar', False):
1182 if not opts.get('keepprogressbar', False):
1183 self._progclear()
1183 self._progclear()
1184 msg = b''.join(args)
1184 msg = b''.join(args)
1185
1185
1186 # opencode timeblockedsection because this is a critical path
1186 # opencode timeblockedsection because this is a critical path
1187 starttime = util.timer()
1187 starttime = util.timer()
1188 try:
1188 try:
1189 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1189 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1190 self._fout.flush()
1190 self._fout.flush()
1191 if getattr(dest, 'structured', False):
1191 if getattr(dest, 'structured', False):
1192 # channel for machine-readable output with metadata, where
1192 # channel for machine-readable output with metadata, where
1193 # no extra colorization is necessary.
1193 # no extra colorization is necessary.
1194 dest.write(msg, **opts)
1194 dest.write(msg, **opts)
1195 elif self._colormode == b'win32':
1195 elif self._colormode == b'win32':
1196 # windows color printing is its own can of crab, defer to
1196 # windows color printing is its own can of crab, defer to
1197 # the color module and that is it.
1197 # the color module and that is it.
1198 color.win32print(self, dest.write, msg, **opts)
1198 color.win32print(self, dest.write, msg, **opts)
1199 else:
1199 else:
1200 if self._colormode is not None:
1200 if self._colormode is not None:
1201 label = opts.get('label', b'')
1201 label = opts.get('label', b'')
1202 msg = self.label(msg, label)
1202 msg = self.label(msg, label)
1203 dest.write(msg)
1203 dest.write(msg)
1204 # stderr may be buffered under win32 when redirected to files,
1204 # stderr may be buffered under win32 when redirected to files,
1205 # including stdout.
1205 # including stdout.
1206 if dest is self._ferr and not getattr(dest, 'closed', False):
1206 if dest is self._ferr and not getattr(dest, 'closed', False):
1207 dest.flush()
1207 dest.flush()
1208 except IOError as err:
1208 except IOError as err:
1209 if dest is self._ferr and err.errno in (
1209 if dest is self._ferr and err.errno in (
1210 errno.EPIPE,
1210 errno.EPIPE,
1211 errno.EIO,
1211 errno.EIO,
1212 errno.EBADF,
1212 errno.EBADF,
1213 ):
1213 ):
1214 # no way to report the error, so ignore it
1214 # no way to report the error, so ignore it
1215 return
1215 return
1216 raise error.StdioError(err)
1216 raise error.StdioError(err)
1217 finally:
1217 finally:
1218 self._blockedtimes[b'stdio_blocked'] += (
1218 self._blockedtimes[b'stdio_blocked'] += (
1219 util.timer() - starttime
1219 util.timer() - starttime
1220 ) * 1000
1220 ) * 1000
1221
1221
1222 def _writemsg(self, dest, *args, **opts):
1222 def _writemsg(self, dest, *args, **opts):
1223 timestamp = self.showtimestamp and opts.get('type') in {
1223 timestamp = self.showtimestamp and opts.get('type') in {
1224 b'debug',
1224 b'debug',
1225 b'error',
1225 b'error',
1226 b'note',
1226 b'note',
1227 b'status',
1227 b'status',
1228 b'warning',
1228 b'warning',
1229 }
1229 }
1230 if timestamp:
1230 if timestamp:
1231 args = (
1231 args = (
1232 b'[%s] '
1232 b'[%s] '
1233 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1233 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1234 ) + args
1234 ) + args
1235 _writemsgwith(self._write, dest, *args, **opts)
1235 _writemsgwith(self._write, dest, *args, **opts)
1236 if timestamp:
1236 if timestamp:
1237 dest.flush()
1237 dest.flush()
1238
1238
1239 def _writemsgnobuf(self, dest, *args, **opts):
1239 def _writemsgnobuf(self, dest, *args, **opts):
1240 _writemsgwith(self._writenobuf, dest, *args, **opts)
1240 _writemsgwith(self._writenobuf, dest, *args, **opts)
1241
1241
1242 def flush(self):
1242 def flush(self):
1243 # opencode timeblockedsection because this is a critical path
1243 # opencode timeblockedsection because this is a critical path
1244 starttime = util.timer()
1244 starttime = util.timer()
1245 try:
1245 try:
1246 try:
1246 try:
1247 self._fout.flush()
1247 self._fout.flush()
1248 except IOError as err:
1248 except IOError as err:
1249 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1249 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1250 raise error.StdioError(err)
1250 raise error.StdioError(err)
1251 finally:
1251 finally:
1252 try:
1252 try:
1253 self._ferr.flush()
1253 self._ferr.flush()
1254 except IOError as err:
1254 except IOError as err:
1255 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1255 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1256 raise error.StdioError(err)
1256 raise error.StdioError(err)
1257 finally:
1257 finally:
1258 self._blockedtimes[b'stdio_blocked'] += (
1258 self._blockedtimes[b'stdio_blocked'] += (
1259 util.timer() - starttime
1259 util.timer() - starttime
1260 ) * 1000
1260 ) * 1000
1261
1261
1262 def _isatty(self, fh):
1262 def _isatty(self, fh):
1263 if self.configbool(b'ui', b'nontty'):
1263 if self.configbool(b'ui', b'nontty'):
1264 return False
1264 return False
1265 return procutil.isatty(fh)
1265 return procutil.isatty(fh)
1266
1266
1267 def protectfinout(self):
1267 def protectfinout(self):
1268 """Duplicate ui streams and redirect original if they are stdio
1268 """Duplicate ui streams and redirect original if they are stdio
1269
1269
1270 Returns (fin, fout) which point to the original ui fds, but may be
1270 Returns (fin, fout) which point to the original ui fds, but may be
1271 copy of them. The returned streams can be considered "owned" in that
1271 copy of them. The returned streams can be considered "owned" in that
1272 print(), exec(), etc. never reach to them.
1272 print(), exec(), etc. never reach to them.
1273 """
1273 """
1274 if self._finoutredirected:
1274 if self._finoutredirected:
1275 # if already redirected, protectstdio() would just create another
1275 # if already redirected, protectstdio() would just create another
1276 # nullfd pair, which is equivalent to returning self._fin/_fout.
1276 # nullfd pair, which is equivalent to returning self._fin/_fout.
1277 return self._fin, self._fout
1277 return self._fin, self._fout
1278 fin, fout = procutil.protectstdio(self._fin, self._fout)
1278 fin, fout = procutil.protectstdio(self._fin, self._fout)
1279 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1279 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1280 return fin, fout
1280 return fin, fout
1281
1281
1282 def restorefinout(self, fin, fout):
1282 def restorefinout(self, fin, fout):
1283 """Restore ui streams from possibly duplicated (fin, fout)"""
1283 """Restore ui streams from possibly duplicated (fin, fout)"""
1284 if (fin, fout) == (self._fin, self._fout):
1284 if (fin, fout) == (self._fin, self._fout):
1285 return
1285 return
1286 procutil.restorestdio(self._fin, self._fout, fin, fout)
1286 procutil.restorestdio(self._fin, self._fout, fin, fout)
1287 # protectfinout() won't create more than one duplicated streams,
1287 # protectfinout() won't create more than one duplicated streams,
1288 # so we can just turn the redirection flag off.
1288 # so we can just turn the redirection flag off.
1289 self._finoutredirected = False
1289 self._finoutredirected = False
1290
1290
1291 @contextlib.contextmanager
1291 @contextlib.contextmanager
1292 def protectedfinout(self):
1292 def protectedfinout(self):
1293 """Run code block with protected standard streams"""
1293 """Run code block with protected standard streams"""
1294 fin, fout = self.protectfinout()
1294 fin, fout = self.protectfinout()
1295 try:
1295 try:
1296 yield fin, fout
1296 yield fin, fout
1297 finally:
1297 finally:
1298 self.restorefinout(fin, fout)
1298 self.restorefinout(fin, fout)
1299
1299
1300 def disablepager(self):
1300 def disablepager(self):
1301 self._disablepager = True
1301 self._disablepager = True
1302
1302
1303 def pager(self, command):
1303 def pager(self, command):
1304 """Start a pager for subsequent command output.
1304 """Start a pager for subsequent command output.
1305
1305
1306 Commands which produce a long stream of output should call
1306 Commands which produce a long stream of output should call
1307 this function to activate the user's preferred pagination
1307 this function to activate the user's preferred pagination
1308 mechanism (which may be no pager). Calling this function
1308 mechanism (which may be no pager). Calling this function
1309 precludes any future use of interactive functionality, such as
1309 precludes any future use of interactive functionality, such as
1310 prompting the user or activating curses.
1310 prompting the user or activating curses.
1311
1311
1312 Args:
1312 Args:
1313 command: The full, non-aliased name of the command. That is, "log"
1313 command: The full, non-aliased name of the command. That is, "log"
1314 not "history, "summary" not "summ", etc.
1314 not "history, "summary" not "summ", etc.
1315 """
1315 """
1316 if self._disablepager or self.pageractive:
1316 if self._disablepager or self.pageractive:
1317 # how pager should do is already determined
1317 # how pager should do is already determined
1318 return
1318 return
1319
1319
1320 if not command.startswith(b'internal-always-') and (
1320 if not command.startswith(b'internal-always-') and (
1321 # explicit --pager=on (= 'internal-always-' prefix) should
1321 # explicit --pager=on (= 'internal-always-' prefix) should
1322 # take precedence over disabling factors below
1322 # take precedence over disabling factors below
1323 command in self.configlist(b'pager', b'ignore')
1323 command in self.configlist(b'pager', b'ignore')
1324 or not self.configbool(b'ui', b'paginate')
1324 or not self.configbool(b'ui', b'paginate')
1325 or not self.configbool(b'pager', b'attend-' + command, True)
1325 or not self.configbool(b'pager', b'attend-' + command, True)
1326 or encoding.environ.get(b'TERM') == b'dumb'
1326 or encoding.environ.get(b'TERM') == b'dumb'
1327 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1327 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1328 # formatted() will need some adjustment.
1328 # formatted() will need some adjustment.
1329 or not self.formatted()
1329 or not self.formatted()
1330 or self.plain()
1330 or self.plain()
1331 or self._buffers
1331 or self._buffers
1332 # TODO: expose debugger-enabled on the UI object
1332 # TODO: expose debugger-enabled on the UI object
1333 or b'--debugger' in pycompat.sysargv
1333 or b'--debugger' in pycompat.sysargv
1334 ):
1334 ):
1335 # We only want to paginate if the ui appears to be
1335 # We only want to paginate if the ui appears to be
1336 # interactive, the user didn't say HGPLAIN or
1336 # interactive, the user didn't say HGPLAIN or
1337 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1337 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1338 return
1338 return
1339
1339
1340 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1340 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1341 if not pagercmd:
1341 if not pagercmd:
1342 return
1342 return
1343
1343
1344 pagerenv = {}
1344 pagerenv = {}
1345 for name, value in rcutil.defaultpagerenv().items():
1345 for name, value in rcutil.defaultpagerenv().items():
1346 if name not in encoding.environ:
1346 if name not in encoding.environ:
1347 pagerenv[name] = value
1347 pagerenv[name] = value
1348
1348
1349 self.debug(
1349 self.debug(
1350 b'starting pager for command %s\n' % stringutil.pprint(command)
1350 b'starting pager for command %s\n' % stringutil.pprint(command)
1351 )
1351 )
1352 self.flush()
1352 self.flush()
1353
1353
1354 wasformatted = self.formatted()
1354 wasformatted = self.formatted()
1355 if util.safehasattr(signal, b"SIGPIPE"):
1355 if util.safehasattr(signal, b"SIGPIPE"):
1356 signal.signal(signal.SIGPIPE, _catchterm)
1356 signal.signal(signal.SIGPIPE, _catchterm)
1357 if self._runpager(pagercmd, pagerenv):
1357 if self._runpager(pagercmd, pagerenv):
1358 self.pageractive = True
1358 self.pageractive = True
1359 # Preserve the formatted-ness of the UI. This is important
1359 # Preserve the formatted-ness of the UI. This is important
1360 # because we mess with stdout, which might confuse
1360 # because we mess with stdout, which might confuse
1361 # auto-detection of things being formatted.
1361 # auto-detection of things being formatted.
1362 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1362 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1363 self.setconfig(b'ui', b'interactive', False, b'pager')
1363 self.setconfig(b'ui', b'interactive', False, b'pager')
1364
1364
1365 # If pagermode differs from color.mode, reconfigure color now that
1365 # If pagermode differs from color.mode, reconfigure color now that
1366 # pageractive is set.
1366 # pageractive is set.
1367 cm = self._colormode
1367 cm = self._colormode
1368 if cm != self.config(b'color', b'pagermode', cm):
1368 if cm != self.config(b'color', b'pagermode', cm):
1369 color.setup(self)
1369 color.setup(self)
1370 else:
1370 else:
1371 # If the pager can't be spawned in dispatch when --pager=on is
1371 # If the pager can't be spawned in dispatch when --pager=on is
1372 # given, don't try again when the command runs, to avoid a duplicate
1372 # given, don't try again when the command runs, to avoid a duplicate
1373 # warning about a missing pager command.
1373 # warning about a missing pager command.
1374 self.disablepager()
1374 self.disablepager()
1375
1375
1376 def _runpager(self, command, env=None):
1376 def _runpager(self, command, env=None):
1377 """Actually start the pager and set up file descriptors.
1377 """Actually start the pager and set up file descriptors.
1378
1378
1379 This is separate in part so that extensions (like chg) can
1379 This is separate in part so that extensions (like chg) can
1380 override how a pager is invoked.
1380 override how a pager is invoked.
1381 """
1381 """
1382 if command == b'cat':
1382 if command == b'cat':
1383 # Save ourselves some work.
1383 # Save ourselves some work.
1384 return False
1384 return False
1385 # If the command doesn't contain any of these characters, we
1385 # If the command doesn't contain any of these characters, we
1386 # assume it's a binary and exec it directly. This means for
1386 # assume it's a binary and exec it directly. This means for
1387 # simple pager command configurations, we can degrade
1387 # simple pager command configurations, we can degrade
1388 # gracefully and tell the user about their broken pager.
1388 # gracefully and tell the user about their broken pager.
1389 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1389 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1390
1390
1391 if pycompat.iswindows and not shell:
1391 if pycompat.iswindows and not shell:
1392 # Window's built-in `more` cannot be invoked with shell=False, but
1392 # Window's built-in `more` cannot be invoked with shell=False, but
1393 # its `more.com` can. Hide this implementation detail from the
1393 # its `more.com` can. Hide this implementation detail from the
1394 # user so we can also get sane bad PAGER behavior. MSYS has
1394 # user so we can also get sane bad PAGER behavior. MSYS has
1395 # `more.exe`, so do a cmd.exe style resolution of the executable to
1395 # `more.exe`, so do a cmd.exe style resolution of the executable to
1396 # determine which one to use.
1396 # determine which one to use.
1397 fullcmd = procutil.findexe(command)
1397 fullcmd = procutil.findexe(command)
1398 if not fullcmd:
1398 if not fullcmd:
1399 self.warn(
1399 self.warn(
1400 _(b"missing pager command '%s', skipping pager\n") % command
1400 _(b"missing pager command '%s', skipping pager\n") % command
1401 )
1401 )
1402 return False
1402 return False
1403
1403
1404 command = fullcmd
1404 command = fullcmd
1405
1405
1406 try:
1406 try:
1407 pager = subprocess.Popen(
1407 pager = subprocess.Popen(
1408 procutil.tonativestr(command),
1408 procutil.tonativestr(command),
1409 shell=shell,
1409 shell=shell,
1410 bufsize=-1,
1410 bufsize=-1,
1411 close_fds=procutil.closefds,
1411 close_fds=procutil.closefds,
1412 stdin=subprocess.PIPE,
1412 stdin=subprocess.PIPE,
1413 stdout=procutil.stdout,
1413 stdout=procutil.stdout,
1414 stderr=procutil.stderr,
1414 stderr=procutil.stderr,
1415 env=procutil.tonativeenv(procutil.shellenviron(env)),
1415 env=procutil.tonativeenv(procutil.shellenviron(env)),
1416 )
1416 )
1417 except OSError as e:
1417 except OSError as e:
1418 if e.errno == errno.ENOENT and not shell:
1418 if e.errno == errno.ENOENT and not shell:
1419 self.warn(
1419 self.warn(
1420 _(b"missing pager command '%s', skipping pager\n") % command
1420 _(b"missing pager command '%s', skipping pager\n") % command
1421 )
1421 )
1422 return False
1422 return False
1423 raise
1423 raise
1424
1424
1425 # back up original file descriptors
1425 # back up original file descriptors
1426 stdoutfd = os.dup(procutil.stdout.fileno())
1426 stdoutfd = os.dup(procutil.stdout.fileno())
1427 stderrfd = os.dup(procutil.stderr.fileno())
1427 stderrfd = os.dup(procutil.stderr.fileno())
1428
1428
1429 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1429 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1430 if self._isatty(procutil.stderr):
1430 if self._isatty(procutil.stderr):
1431 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1431 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1432
1432
1433 @self.atexit
1433 @self.atexit
1434 def killpager():
1434 def killpager():
1435 if util.safehasattr(signal, b"SIGINT"):
1435 if util.safehasattr(signal, b"SIGINT"):
1436 signal.signal(signal.SIGINT, signal.SIG_IGN)
1436 signal.signal(signal.SIGINT, signal.SIG_IGN)
1437 # restore original fds, closing pager.stdin copies in the process
1437 # restore original fds, closing pager.stdin copies in the process
1438 os.dup2(stdoutfd, procutil.stdout.fileno())
1438 os.dup2(stdoutfd, procutil.stdout.fileno())
1439 os.dup2(stderrfd, procutil.stderr.fileno())
1439 os.dup2(stderrfd, procutil.stderr.fileno())
1440 pager.stdin.close()
1440 pager.stdin.close()
1441 pager.wait()
1441 pager.wait()
1442
1442
1443 return True
1443 return True
1444
1444
1445 @property
1445 @property
1446 def _exithandlers(self):
1446 def _exithandlers(self):
1447 return _reqexithandlers
1447 return _reqexithandlers
1448
1448
1449 def atexit(self, func, *args, **kwargs):
1449 def atexit(self, func, *args, **kwargs):
1450 '''register a function to run after dispatching a request
1450 '''register a function to run after dispatching a request
1451
1451
1452 Handlers do not stay registered across request boundaries.'''
1452 Handlers do not stay registered across request boundaries.'''
1453 self._exithandlers.append((func, args, kwargs))
1453 self._exithandlers.append((func, args, kwargs))
1454 return func
1454 return func
1455
1455
1456 def interface(self, feature):
1456 def interface(self, feature):
1457 """what interface to use for interactive console features?
1457 """what interface to use for interactive console features?
1458
1458
1459 The interface is controlled by the value of `ui.interface` but also by
1459 The interface is controlled by the value of `ui.interface` but also by
1460 the value of feature-specific configuration. For example:
1460 the value of feature-specific configuration. For example:
1461
1461
1462 ui.interface.histedit = text
1462 ui.interface.histedit = text
1463 ui.interface.chunkselector = curses
1463 ui.interface.chunkselector = curses
1464
1464
1465 Here the features are "histedit" and "chunkselector".
1465 Here the features are "histedit" and "chunkselector".
1466
1466
1467 The configuration above means that the default interfaces for commands
1467 The configuration above means that the default interfaces for commands
1468 is curses, the interface for histedit is text and the interface for
1468 is curses, the interface for histedit is text and the interface for
1469 selecting chunk is crecord (the best curses interface available).
1469 selecting chunk is crecord (the best curses interface available).
1470
1470
1471 Consider the following example:
1471 Consider the following example:
1472 ui.interface = curses
1472 ui.interface = curses
1473 ui.interface.histedit = text
1473 ui.interface.histedit = text
1474
1474
1475 Then histedit will use the text interface and chunkselector will use
1475 Then histedit will use the text interface and chunkselector will use
1476 the default curses interface (crecord at the moment).
1476 the default curses interface (crecord at the moment).
1477 """
1477 """
1478 alldefaults = frozenset([b"text", b"curses"])
1478 alldefaults = frozenset([b"text", b"curses"])
1479
1479
1480 featureinterfaces = {
1480 featureinterfaces = {
1481 b"chunkselector": [b"text", b"curses",],
1481 b"chunkselector": [b"text", b"curses",],
1482 b"histedit": [b"text", b"curses",],
1482 b"histedit": [b"text", b"curses",],
1483 }
1483 }
1484
1484
1485 # Feature-specific interface
1485 # Feature-specific interface
1486 if feature not in featureinterfaces.keys():
1486 if feature not in featureinterfaces.keys():
1487 # Programming error, not user error
1487 # Programming error, not user error
1488 raise ValueError(b"Unknown feature requested %s" % feature)
1488 raise ValueError(b"Unknown feature requested %s" % feature)
1489
1489
1490 availableinterfaces = frozenset(featureinterfaces[feature])
1490 availableinterfaces = frozenset(featureinterfaces[feature])
1491 if alldefaults > availableinterfaces:
1491 if alldefaults > availableinterfaces:
1492 # Programming error, not user error. We need a use case to
1492 # Programming error, not user error. We need a use case to
1493 # define the right thing to do here.
1493 # define the right thing to do here.
1494 raise ValueError(
1494 raise ValueError(
1495 b"Feature %s does not handle all default interfaces" % feature
1495 b"Feature %s does not handle all default interfaces" % feature
1496 )
1496 )
1497
1497
1498 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1498 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1499 return b"text"
1499 return b"text"
1500
1500
1501 # Default interface for all the features
1501 # Default interface for all the features
1502 defaultinterface = b"text"
1502 defaultinterface = b"text"
1503 i = self.config(b"ui", b"interface")
1503 i = self.config(b"ui", b"interface")
1504 if i in alldefaults:
1504 if i in alldefaults:
1505 defaultinterface = i
1505 defaultinterface = i
1506
1506
1507 choseninterface = defaultinterface
1507 choseninterface = defaultinterface
1508 f = self.config(b"ui", b"interface.%s" % feature)
1508 f = self.config(b"ui", b"interface.%s" % feature)
1509 if f in availableinterfaces:
1509 if f in availableinterfaces:
1510 choseninterface = f
1510 choseninterface = f
1511
1511
1512 if i is not None and defaultinterface != i:
1512 if i is not None and defaultinterface != i:
1513 if f is not None:
1513 if f is not None:
1514 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1514 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1515 else:
1515 else:
1516 self.warn(
1516 self.warn(
1517 _(b"invalid value for ui.interface: %s (using %s)\n")
1517 _(b"invalid value for ui.interface: %s (using %s)\n")
1518 % (i, choseninterface)
1518 % (i, choseninterface)
1519 )
1519 )
1520 if f is not None and choseninterface != f:
1520 if f is not None and choseninterface != f:
1521 self.warn(
1521 self.warn(
1522 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1522 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1523 % (feature, f, choseninterface)
1523 % (feature, f, choseninterface)
1524 )
1524 )
1525
1525
1526 return choseninterface
1526 return choseninterface
1527
1527
1528 def interactive(self):
1528 def interactive(self):
1529 '''is interactive input allowed?
1529 '''is interactive input allowed?
1530
1530
1531 An interactive session is a session where input can be reasonably read
1531 An interactive session is a session where input can be reasonably read
1532 from `sys.stdin'. If this function returns false, any attempt to read
1532 from `sys.stdin'. If this function returns false, any attempt to read
1533 from stdin should fail with an error, unless a sensible default has been
1533 from stdin should fail with an error, unless a sensible default has been
1534 specified.
1534 specified.
1535
1535
1536 Interactiveness is triggered by the value of the `ui.interactive'
1536 Interactiveness is triggered by the value of the `ui.interactive'
1537 configuration variable or - if it is unset - when `sys.stdin' points
1537 configuration variable or - if it is unset - when `sys.stdin' points
1538 to a terminal device.
1538 to a terminal device.
1539
1539
1540 This function refers to input only; for output, see `ui.formatted()'.
1540 This function refers to input only; for output, see `ui.formatted()'.
1541 '''
1541 '''
1542 i = self.configbool(b"ui", b"interactive")
1542 i = self.configbool(b"ui", b"interactive")
1543 if i is None:
1543 if i is None:
1544 # some environments replace stdin without implementing isatty
1544 # some environments replace stdin without implementing isatty
1545 # usually those are non-interactive
1545 # usually those are non-interactive
1546 return self._isatty(self._fin)
1546 return self._isatty(self._fin)
1547
1547
1548 return i
1548 return i
1549
1549
1550 def termwidth(self):
1550 def termwidth(self):
1551 '''how wide is the terminal in columns?
1551 '''how wide is the terminal in columns?
1552 '''
1552 '''
1553 if b'COLUMNS' in encoding.environ:
1553 if b'COLUMNS' in encoding.environ:
1554 try:
1554 try:
1555 return int(encoding.environ[b'COLUMNS'])
1555 return int(encoding.environ[b'COLUMNS'])
1556 except ValueError:
1556 except ValueError:
1557 pass
1557 pass
1558 return scmutil.termsize(self)[0]
1558 return scmutil.termsize(self)[0]
1559
1559
1560 def formatted(self):
1560 def formatted(self):
1561 '''should formatted output be used?
1561 '''should formatted output be used?
1562
1562
1563 It is often desirable to format the output to suite the output medium.
1563 It is often desirable to format the output to suite the output medium.
1564 Examples of this are truncating long lines or colorizing messages.
1564 Examples of this are truncating long lines or colorizing messages.
1565 However, this is not often not desirable when piping output into other
1565 However, this is not often not desirable when piping output into other
1566 utilities, e.g. `grep'.
1566 utilities, e.g. `grep'.
1567
1567
1568 Formatted output is triggered by the value of the `ui.formatted'
1568 Formatted output is triggered by the value of the `ui.formatted'
1569 configuration variable or - if it is unset - when `sys.stdout' points
1569 configuration variable or - if it is unset - when `sys.stdout' points
1570 to a terminal device. Please note that `ui.formatted' should be
1570 to a terminal device. Please note that `ui.formatted' should be
1571 considered an implementation detail; it is not intended for use outside
1571 considered an implementation detail; it is not intended for use outside
1572 Mercurial or its extensions.
1572 Mercurial or its extensions.
1573
1573
1574 This function refers to output only; for input, see `ui.interactive()'.
1574 This function refers to output only; for input, see `ui.interactive()'.
1575 This function always returns false when in plain mode, see `ui.plain()'.
1575 This function always returns false when in plain mode, see `ui.plain()'.
1576 '''
1576 '''
1577 if self.plain():
1577 if self.plain():
1578 return False
1578 return False
1579
1579
1580 i = self.configbool(b"ui", b"formatted")
1580 i = self.configbool(b"ui", b"formatted")
1581 if i is None:
1581 if i is None:
1582 # some environments replace stdout without implementing isatty
1582 # some environments replace stdout without implementing isatty
1583 # usually those are non-interactive
1583 # usually those are non-interactive
1584 return self._isatty(self._fout)
1584 return self._isatty(self._fout)
1585
1585
1586 return i
1586 return i
1587
1587
1588 def _readline(self, prompt=b' ', promptopts=None):
1588 def _readline(self, prompt=b' ', promptopts=None):
1589 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1589 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1590 # because they have to be text streams with *no buffering*. Instead,
1590 # because they have to be text streams with *no buffering*. Instead,
1591 # we use rawinput() only if call_readline() will be invoked by
1591 # we use rawinput() only if call_readline() will be invoked by
1592 # PyOS_Readline(), so no I/O will be made at Python layer.
1592 # PyOS_Readline(), so no I/O will be made at Python layer.
1593 usereadline = (
1593 usereadline = (
1594 self._isatty(self._fin)
1594 self._isatty(self._fin)
1595 and self._isatty(self._fout)
1595 and self._isatty(self._fout)
1596 and procutil.isstdin(self._fin)
1596 and procutil.isstdin(self._fin)
1597 and procutil.isstdout(self._fout)
1597 and procutil.isstdout(self._fout)
1598 )
1598 )
1599 if usereadline:
1599 if usereadline:
1600 try:
1600 try:
1601 # magically add command line editing support, where
1601 # magically add command line editing support, where
1602 # available
1602 # available
1603 import readline
1603 import readline
1604
1604
1605 # force demandimport to really load the module
1605 # force demandimport to really load the module
1606 readline.read_history_file
1606 readline.read_history_file
1607 # windows sometimes raises something other than ImportError
1607 # windows sometimes raises something other than ImportError
1608 except Exception:
1608 except Exception:
1609 usereadline = False
1609 usereadline = False
1610
1610
1611 if self._colormode == b'win32' or not usereadline:
1611 if self._colormode == b'win32' or not usereadline:
1612 if not promptopts:
1612 if not promptopts:
1613 promptopts = {}
1613 promptopts = {}
1614 self._writemsgnobuf(
1614 self._writemsgnobuf(
1615 self._fmsgout, prompt, type=b'prompt', **promptopts
1615 self._fmsgout, prompt, type=b'prompt', **promptopts
1616 )
1616 )
1617 self.flush()
1617 self.flush()
1618 prompt = b' '
1618 prompt = b' '
1619 else:
1619 else:
1620 prompt = self.label(prompt, b'ui.prompt') + b' '
1620 prompt = self.label(prompt, b'ui.prompt') + b' '
1621
1621
1622 # prompt ' ' must exist; otherwise readline may delete entire line
1622 # prompt ' ' must exist; otherwise readline may delete entire line
1623 # - http://bugs.python.org/issue12833
1623 # - http://bugs.python.org/issue12833
1624 with self.timeblockedsection(b'stdio'):
1624 with self.timeblockedsection(b'stdio'):
1625 if usereadline:
1625 if usereadline:
1626 self.flush()
1626 self.flush()
1627 prompt = encoding.strfromlocal(prompt)
1627 prompt = encoding.strfromlocal(prompt)
1628 line = encoding.strtolocal(pycompat.rawinput(prompt))
1628 line = encoding.strtolocal(pycompat.rawinput(prompt))
1629 # When stdin is in binary mode on Windows, it can cause
1629 # When stdin is in binary mode on Windows, it can cause
1630 # raw_input() to emit an extra trailing carriage return
1630 # raw_input() to emit an extra trailing carriage return
1631 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1631 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1632 line = line[:-1]
1632 line = line[:-1]
1633 else:
1633 else:
1634 self._fout.write(pycompat.bytestr(prompt))
1634 self._fout.write(pycompat.bytestr(prompt))
1635 self._fout.flush()
1635 self._fout.flush()
1636 line = self._fin.readline()
1636 line = self._fin.readline()
1637 if not line:
1637 if not line:
1638 raise EOFError
1638 raise EOFError
1639 line = line.rstrip(pycompat.oslinesep)
1639 line = line.rstrip(pycompat.oslinesep)
1640
1640
1641 return line
1641 return line
1642
1642
1643 def prompt(self, msg, default=b"y"):
1643 def prompt(self, msg, default=b"y"):
1644 """Prompt user with msg, read response.
1644 """Prompt user with msg, read response.
1645 If ui is not interactive, the default is returned.
1645 If ui is not interactive, the default is returned.
1646 """
1646 """
1647 return self._prompt(msg, default=default)
1647 return self._prompt(msg, default=default)
1648
1648
1649 def _prompt(self, msg, **opts):
1649 def _prompt(self, msg, **opts):
1650 default = opts['default']
1650 default = opts['default']
1651 if not self.interactive():
1651 if not self.interactive():
1652 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1652 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1653 self._writemsg(
1653 self._writemsg(
1654 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1654 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1655 )
1655 )
1656 return default
1656 return default
1657 try:
1657 try:
1658 r = self._readline(prompt=msg, promptopts=opts)
1658 r = self._readline(prompt=msg, promptopts=opts)
1659 if not r:
1659 if not r:
1660 r = default
1660 r = default
1661 if self.configbool(b'ui', b'promptecho'):
1661 if self.configbool(b'ui', b'promptecho'):
1662 self._writemsg(
1662 self._writemsg(
1663 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1663 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1664 )
1664 )
1665 return r
1665 return r
1666 except EOFError:
1666 except EOFError:
1667 raise error.ResponseExpected()
1667 raise error.ResponseExpected()
1668
1668
1669 @staticmethod
1669 @staticmethod
1670 def extractchoices(prompt):
1670 def extractchoices(prompt):
1671 """Extract prompt message and list of choices from specified prompt.
1671 """Extract prompt message and list of choices from specified prompt.
1672
1672
1673 This returns tuple "(message, choices)", and "choices" is the
1673 This returns tuple "(message, choices)", and "choices" is the
1674 list of tuple "(response character, text without &)".
1674 list of tuple "(response character, text without &)".
1675
1675
1676 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1676 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1677 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1677 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1678 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1678 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1679 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1679 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1680 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1680 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1681 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1681 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1682 """
1682 """
1683
1683
1684 # Sadly, the prompt string may have been built with a filename
1684 # Sadly, the prompt string may have been built with a filename
1685 # containing "$$" so let's try to find the first valid-looking
1685 # containing "$$" so let's try to find the first valid-looking
1686 # prompt to start parsing. Sadly, we also can't rely on
1686 # prompt to start parsing. Sadly, we also can't rely on
1687 # choices containing spaces, ASCII, or basically anything
1687 # choices containing spaces, ASCII, or basically anything
1688 # except an ampersand followed by a character.
1688 # except an ampersand followed by a character.
1689 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1689 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1690 msg = m.group(1)
1690 msg = m.group(1)
1691 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1691 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1692
1692
1693 def choicetuple(s):
1693 def choicetuple(s):
1694 ampidx = s.index(b'&')
1694 ampidx = s.index(b'&')
1695 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1695 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1696
1696
1697 return (msg, [choicetuple(s) for s in choices])
1697 return (msg, [choicetuple(s) for s in choices])
1698
1698
1699 def promptchoice(self, prompt, default=0):
1699 def promptchoice(self, prompt, default=0):
1700 """Prompt user with a message, read response, and ensure it matches
1700 """Prompt user with a message, read response, and ensure it matches
1701 one of the provided choices. The prompt is formatted as follows:
1701 one of the provided choices. The prompt is formatted as follows:
1702
1702
1703 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1703 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1704
1704
1705 The index of the choice is returned. Responses are case
1705 The index of the choice is returned. Responses are case
1706 insensitive. If ui is not interactive, the default is
1706 insensitive. If ui is not interactive, the default is
1707 returned.
1707 returned.
1708 """
1708 """
1709
1709
1710 msg, choices = self.extractchoices(prompt)
1710 msg, choices = self.extractchoices(prompt)
1711 resps = [r for r, t in choices]
1711 resps = [r for r, t in choices]
1712 while True:
1712 while True:
1713 r = self._prompt(msg, default=resps[default], choices=choices)
1713 r = self._prompt(msg, default=resps[default], choices=choices)
1714 if r.lower() in resps:
1714 if r.lower() in resps:
1715 return resps.index(r.lower())
1715 return resps.index(r.lower())
1716 # TODO: shouldn't it be a warning?
1716 # TODO: shouldn't it be a warning?
1717 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1717 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1718
1718
1719 def getpass(self, prompt=None, default=None):
1719 def getpass(self, prompt=None, default=None):
1720 if not self.interactive():
1720 if not self.interactive():
1721 return default
1721 return default
1722 try:
1722 try:
1723 self._writemsg(
1723 self._writemsg(
1724 self._fmsgerr,
1724 self._fmsgerr,
1725 prompt or _(b'password: '),
1725 prompt or _(b'password: '),
1726 type=b'prompt',
1726 type=b'prompt',
1727 password=True,
1727 password=True,
1728 )
1728 )
1729 # disable getpass() only if explicitly specified. it's still valid
1729 # disable getpass() only if explicitly specified. it's still valid
1730 # to interact with tty even if fin is not a tty.
1730 # to interact with tty even if fin is not a tty.
1731 with self.timeblockedsection(b'stdio'):
1731 with self.timeblockedsection(b'stdio'):
1732 if self.configbool(b'ui', b'nontty'):
1732 if self.configbool(b'ui', b'nontty'):
1733 l = self._fin.readline()
1733 l = self._fin.readline()
1734 if not l:
1734 if not l:
1735 raise EOFError
1735 raise EOFError
1736 return l.rstrip(b'\n')
1736 return l.rstrip(b'\n')
1737 else:
1737 else:
1738 return getpass.getpass('')
1738 return encoding.strtolocal(getpass.getpass(''))
1739 except EOFError:
1739 except EOFError:
1740 raise error.ResponseExpected()
1740 raise error.ResponseExpected()
1741
1741
1742 def status(self, *msg, **opts):
1742 def status(self, *msg, **opts):
1743 '''write status message to output (if ui.quiet is False)
1743 '''write status message to output (if ui.quiet is False)
1744
1744
1745 This adds an output label of "ui.status".
1745 This adds an output label of "ui.status".
1746 '''
1746 '''
1747 if not self.quiet:
1747 if not self.quiet:
1748 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1748 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1749
1749
1750 def warn(self, *msg, **opts):
1750 def warn(self, *msg, **opts):
1751 '''write warning message to output (stderr)
1751 '''write warning message to output (stderr)
1752
1752
1753 This adds an output label of "ui.warning".
1753 This adds an output label of "ui.warning".
1754 '''
1754 '''
1755 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1755 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1756
1756
1757 def error(self, *msg, **opts):
1757 def error(self, *msg, **opts):
1758 '''write error message to output (stderr)
1758 '''write error message to output (stderr)
1759
1759
1760 This adds an output label of "ui.error".
1760 This adds an output label of "ui.error".
1761 '''
1761 '''
1762 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1762 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1763
1763
1764 def note(self, *msg, **opts):
1764 def note(self, *msg, **opts):
1765 '''write note to output (if ui.verbose is True)
1765 '''write note to output (if ui.verbose is True)
1766
1766
1767 This adds an output label of "ui.note".
1767 This adds an output label of "ui.note".
1768 '''
1768 '''
1769 if self.verbose:
1769 if self.verbose:
1770 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1770 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1771
1771
1772 def debug(self, *msg, **opts):
1772 def debug(self, *msg, **opts):
1773 '''write debug message to output (if ui.debugflag is True)
1773 '''write debug message to output (if ui.debugflag is True)
1774
1774
1775 This adds an output label of "ui.debug".
1775 This adds an output label of "ui.debug".
1776 '''
1776 '''
1777 if self.debugflag:
1777 if self.debugflag:
1778 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1778 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1779 self.log(b'debug', b'%s', b''.join(msg))
1779 self.log(b'debug', b'%s', b''.join(msg))
1780
1780
1781 # Aliases to defeat check-code.
1781 # Aliases to defeat check-code.
1782 statusnoi18n = status
1782 statusnoi18n = status
1783 notenoi18n = note
1783 notenoi18n = note
1784 warnnoi18n = warn
1784 warnnoi18n = warn
1785 writenoi18n = write
1785 writenoi18n = write
1786
1786
1787 def edit(
1787 def edit(
1788 self,
1788 self,
1789 text,
1789 text,
1790 user,
1790 user,
1791 extra=None,
1791 extra=None,
1792 editform=None,
1792 editform=None,
1793 pending=None,
1793 pending=None,
1794 repopath=None,
1794 repopath=None,
1795 action=None,
1795 action=None,
1796 ):
1796 ):
1797 if action is None:
1797 if action is None:
1798 self.develwarn(
1798 self.develwarn(
1799 b'action is None but will soon be a required '
1799 b'action is None but will soon be a required '
1800 b'parameter to ui.edit()'
1800 b'parameter to ui.edit()'
1801 )
1801 )
1802 extra_defaults = {
1802 extra_defaults = {
1803 b'prefix': b'editor',
1803 b'prefix': b'editor',
1804 b'suffix': b'.txt',
1804 b'suffix': b'.txt',
1805 }
1805 }
1806 if extra is not None:
1806 if extra is not None:
1807 if extra.get(b'suffix') is not None:
1807 if extra.get(b'suffix') is not None:
1808 self.develwarn(
1808 self.develwarn(
1809 b'extra.suffix is not None but will soon be '
1809 b'extra.suffix is not None but will soon be '
1810 b'ignored by ui.edit()'
1810 b'ignored by ui.edit()'
1811 )
1811 )
1812 extra_defaults.update(extra)
1812 extra_defaults.update(extra)
1813 extra = extra_defaults
1813 extra = extra_defaults
1814
1814
1815 if action == b'diff':
1815 if action == b'diff':
1816 suffix = b'.diff'
1816 suffix = b'.diff'
1817 elif action:
1817 elif action:
1818 suffix = b'.%s.hg.txt' % action
1818 suffix = b'.%s.hg.txt' % action
1819 else:
1819 else:
1820 suffix = extra[b'suffix']
1820 suffix = extra[b'suffix']
1821
1821
1822 rdir = None
1822 rdir = None
1823 if self.configbool(b'experimental', b'editortmpinhg'):
1823 if self.configbool(b'experimental', b'editortmpinhg'):
1824 rdir = repopath
1824 rdir = repopath
1825 (fd, name) = pycompat.mkstemp(
1825 (fd, name) = pycompat.mkstemp(
1826 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1826 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1827 )
1827 )
1828 try:
1828 try:
1829 with os.fdopen(fd, 'wb') as f:
1829 with os.fdopen(fd, 'wb') as f:
1830 f.write(util.tonativeeol(text))
1830 f.write(util.tonativeeol(text))
1831
1831
1832 environ = {b'HGUSER': user}
1832 environ = {b'HGUSER': user}
1833 if b'transplant_source' in extra:
1833 if b'transplant_source' in extra:
1834 environ.update(
1834 environ.update(
1835 {b'HGREVISION': hex(extra[b'transplant_source'])}
1835 {b'HGREVISION': hex(extra[b'transplant_source'])}
1836 )
1836 )
1837 for label in (b'intermediate-source', b'source', b'rebase_source'):
1837 for label in (b'intermediate-source', b'source', b'rebase_source'):
1838 if label in extra:
1838 if label in extra:
1839 environ.update({b'HGREVISION': extra[label]})
1839 environ.update({b'HGREVISION': extra[label]})
1840 break
1840 break
1841 if editform:
1841 if editform:
1842 environ.update({b'HGEDITFORM': editform})
1842 environ.update({b'HGEDITFORM': editform})
1843 if pending:
1843 if pending:
1844 environ.update({b'HG_PENDING': pending})
1844 environ.update({b'HG_PENDING': pending})
1845
1845
1846 editor = self.geteditor()
1846 editor = self.geteditor()
1847
1847
1848 self.system(
1848 self.system(
1849 b"%s \"%s\"" % (editor, name),
1849 b"%s \"%s\"" % (editor, name),
1850 environ=environ,
1850 environ=environ,
1851 onerr=error.Abort,
1851 onerr=error.Abort,
1852 errprefix=_(b"edit failed"),
1852 errprefix=_(b"edit failed"),
1853 blockedtag=b'editor',
1853 blockedtag=b'editor',
1854 )
1854 )
1855
1855
1856 with open(name, 'rb') as f:
1856 with open(name, 'rb') as f:
1857 t = util.fromnativeeol(f.read())
1857 t = util.fromnativeeol(f.read())
1858 finally:
1858 finally:
1859 os.unlink(name)
1859 os.unlink(name)
1860
1860
1861 return t
1861 return t
1862
1862
1863 def system(
1863 def system(
1864 self,
1864 self,
1865 cmd,
1865 cmd,
1866 environ=None,
1866 environ=None,
1867 cwd=None,
1867 cwd=None,
1868 onerr=None,
1868 onerr=None,
1869 errprefix=None,
1869 errprefix=None,
1870 blockedtag=None,
1870 blockedtag=None,
1871 ):
1871 ):
1872 '''execute shell command with appropriate output stream. command
1872 '''execute shell command with appropriate output stream. command
1873 output will be redirected if fout is not stdout.
1873 output will be redirected if fout is not stdout.
1874
1874
1875 if command fails and onerr is None, return status, else raise onerr
1875 if command fails and onerr is None, return status, else raise onerr
1876 object as exception.
1876 object as exception.
1877 '''
1877 '''
1878 if blockedtag is None:
1878 if blockedtag is None:
1879 # Long cmds tend to be because of an absolute path on cmd. Keep
1879 # Long cmds tend to be because of an absolute path on cmd. Keep
1880 # the tail end instead
1880 # the tail end instead
1881 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1881 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1882 blockedtag = b'unknown_system_' + cmdsuffix
1882 blockedtag = b'unknown_system_' + cmdsuffix
1883 out = self._fout
1883 out = self._fout
1884 if any(s[1] for s in self._bufferstates):
1884 if any(s[1] for s in self._bufferstates):
1885 out = self
1885 out = self
1886 with self.timeblockedsection(blockedtag):
1886 with self.timeblockedsection(blockedtag):
1887 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1887 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1888 if rc and onerr:
1888 if rc and onerr:
1889 errmsg = b'%s %s' % (
1889 errmsg = b'%s %s' % (
1890 procutil.shellsplit(cmd)[0],
1890 procutil.shellsplit(cmd)[0],
1891 procutil.explainexit(rc),
1891 procutil.explainexit(rc),
1892 )
1892 )
1893 if errprefix:
1893 if errprefix:
1894 errmsg = b'%s: %s' % (errprefix, errmsg)
1894 errmsg = b'%s: %s' % (errprefix, errmsg)
1895 raise onerr(errmsg)
1895 raise onerr(errmsg)
1896 return rc
1896 return rc
1897
1897
1898 def _runsystem(self, cmd, environ, cwd, out):
1898 def _runsystem(self, cmd, environ, cwd, out):
1899 """actually execute the given shell command (can be overridden by
1899 """actually execute the given shell command (can be overridden by
1900 extensions like chg)"""
1900 extensions like chg)"""
1901 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1901 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1902
1902
1903 def traceback(self, exc=None, force=False):
1903 def traceback(self, exc=None, force=False):
1904 '''print exception traceback if traceback printing enabled or forced.
1904 '''print exception traceback if traceback printing enabled or forced.
1905 only to call in exception handler. returns true if traceback
1905 only to call in exception handler. returns true if traceback
1906 printed.'''
1906 printed.'''
1907 if self.tracebackflag or force:
1907 if self.tracebackflag or force:
1908 if exc is None:
1908 if exc is None:
1909 exc = sys.exc_info()
1909 exc = sys.exc_info()
1910 cause = getattr(exc[1], 'cause', None)
1910 cause = getattr(exc[1], 'cause', None)
1911
1911
1912 if cause is not None:
1912 if cause is not None:
1913 causetb = traceback.format_tb(cause[2])
1913 causetb = traceback.format_tb(cause[2])
1914 exctb = traceback.format_tb(exc[2])
1914 exctb = traceback.format_tb(exc[2])
1915 exconly = traceback.format_exception_only(cause[0], cause[1])
1915 exconly = traceback.format_exception_only(cause[0], cause[1])
1916
1916
1917 # exclude frame where 'exc' was chained and rethrown from exctb
1917 # exclude frame where 'exc' was chained and rethrown from exctb
1918 self.write_err(
1918 self.write_err(
1919 b'Traceback (most recent call last):\n',
1919 b'Traceback (most recent call last):\n',
1920 encoding.strtolocal(''.join(exctb[:-1])),
1920 encoding.strtolocal(''.join(exctb[:-1])),
1921 encoding.strtolocal(''.join(causetb)),
1921 encoding.strtolocal(''.join(causetb)),
1922 encoding.strtolocal(''.join(exconly)),
1922 encoding.strtolocal(''.join(exconly)),
1923 )
1923 )
1924 else:
1924 else:
1925 output = traceback.format_exception(exc[0], exc[1], exc[2])
1925 output = traceback.format_exception(exc[0], exc[1], exc[2])
1926 self.write_err(encoding.strtolocal(''.join(output)))
1926 self.write_err(encoding.strtolocal(''.join(output)))
1927 return self.tracebackflag or force
1927 return self.tracebackflag or force
1928
1928
1929 def geteditor(self):
1929 def geteditor(self):
1930 '''return editor to use'''
1930 '''return editor to use'''
1931 if pycompat.sysplatform == b'plan9':
1931 if pycompat.sysplatform == b'plan9':
1932 # vi is the MIPS instruction simulator on Plan 9. We
1932 # vi is the MIPS instruction simulator on Plan 9. We
1933 # instead default to E to plumb commit messages to
1933 # instead default to E to plumb commit messages to
1934 # avoid confusion.
1934 # avoid confusion.
1935 editor = b'E'
1935 editor = b'E'
1936 elif pycompat.isdarwin:
1936 elif pycompat.isdarwin:
1937 # vi on darwin is POSIX compatible to a fault, and that includes
1937 # vi on darwin is POSIX compatible to a fault, and that includes
1938 # exiting non-zero if you make any mistake when running an ex
1938 # exiting non-zero if you make any mistake when running an ex
1939 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1939 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
1940 # while s/vi/vim/ doesn't.
1940 # while s/vi/vim/ doesn't.
1941 editor = b'vim'
1941 editor = b'vim'
1942 else:
1942 else:
1943 editor = b'vi'
1943 editor = b'vi'
1944 return encoding.environ.get(b"HGEDITOR") or self.config(
1944 return encoding.environ.get(b"HGEDITOR") or self.config(
1945 b"ui", b"editor", editor
1945 b"ui", b"editor", editor
1946 )
1946 )
1947
1947
1948 @util.propertycache
1948 @util.propertycache
1949 def _progbar(self):
1949 def _progbar(self):
1950 """setup the progbar singleton to the ui object"""
1950 """setup the progbar singleton to the ui object"""
1951 if (
1951 if (
1952 self.quiet
1952 self.quiet
1953 or self.debugflag
1953 or self.debugflag
1954 or self.configbool(b'progress', b'disable')
1954 or self.configbool(b'progress', b'disable')
1955 or not progress.shouldprint(self)
1955 or not progress.shouldprint(self)
1956 ):
1956 ):
1957 return None
1957 return None
1958 return getprogbar(self)
1958 return getprogbar(self)
1959
1959
1960 def _progclear(self):
1960 def _progclear(self):
1961 """clear progress bar output if any. use it before any output"""
1961 """clear progress bar output if any. use it before any output"""
1962 if not haveprogbar(): # nothing loaded yet
1962 if not haveprogbar(): # nothing loaded yet
1963 return
1963 return
1964 if self._progbar is not None and self._progbar.printed:
1964 if self._progbar is not None and self._progbar.printed:
1965 self._progbar.clear()
1965 self._progbar.clear()
1966
1966
1967 def makeprogress(self, topic, unit=b"", total=None):
1967 def makeprogress(self, topic, unit=b"", total=None):
1968 """Create a progress helper for the specified topic"""
1968 """Create a progress helper for the specified topic"""
1969 if getattr(self._fmsgerr, 'structured', False):
1969 if getattr(self._fmsgerr, 'structured', False):
1970 # channel for machine-readable output with metadata, just send
1970 # channel for machine-readable output with metadata, just send
1971 # raw information
1971 # raw information
1972 # TODO: consider porting some useful information (e.g. estimated
1972 # TODO: consider porting some useful information (e.g. estimated
1973 # time) from progbar. we might want to support update delay to
1973 # time) from progbar. we might want to support update delay to
1974 # reduce the cost of transferring progress messages.
1974 # reduce the cost of transferring progress messages.
1975 def updatebar(topic, pos, item, unit, total):
1975 def updatebar(topic, pos, item, unit, total):
1976 self._fmsgerr.write(
1976 self._fmsgerr.write(
1977 None,
1977 None,
1978 type=b'progress',
1978 type=b'progress',
1979 topic=topic,
1979 topic=topic,
1980 pos=pos,
1980 pos=pos,
1981 item=item,
1981 item=item,
1982 unit=unit,
1982 unit=unit,
1983 total=total,
1983 total=total,
1984 )
1984 )
1985
1985
1986 elif self._progbar is not None:
1986 elif self._progbar is not None:
1987 updatebar = self._progbar.progress
1987 updatebar = self._progbar.progress
1988 else:
1988 else:
1989
1989
1990 def updatebar(topic, pos, item, unit, total):
1990 def updatebar(topic, pos, item, unit, total):
1991 pass
1991 pass
1992
1992
1993 return scmutil.progress(self, updatebar, topic, unit, total)
1993 return scmutil.progress(self, updatebar, topic, unit, total)
1994
1994
1995 def getlogger(self, name):
1995 def getlogger(self, name):
1996 """Returns a logger of the given name; or None if not registered"""
1996 """Returns a logger of the given name; or None if not registered"""
1997 return self._loggers.get(name)
1997 return self._loggers.get(name)
1998
1998
1999 def setlogger(self, name, logger):
1999 def setlogger(self, name, logger):
2000 """Install logger which can be identified later by the given name
2000 """Install logger which can be identified later by the given name
2001
2001
2002 More than one loggers can be registered. Use extension or module
2002 More than one loggers can be registered. Use extension or module
2003 name to uniquely identify the logger instance.
2003 name to uniquely identify the logger instance.
2004 """
2004 """
2005 self._loggers[name] = logger
2005 self._loggers[name] = logger
2006
2006
2007 def log(self, event, msgfmt, *msgargs, **opts):
2007 def log(self, event, msgfmt, *msgargs, **opts):
2008 '''hook for logging facility extensions
2008 '''hook for logging facility extensions
2009
2009
2010 event should be a readily-identifiable subsystem, which will
2010 event should be a readily-identifiable subsystem, which will
2011 allow filtering.
2011 allow filtering.
2012
2012
2013 msgfmt should be a newline-terminated format string to log, and
2013 msgfmt should be a newline-terminated format string to log, and
2014 *msgargs are %-formatted into it.
2014 *msgargs are %-formatted into it.
2015
2015
2016 **opts currently has no defined meanings.
2016 **opts currently has no defined meanings.
2017 '''
2017 '''
2018 if not self._loggers:
2018 if not self._loggers:
2019 return
2019 return
2020 activeloggers = [
2020 activeloggers = [
2021 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2021 l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
2022 ]
2022 ]
2023 if not activeloggers:
2023 if not activeloggers:
2024 return
2024 return
2025 msg = msgfmt % msgargs
2025 msg = msgfmt % msgargs
2026 opts = pycompat.byteskwargs(opts)
2026 opts = pycompat.byteskwargs(opts)
2027 # guard against recursion from e.g. ui.debug()
2027 # guard against recursion from e.g. ui.debug()
2028 registeredloggers = self._loggers
2028 registeredloggers = self._loggers
2029 self._loggers = {}
2029 self._loggers = {}
2030 try:
2030 try:
2031 for logger in activeloggers:
2031 for logger in activeloggers:
2032 logger.log(self, event, msg, opts)
2032 logger.log(self, event, msg, opts)
2033 finally:
2033 finally:
2034 self._loggers = registeredloggers
2034 self._loggers = registeredloggers
2035
2035
2036 def label(self, msg, label):
2036 def label(self, msg, label):
2037 '''style msg based on supplied label
2037 '''style msg based on supplied label
2038
2038
2039 If some color mode is enabled, this will add the necessary control
2039 If some color mode is enabled, this will add the necessary control
2040 characters to apply such color. In addition, 'debug' color mode adds
2040 characters to apply such color. In addition, 'debug' color mode adds
2041 markup showing which label affects a piece of text.
2041 markup showing which label affects a piece of text.
2042
2042
2043 ui.write(s, 'label') is equivalent to
2043 ui.write(s, 'label') is equivalent to
2044 ui.write(ui.label(s, 'label')).
2044 ui.write(ui.label(s, 'label')).
2045 '''
2045 '''
2046 if self._colormode is not None:
2046 if self._colormode is not None:
2047 return color.colorlabel(self, msg, label)
2047 return color.colorlabel(self, msg, label)
2048 return msg
2048 return msg
2049
2049
2050 def develwarn(self, msg, stacklevel=1, config=None):
2050 def develwarn(self, msg, stacklevel=1, config=None):
2051 """issue a developer warning message
2051 """issue a developer warning message
2052
2052
2053 Use 'stacklevel' to report the offender some layers further up in the
2053 Use 'stacklevel' to report the offender some layers further up in the
2054 stack.
2054 stack.
2055 """
2055 """
2056 if not self.configbool(b'devel', b'all-warnings'):
2056 if not self.configbool(b'devel', b'all-warnings'):
2057 if config is None or not self.configbool(b'devel', config):
2057 if config is None or not self.configbool(b'devel', config):
2058 return
2058 return
2059 msg = b'devel-warn: ' + msg
2059 msg = b'devel-warn: ' + msg
2060 stacklevel += 1 # get in develwarn
2060 stacklevel += 1 # get in develwarn
2061 if self.tracebackflag:
2061 if self.tracebackflag:
2062 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2062 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2063 self.log(
2063 self.log(
2064 b'develwarn',
2064 b'develwarn',
2065 b'%s at:\n%s'
2065 b'%s at:\n%s'
2066 % (msg, b''.join(util.getstackframes(stacklevel))),
2066 % (msg, b''.join(util.getstackframes(stacklevel))),
2067 )
2067 )
2068 else:
2068 else:
2069 curframe = inspect.currentframe()
2069 curframe = inspect.currentframe()
2070 calframe = inspect.getouterframes(curframe, 2)
2070 calframe = inspect.getouterframes(curframe, 2)
2071 fname, lineno, fmsg = calframe[stacklevel][1:4]
2071 fname, lineno, fmsg = calframe[stacklevel][1:4]
2072 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2072 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2073 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2073 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2074 self.log(
2074 self.log(
2075 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2075 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2076 )
2076 )
2077
2077
2078 # avoid cycles
2078 # avoid cycles
2079 del curframe
2079 del curframe
2080 del calframe
2080 del calframe
2081
2081
2082 def deprecwarn(self, msg, version, stacklevel=2):
2082 def deprecwarn(self, msg, version, stacklevel=2):
2083 """issue a deprecation warning
2083 """issue a deprecation warning
2084
2084
2085 - msg: message explaining what is deprecated and how to upgrade,
2085 - msg: message explaining what is deprecated and how to upgrade,
2086 - version: last version where the API will be supported,
2086 - version: last version where the API will be supported,
2087 """
2087 """
2088 if not (
2088 if not (
2089 self.configbool(b'devel', b'all-warnings')
2089 self.configbool(b'devel', b'all-warnings')
2090 or self.configbool(b'devel', b'deprec-warn')
2090 or self.configbool(b'devel', b'deprec-warn')
2091 ):
2091 ):
2092 return
2092 return
2093 msg += (
2093 msg += (
2094 b"\n(compatibility will be dropped after Mercurial-%s,"
2094 b"\n(compatibility will be dropped after Mercurial-%s,"
2095 b" update your code.)"
2095 b" update your code.)"
2096 ) % version
2096 ) % version
2097 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2097 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2098
2098
2099 def exportableenviron(self):
2099 def exportableenviron(self):
2100 """The environment variables that are safe to export, e.g. through
2100 """The environment variables that are safe to export, e.g. through
2101 hgweb.
2101 hgweb.
2102 """
2102 """
2103 return self._exportableenviron
2103 return self._exportableenviron
2104
2104
2105 @contextlib.contextmanager
2105 @contextlib.contextmanager
2106 def configoverride(self, overrides, source=b""):
2106 def configoverride(self, overrides, source=b""):
2107 """Context manager for temporary config overrides
2107 """Context manager for temporary config overrides
2108 `overrides` must be a dict of the following structure:
2108 `overrides` must be a dict of the following structure:
2109 {(section, name) : value}"""
2109 {(section, name) : value}"""
2110 backups = {}
2110 backups = {}
2111 try:
2111 try:
2112 for (section, name), value in overrides.items():
2112 for (section, name), value in overrides.items():
2113 backups[(section, name)] = self.backupconfig(section, name)
2113 backups[(section, name)] = self.backupconfig(section, name)
2114 self.setconfig(section, name, value, source)
2114 self.setconfig(section, name, value, source)
2115 yield
2115 yield
2116 finally:
2116 finally:
2117 for __, backup in backups.items():
2117 for __, backup in backups.items():
2118 self.restoreconfig(backup)
2118 self.restoreconfig(backup)
2119 # just restoring ui.quiet config to the previous value is not enough
2119 # just restoring ui.quiet config to the previous value is not enough
2120 # as it does not update ui.quiet class member
2120 # as it does not update ui.quiet class member
2121 if (b'ui', b'quiet') in overrides:
2121 if (b'ui', b'quiet') in overrides:
2122 self.fixconfig(section=b'ui')
2122 self.fixconfig(section=b'ui')
2123
2123
2124 def estimatememory(self):
2124 def estimatememory(self):
2125 """Provide an estimate for the available system memory in Bytes.
2125 """Provide an estimate for the available system memory in Bytes.
2126
2126
2127 This can be overriden via ui.available-memory. It returns None, if
2127 This can be overriden via ui.available-memory. It returns None, if
2128 no estimate can be computed.
2128 no estimate can be computed.
2129 """
2129 """
2130 value = self.config(b'ui', b'available-memory')
2130 value = self.config(b'ui', b'available-memory')
2131 if value is not None:
2131 if value is not None:
2132 try:
2132 try:
2133 return util.sizetoint(value)
2133 return util.sizetoint(value)
2134 except error.ParseError:
2134 except error.ParseError:
2135 raise error.ConfigError(
2135 raise error.ConfigError(
2136 _(b"ui.available-memory value is invalid ('%s')") % value
2136 _(b"ui.available-memory value is invalid ('%s')") % value
2137 )
2137 )
2138 return util._estimatememory()
2138 return util._estimatememory()
2139
2139
2140
2140
2141 class paths(dict):
2141 class paths(dict):
2142 """Represents a collection of paths and their configs.
2142 """Represents a collection of paths and their configs.
2143
2143
2144 Data is initially derived from ui instances and the config files they have
2144 Data is initially derived from ui instances and the config files they have
2145 loaded.
2145 loaded.
2146 """
2146 """
2147
2147
2148 def __init__(self, ui):
2148 def __init__(self, ui):
2149 dict.__init__(self)
2149 dict.__init__(self)
2150
2150
2151 for name, loc in ui.configitems(b'paths', ignoresub=True):
2151 for name, loc in ui.configitems(b'paths', ignoresub=True):
2152 # No location is the same as not existing.
2152 # No location is the same as not existing.
2153 if not loc:
2153 if not loc:
2154 continue
2154 continue
2155 loc, sub = ui.configsuboptions(b'paths', name)
2155 loc, sub = ui.configsuboptions(b'paths', name)
2156 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2156 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
2157
2157
2158 def getpath(self, name, default=None):
2158 def getpath(self, name, default=None):
2159 """Return a ``path`` from a string, falling back to default.
2159 """Return a ``path`` from a string, falling back to default.
2160
2160
2161 ``name`` can be a named path or locations. Locations are filesystem
2161 ``name`` can be a named path or locations. Locations are filesystem
2162 paths or URIs.
2162 paths or URIs.
2163
2163
2164 Returns None if ``name`` is not a registered path, a URI, or a local
2164 Returns None if ``name`` is not a registered path, a URI, or a local
2165 path to a repo.
2165 path to a repo.
2166 """
2166 """
2167 # Only fall back to default if no path was requested.
2167 # Only fall back to default if no path was requested.
2168 if name is None:
2168 if name is None:
2169 if not default:
2169 if not default:
2170 default = ()
2170 default = ()
2171 elif not isinstance(default, (tuple, list)):
2171 elif not isinstance(default, (tuple, list)):
2172 default = (default,)
2172 default = (default,)
2173 for k in default:
2173 for k in default:
2174 try:
2174 try:
2175 return self[k]
2175 return self[k]
2176 except KeyError:
2176 except KeyError:
2177 continue
2177 continue
2178 return None
2178 return None
2179
2179
2180 # Most likely empty string.
2180 # Most likely empty string.
2181 # This may need to raise in the future.
2181 # This may need to raise in the future.
2182 if not name:
2182 if not name:
2183 return None
2183 return None
2184
2184
2185 try:
2185 try:
2186 return self[name]
2186 return self[name]
2187 except KeyError:
2187 except KeyError:
2188 # Try to resolve as a local path or URI.
2188 # Try to resolve as a local path or URI.
2189 try:
2189 try:
2190 # We don't pass sub-options in, so no need to pass ui instance.
2190 # We don't pass sub-options in, so no need to pass ui instance.
2191 return path(None, None, rawloc=name)
2191 return path(None, None, rawloc=name)
2192 except ValueError:
2192 except ValueError:
2193 raise error.RepoError(_(b'repository %s does not exist') % name)
2193 raise error.RepoError(_(b'repository %s does not exist') % name)
2194
2194
2195
2195
2196 _pathsuboptions = {}
2196 _pathsuboptions = {}
2197
2197
2198
2198
2199 def pathsuboption(option, attr):
2199 def pathsuboption(option, attr):
2200 """Decorator used to declare a path sub-option.
2200 """Decorator used to declare a path sub-option.
2201
2201
2202 Arguments are the sub-option name and the attribute it should set on
2202 Arguments are the sub-option name and the attribute it should set on
2203 ``path`` instances.
2203 ``path`` instances.
2204
2204
2205 The decorated function will receive as arguments a ``ui`` instance,
2205 The decorated function will receive as arguments a ``ui`` instance,
2206 ``path`` instance, and the string value of this option from the config.
2206 ``path`` instance, and the string value of this option from the config.
2207 The function should return the value that will be set on the ``path``
2207 The function should return the value that will be set on the ``path``
2208 instance.
2208 instance.
2209
2209
2210 This decorator can be used to perform additional verification of
2210 This decorator can be used to perform additional verification of
2211 sub-options and to change the type of sub-options.
2211 sub-options and to change the type of sub-options.
2212 """
2212 """
2213
2213
2214 def register(func):
2214 def register(func):
2215 _pathsuboptions[option] = (attr, func)
2215 _pathsuboptions[option] = (attr, func)
2216 return func
2216 return func
2217
2217
2218 return register
2218 return register
2219
2219
2220
2220
2221 @pathsuboption(b'pushurl', b'pushloc')
2221 @pathsuboption(b'pushurl', b'pushloc')
2222 def pushurlpathoption(ui, path, value):
2222 def pushurlpathoption(ui, path, value):
2223 u = util.url(value)
2223 u = util.url(value)
2224 # Actually require a URL.
2224 # Actually require a URL.
2225 if not u.scheme:
2225 if not u.scheme:
2226 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2226 ui.warn(_(b'(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
2227 return None
2227 return None
2228
2228
2229 # Don't support the #foo syntax in the push URL to declare branch to
2229 # Don't support the #foo syntax in the push URL to declare branch to
2230 # push.
2230 # push.
2231 if u.fragment:
2231 if u.fragment:
2232 ui.warn(
2232 ui.warn(
2233 _(
2233 _(
2234 b'("#fragment" in paths.%s:pushurl not supported; '
2234 b'("#fragment" in paths.%s:pushurl not supported; '
2235 b'ignoring)\n'
2235 b'ignoring)\n'
2236 )
2236 )
2237 % path.name
2237 % path.name
2238 )
2238 )
2239 u.fragment = None
2239 u.fragment = None
2240
2240
2241 return bytes(u)
2241 return bytes(u)
2242
2242
2243
2243
2244 @pathsuboption(b'pushrev', b'pushrev')
2244 @pathsuboption(b'pushrev', b'pushrev')
2245 def pushrevpathoption(ui, path, value):
2245 def pushrevpathoption(ui, path, value):
2246 return value
2246 return value
2247
2247
2248
2248
2249 class path(object):
2249 class path(object):
2250 """Represents an individual path and its configuration."""
2250 """Represents an individual path and its configuration."""
2251
2251
2252 def __init__(self, ui, name, rawloc=None, suboptions=None):
2252 def __init__(self, ui, name, rawloc=None, suboptions=None):
2253 """Construct a path from its config options.
2253 """Construct a path from its config options.
2254
2254
2255 ``ui`` is the ``ui`` instance the path is coming from.
2255 ``ui`` is the ``ui`` instance the path is coming from.
2256 ``name`` is the symbolic name of the path.
2256 ``name`` is the symbolic name of the path.
2257 ``rawloc`` is the raw location, as defined in the config.
2257 ``rawloc`` is the raw location, as defined in the config.
2258 ``pushloc`` is the raw locations pushes should be made to.
2258 ``pushloc`` is the raw locations pushes should be made to.
2259
2259
2260 If ``name`` is not defined, we require that the location be a) a local
2260 If ``name`` is not defined, we require that the location be a) a local
2261 filesystem path with a .hg directory or b) a URL. If not,
2261 filesystem path with a .hg directory or b) a URL. If not,
2262 ``ValueError`` is raised.
2262 ``ValueError`` is raised.
2263 """
2263 """
2264 if not rawloc:
2264 if not rawloc:
2265 raise ValueError(b'rawloc must be defined')
2265 raise ValueError(b'rawloc must be defined')
2266
2266
2267 # Locations may define branches via syntax <base>#<branch>.
2267 # Locations may define branches via syntax <base>#<branch>.
2268 u = util.url(rawloc)
2268 u = util.url(rawloc)
2269 branch = None
2269 branch = None
2270 if u.fragment:
2270 if u.fragment:
2271 branch = u.fragment
2271 branch = u.fragment
2272 u.fragment = None
2272 u.fragment = None
2273
2273
2274 self.url = u
2274 self.url = u
2275 self.branch = branch
2275 self.branch = branch
2276
2276
2277 self.name = name
2277 self.name = name
2278 self.rawloc = rawloc
2278 self.rawloc = rawloc
2279 self.loc = b'%s' % u
2279 self.loc = b'%s' % u
2280
2280
2281 # When given a raw location but not a symbolic name, validate the
2281 # When given a raw location but not a symbolic name, validate the
2282 # location is valid.
2282 # location is valid.
2283 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2283 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
2284 raise ValueError(
2284 raise ValueError(
2285 b'location is not a URL or path to a local '
2285 b'location is not a URL or path to a local '
2286 b'repo: %s' % rawloc
2286 b'repo: %s' % rawloc
2287 )
2287 )
2288
2288
2289 suboptions = suboptions or {}
2289 suboptions = suboptions or {}
2290
2290
2291 # Now process the sub-options. If a sub-option is registered, its
2291 # Now process the sub-options. If a sub-option is registered, its
2292 # attribute will always be present. The value will be None if there
2292 # attribute will always be present. The value will be None if there
2293 # was no valid sub-option.
2293 # was no valid sub-option.
2294 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2294 for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
2295 if suboption not in suboptions:
2295 if suboption not in suboptions:
2296 setattr(self, attr, None)
2296 setattr(self, attr, None)
2297 continue
2297 continue
2298
2298
2299 value = func(ui, self, suboptions[suboption])
2299 value = func(ui, self, suboptions[suboption])
2300 setattr(self, attr, value)
2300 setattr(self, attr, value)
2301
2301
2302 def _isvalidlocalpath(self, path):
2302 def _isvalidlocalpath(self, path):
2303 """Returns True if the given path is a potentially valid repository.
2303 """Returns True if the given path is a potentially valid repository.
2304 This is its own function so that extensions can change the definition of
2304 This is its own function so that extensions can change the definition of
2305 'valid' in this case (like when pulling from a git repo into a hg
2305 'valid' in this case (like when pulling from a git repo into a hg
2306 one)."""
2306 one)."""
2307 try:
2307 try:
2308 return os.path.isdir(os.path.join(path, b'.hg'))
2308 return os.path.isdir(os.path.join(path, b'.hg'))
2309 # Python 2 may return TypeError. Python 3, ValueError.
2309 # Python 2 may return TypeError. Python 3, ValueError.
2310 except (TypeError, ValueError):
2310 except (TypeError, ValueError):
2311 return False
2311 return False
2312
2312
2313 @property
2313 @property
2314 def suboptions(self):
2314 def suboptions(self):
2315 """Return sub-options and their values for this path.
2315 """Return sub-options and their values for this path.
2316
2316
2317 This is intended to be used for presentation purposes.
2317 This is intended to be used for presentation purposes.
2318 """
2318 """
2319 d = {}
2319 d = {}
2320 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2320 for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
2321 value = getattr(self, attr)
2321 value = getattr(self, attr)
2322 if value is not None:
2322 if value is not None:
2323 d[subopt] = value
2323 d[subopt] = value
2324 return d
2324 return d
2325
2325
2326
2326
2327 # we instantiate one globally shared progress bar to avoid
2327 # we instantiate one globally shared progress bar to avoid
2328 # competing progress bars when multiple UI objects get created
2328 # competing progress bars when multiple UI objects get created
2329 _progresssingleton = None
2329 _progresssingleton = None
2330
2330
2331
2331
2332 def getprogbar(ui):
2332 def getprogbar(ui):
2333 global _progresssingleton
2333 global _progresssingleton
2334 if _progresssingleton is None:
2334 if _progresssingleton is None:
2335 # passing 'ui' object to the singleton is fishy,
2335 # passing 'ui' object to the singleton is fishy,
2336 # this is how the extension used to work but feel free to rework it.
2336 # this is how the extension used to work but feel free to rework it.
2337 _progresssingleton = progress.progbar(ui)
2337 _progresssingleton = progress.progbar(ui)
2338 return _progresssingleton
2338 return _progresssingleton
2339
2339
2340
2340
2341 def haveprogbar():
2341 def haveprogbar():
2342 return _progresssingleton is not None
2342 return _progresssingleton is not None
2343
2343
2344
2344
2345 def _selectmsgdests(ui):
2345 def _selectmsgdests(ui):
2346 name = ui.config(b'ui', b'message-output')
2346 name = ui.config(b'ui', b'message-output')
2347 if name == b'channel':
2347 if name == b'channel':
2348 if ui.fmsg:
2348 if ui.fmsg:
2349 return ui.fmsg, ui.fmsg
2349 return ui.fmsg, ui.fmsg
2350 else:
2350 else:
2351 # fall back to ferr if channel isn't ready so that status/error
2351 # fall back to ferr if channel isn't ready so that status/error
2352 # messages can be printed
2352 # messages can be printed
2353 return ui.ferr, ui.ferr
2353 return ui.ferr, ui.ferr
2354 if name == b'stdio':
2354 if name == b'stdio':
2355 return ui.fout, ui.ferr
2355 return ui.fout, ui.ferr
2356 if name == b'stderr':
2356 if name == b'stderr':
2357 return ui.ferr, ui.ferr
2357 return ui.ferr, ui.ferr
2358 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2358 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2359
2359
2360
2360
2361 def _writemsgwith(write, dest, *args, **opts):
2361 def _writemsgwith(write, dest, *args, **opts):
2362 """Write ui message with the given ui._write*() function
2362 """Write ui message with the given ui._write*() function
2363
2363
2364 The specified message type is translated to 'ui.<type>' label if the dest
2364 The specified message type is translated to 'ui.<type>' label if the dest
2365 isn't a structured channel, so that the message will be colorized.
2365 isn't a structured channel, so that the message will be colorized.
2366 """
2366 """
2367 # TODO: maybe change 'type' to a mandatory option
2367 # TODO: maybe change 'type' to a mandatory option
2368 if 'type' in opts and not getattr(dest, 'structured', False):
2368 if 'type' in opts and not getattr(dest, 'structured', False):
2369 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2369 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2370 write(dest, *args, **opts)
2370 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now