##// END OF EJS Templates
wireproto: rename HTTPV2 so it less like HTTP/2...
Gregory Szorc -
r37662:77c9ee77 default
parent child Browse files
Show More
@@ -1,3101 +1,3101
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 operator
14 import operator
15 import os
15 import os
16 import random
16 import random
17 import re
17 import re
18 import socket
18 import socket
19 import ssl
19 import ssl
20 import stat
20 import stat
21 import string
21 import string
22 import subprocess
22 import subprocess
23 import sys
23 import sys
24 import tempfile
24 import tempfile
25 import time
25 import time
26
26
27 from .i18n import _
27 from .i18n import _
28 from .node import (
28 from .node import (
29 bin,
29 bin,
30 hex,
30 hex,
31 nullhex,
31 nullhex,
32 nullid,
32 nullid,
33 nullrev,
33 nullrev,
34 short,
34 short,
35 )
35 )
36 from .thirdparty import (
36 from .thirdparty import (
37 cbor,
37 cbor,
38 )
38 )
39 from . import (
39 from . import (
40 bundle2,
40 bundle2,
41 changegroup,
41 changegroup,
42 cmdutil,
42 cmdutil,
43 color,
43 color,
44 context,
44 context,
45 dagparser,
45 dagparser,
46 dagutil,
46 dagutil,
47 encoding,
47 encoding,
48 error,
48 error,
49 exchange,
49 exchange,
50 extensions,
50 extensions,
51 filemerge,
51 filemerge,
52 fileset,
52 fileset,
53 formatter,
53 formatter,
54 hg,
54 hg,
55 httppeer,
55 httppeer,
56 localrepo,
56 localrepo,
57 lock as lockmod,
57 lock as lockmod,
58 logcmdutil,
58 logcmdutil,
59 merge as mergemod,
59 merge as mergemod,
60 obsolete,
60 obsolete,
61 obsutil,
61 obsutil,
62 phases,
62 phases,
63 policy,
63 policy,
64 pvec,
64 pvec,
65 pycompat,
65 pycompat,
66 registrar,
66 registrar,
67 repair,
67 repair,
68 revlog,
68 revlog,
69 revset,
69 revset,
70 revsetlang,
70 revsetlang,
71 scmutil,
71 scmutil,
72 setdiscovery,
72 setdiscovery,
73 simplemerge,
73 simplemerge,
74 smartset,
74 smartset,
75 sshpeer,
75 sshpeer,
76 sslutil,
76 sslutil,
77 streamclone,
77 streamclone,
78 templater,
78 templater,
79 treediscovery,
79 treediscovery,
80 upgrade,
80 upgrade,
81 url as urlmod,
81 url as urlmod,
82 util,
82 util,
83 vfs as vfsmod,
83 vfs as vfsmod,
84 wireprotoframing,
84 wireprotoframing,
85 wireprotoserver,
85 wireprotoserver,
86 wireprototypes,
86 wireprototypes,
87 )
87 )
88 from .utils import (
88 from .utils import (
89 dateutil,
89 dateutil,
90 procutil,
90 procutil,
91 stringutil,
91 stringutil,
92 )
92 )
93
93
94 release = lockmod.release
94 release = lockmod.release
95
95
96 command = registrar.command()
96 command = registrar.command()
97
97
98 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
98 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
99 def debugancestor(ui, repo, *args):
99 def debugancestor(ui, repo, *args):
100 """find the ancestor revision of two revisions in a given index"""
100 """find the ancestor revision of two revisions in a given index"""
101 if len(args) == 3:
101 if len(args) == 3:
102 index, rev1, rev2 = args
102 index, rev1, rev2 = args
103 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
103 r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
104 lookup = r.lookup
104 lookup = r.lookup
105 elif len(args) == 2:
105 elif len(args) == 2:
106 if not repo:
106 if not repo:
107 raise error.Abort(_('there is no Mercurial repository here '
107 raise error.Abort(_('there is no Mercurial repository here '
108 '(.hg not found)'))
108 '(.hg not found)'))
109 rev1, rev2 = args
109 rev1, rev2 = args
110 r = repo.changelog
110 r = repo.changelog
111 lookup = repo.lookup
111 lookup = repo.lookup
112 else:
112 else:
113 raise error.Abort(_('either two or three arguments required'))
113 raise error.Abort(_('either two or three arguments required'))
114 a = r.ancestor(lookup(rev1), lookup(rev2))
114 a = r.ancestor(lookup(rev1), lookup(rev2))
115 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
115 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
116
116
117 @command('debugapplystreamclonebundle', [], 'FILE')
117 @command('debugapplystreamclonebundle', [], 'FILE')
118 def debugapplystreamclonebundle(ui, repo, fname):
118 def debugapplystreamclonebundle(ui, repo, fname):
119 """apply a stream clone bundle file"""
119 """apply a stream clone bundle file"""
120 f = hg.openpath(ui, fname)
120 f = hg.openpath(ui, fname)
121 gen = exchange.readbundle(ui, f, fname)
121 gen = exchange.readbundle(ui, f, fname)
122 gen.apply(repo)
122 gen.apply(repo)
123
123
124 @command('debugbuilddag',
124 @command('debugbuilddag',
125 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
125 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
126 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
126 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
127 ('n', 'new-file', None, _('add new file at each rev'))],
127 ('n', 'new-file', None, _('add new file at each rev'))],
128 _('[OPTION]... [TEXT]'))
128 _('[OPTION]... [TEXT]'))
129 def debugbuilddag(ui, repo, text=None,
129 def debugbuilddag(ui, repo, text=None,
130 mergeable_file=False,
130 mergeable_file=False,
131 overwritten_file=False,
131 overwritten_file=False,
132 new_file=False):
132 new_file=False):
133 """builds a repo with a given DAG from scratch in the current empty repo
133 """builds a repo with a given DAG from scratch in the current empty repo
134
134
135 The description of the DAG is read from stdin if not given on the
135 The description of the DAG is read from stdin if not given on the
136 command line.
136 command line.
137
137
138 Elements:
138 Elements:
139
139
140 - "+n" is a linear run of n nodes based on the current default parent
140 - "+n" is a linear run of n nodes based on the current default parent
141 - "." is a single node based on the current default parent
141 - "." is a single node based on the current default parent
142 - "$" resets the default parent to null (implied at the start);
142 - "$" resets the default parent to null (implied at the start);
143 otherwise the default parent is always the last node created
143 otherwise the default parent is always the last node created
144 - "<p" sets the default parent to the backref p
144 - "<p" sets the default parent to the backref p
145 - "*p" is a fork at parent p, which is a backref
145 - "*p" is a fork at parent p, which is a backref
146 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
146 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
147 - "/p2" is a merge of the preceding node and p2
147 - "/p2" is a merge of the preceding node and p2
148 - ":tag" defines a local tag for the preceding node
148 - ":tag" defines a local tag for the preceding node
149 - "@branch" sets the named branch for subsequent nodes
149 - "@branch" sets the named branch for subsequent nodes
150 - "#...\\n" is a comment up to the end of the line
150 - "#...\\n" is a comment up to the end of the line
151
151
152 Whitespace between the above elements is ignored.
152 Whitespace between the above elements is ignored.
153
153
154 A backref is either
154 A backref is either
155
155
156 - a number n, which references the node curr-n, where curr is the current
156 - a number n, which references the node curr-n, where curr is the current
157 node, or
157 node, or
158 - the name of a local tag you placed earlier using ":tag", or
158 - the name of a local tag you placed earlier using ":tag", or
159 - empty to denote the default parent.
159 - empty to denote the default parent.
160
160
161 All string valued-elements are either strictly alphanumeric, or must
161 All string valued-elements are either strictly alphanumeric, or must
162 be enclosed in double quotes ("..."), with "\\" as escape character.
162 be enclosed in double quotes ("..."), with "\\" as escape character.
163 """
163 """
164
164
165 if text is None:
165 if text is None:
166 ui.status(_("reading DAG from stdin\n"))
166 ui.status(_("reading DAG from stdin\n"))
167 text = ui.fin.read()
167 text = ui.fin.read()
168
168
169 cl = repo.changelog
169 cl = repo.changelog
170 if len(cl) > 0:
170 if len(cl) > 0:
171 raise error.Abort(_('repository is not empty'))
171 raise error.Abort(_('repository is not empty'))
172
172
173 # determine number of revs in DAG
173 # determine number of revs in DAG
174 total = 0
174 total = 0
175 for type, data in dagparser.parsedag(text):
175 for type, data in dagparser.parsedag(text):
176 if type == 'n':
176 if type == 'n':
177 total += 1
177 total += 1
178
178
179 if mergeable_file:
179 if mergeable_file:
180 linesperrev = 2
180 linesperrev = 2
181 # make a file with k lines per rev
181 # make a file with k lines per rev
182 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
182 initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
183 initialmergedlines.append("")
183 initialmergedlines.append("")
184
184
185 tags = []
185 tags = []
186
186
187 wlock = lock = tr = None
187 wlock = lock = tr = None
188 try:
188 try:
189 wlock = repo.wlock()
189 wlock = repo.wlock()
190 lock = repo.lock()
190 lock = repo.lock()
191 tr = repo.transaction("builddag")
191 tr = repo.transaction("builddag")
192
192
193 at = -1
193 at = -1
194 atbranch = 'default'
194 atbranch = 'default'
195 nodeids = []
195 nodeids = []
196 id = 0
196 id = 0
197 ui.progress(_('building'), id, unit=_('revisions'), total=total)
197 ui.progress(_('building'), id, unit=_('revisions'), total=total)
198 for type, data in dagparser.parsedag(text):
198 for type, data in dagparser.parsedag(text):
199 if type == 'n':
199 if type == 'n':
200 ui.note(('node %s\n' % pycompat.bytestr(data)))
200 ui.note(('node %s\n' % pycompat.bytestr(data)))
201 id, ps = data
201 id, ps = data
202
202
203 files = []
203 files = []
204 filecontent = {}
204 filecontent = {}
205
205
206 p2 = None
206 p2 = None
207 if mergeable_file:
207 if mergeable_file:
208 fn = "mf"
208 fn = "mf"
209 p1 = repo[ps[0]]
209 p1 = repo[ps[0]]
210 if len(ps) > 1:
210 if len(ps) > 1:
211 p2 = repo[ps[1]]
211 p2 = repo[ps[1]]
212 pa = p1.ancestor(p2)
212 pa = p1.ancestor(p2)
213 base, local, other = [x[fn].data() for x in (pa, p1,
213 base, local, other = [x[fn].data() for x in (pa, p1,
214 p2)]
214 p2)]
215 m3 = simplemerge.Merge3Text(base, local, other)
215 m3 = simplemerge.Merge3Text(base, local, other)
216 ml = [l.strip() for l in m3.merge_lines()]
216 ml = [l.strip() for l in m3.merge_lines()]
217 ml.append("")
217 ml.append("")
218 elif at > 0:
218 elif at > 0:
219 ml = p1[fn].data().split("\n")
219 ml = p1[fn].data().split("\n")
220 else:
220 else:
221 ml = initialmergedlines
221 ml = initialmergedlines
222 ml[id * linesperrev] += " r%i" % id
222 ml[id * linesperrev] += " r%i" % id
223 mergedtext = "\n".join(ml)
223 mergedtext = "\n".join(ml)
224 files.append(fn)
224 files.append(fn)
225 filecontent[fn] = mergedtext
225 filecontent[fn] = mergedtext
226
226
227 if overwritten_file:
227 if overwritten_file:
228 fn = "of"
228 fn = "of"
229 files.append(fn)
229 files.append(fn)
230 filecontent[fn] = "r%i\n" % id
230 filecontent[fn] = "r%i\n" % id
231
231
232 if new_file:
232 if new_file:
233 fn = "nf%i" % id
233 fn = "nf%i" % id
234 files.append(fn)
234 files.append(fn)
235 filecontent[fn] = "r%i\n" % id
235 filecontent[fn] = "r%i\n" % id
236 if len(ps) > 1:
236 if len(ps) > 1:
237 if not p2:
237 if not p2:
238 p2 = repo[ps[1]]
238 p2 = repo[ps[1]]
239 for fn in p2:
239 for fn in p2:
240 if fn.startswith("nf"):
240 if fn.startswith("nf"):
241 files.append(fn)
241 files.append(fn)
242 filecontent[fn] = p2[fn].data()
242 filecontent[fn] = p2[fn].data()
243
243
244 def fctxfn(repo, cx, path):
244 def fctxfn(repo, cx, path):
245 if path in filecontent:
245 if path in filecontent:
246 return context.memfilectx(repo, cx, path,
246 return context.memfilectx(repo, cx, path,
247 filecontent[path])
247 filecontent[path])
248 return None
248 return None
249
249
250 if len(ps) == 0 or ps[0] < 0:
250 if len(ps) == 0 or ps[0] < 0:
251 pars = [None, None]
251 pars = [None, None]
252 elif len(ps) == 1:
252 elif len(ps) == 1:
253 pars = [nodeids[ps[0]], None]
253 pars = [nodeids[ps[0]], None]
254 else:
254 else:
255 pars = [nodeids[p] for p in ps]
255 pars = [nodeids[p] for p in ps]
256 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
256 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
257 date=(id, 0),
257 date=(id, 0),
258 user="debugbuilddag",
258 user="debugbuilddag",
259 extra={'branch': atbranch})
259 extra={'branch': atbranch})
260 nodeid = repo.commitctx(cx)
260 nodeid = repo.commitctx(cx)
261 nodeids.append(nodeid)
261 nodeids.append(nodeid)
262 at = id
262 at = id
263 elif type == 'l':
263 elif type == 'l':
264 id, name = data
264 id, name = data
265 ui.note(('tag %s\n' % name))
265 ui.note(('tag %s\n' % name))
266 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
266 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
267 elif type == 'a':
267 elif type == 'a':
268 ui.note(('branch %s\n' % data))
268 ui.note(('branch %s\n' % data))
269 atbranch = data
269 atbranch = data
270 ui.progress(_('building'), id, unit=_('revisions'), total=total)
270 ui.progress(_('building'), id, unit=_('revisions'), total=total)
271 tr.close()
271 tr.close()
272
272
273 if tags:
273 if tags:
274 repo.vfs.write("localtags", "".join(tags))
274 repo.vfs.write("localtags", "".join(tags))
275 finally:
275 finally:
276 ui.progress(_('building'), None)
276 ui.progress(_('building'), None)
277 release(tr, lock, wlock)
277 release(tr, lock, wlock)
278
278
279 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
279 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
280 indent_string = ' ' * indent
280 indent_string = ' ' * indent
281 if all:
281 if all:
282 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
282 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
283 % indent_string)
283 % indent_string)
284
284
285 def showchunks(named):
285 def showchunks(named):
286 ui.write("\n%s%s\n" % (indent_string, named))
286 ui.write("\n%s%s\n" % (indent_string, named))
287 for deltadata in gen.deltaiter():
287 for deltadata in gen.deltaiter():
288 node, p1, p2, cs, deltabase, delta, flags = deltadata
288 node, p1, p2, cs, deltabase, delta, flags = deltadata
289 ui.write("%s%s %s %s %s %s %d\n" %
289 ui.write("%s%s %s %s %s %s %d\n" %
290 (indent_string, hex(node), hex(p1), hex(p2),
290 (indent_string, hex(node), hex(p1), hex(p2),
291 hex(cs), hex(deltabase), len(delta)))
291 hex(cs), hex(deltabase), len(delta)))
292
292
293 chunkdata = gen.changelogheader()
293 chunkdata = gen.changelogheader()
294 showchunks("changelog")
294 showchunks("changelog")
295 chunkdata = gen.manifestheader()
295 chunkdata = gen.manifestheader()
296 showchunks("manifest")
296 showchunks("manifest")
297 for chunkdata in iter(gen.filelogheader, {}):
297 for chunkdata in iter(gen.filelogheader, {}):
298 fname = chunkdata['filename']
298 fname = chunkdata['filename']
299 showchunks(fname)
299 showchunks(fname)
300 else:
300 else:
301 if isinstance(gen, bundle2.unbundle20):
301 if isinstance(gen, bundle2.unbundle20):
302 raise error.Abort(_('use debugbundle2 for this file'))
302 raise error.Abort(_('use debugbundle2 for this file'))
303 chunkdata = gen.changelogheader()
303 chunkdata = gen.changelogheader()
304 for deltadata in gen.deltaiter():
304 for deltadata in gen.deltaiter():
305 node, p1, p2, cs, deltabase, delta, flags = deltadata
305 node, p1, p2, cs, deltabase, delta, flags = deltadata
306 ui.write("%s%s\n" % (indent_string, hex(node)))
306 ui.write("%s%s\n" % (indent_string, hex(node)))
307
307
308 def _debugobsmarkers(ui, part, indent=0, **opts):
308 def _debugobsmarkers(ui, part, indent=0, **opts):
309 """display version and markers contained in 'data'"""
309 """display version and markers contained in 'data'"""
310 opts = pycompat.byteskwargs(opts)
310 opts = pycompat.byteskwargs(opts)
311 data = part.read()
311 data = part.read()
312 indent_string = ' ' * indent
312 indent_string = ' ' * indent
313 try:
313 try:
314 version, markers = obsolete._readmarkers(data)
314 version, markers = obsolete._readmarkers(data)
315 except error.UnknownVersion as exc:
315 except error.UnknownVersion as exc:
316 msg = "%sunsupported version: %s (%d bytes)\n"
316 msg = "%sunsupported version: %s (%d bytes)\n"
317 msg %= indent_string, exc.version, len(data)
317 msg %= indent_string, exc.version, len(data)
318 ui.write(msg)
318 ui.write(msg)
319 else:
319 else:
320 msg = "%sversion: %d (%d bytes)\n"
320 msg = "%sversion: %d (%d bytes)\n"
321 msg %= indent_string, version, len(data)
321 msg %= indent_string, version, len(data)
322 ui.write(msg)
322 ui.write(msg)
323 fm = ui.formatter('debugobsolete', opts)
323 fm = ui.formatter('debugobsolete', opts)
324 for rawmarker in sorted(markers):
324 for rawmarker in sorted(markers):
325 m = obsutil.marker(None, rawmarker)
325 m = obsutil.marker(None, rawmarker)
326 fm.startitem()
326 fm.startitem()
327 fm.plain(indent_string)
327 fm.plain(indent_string)
328 cmdutil.showmarker(fm, m)
328 cmdutil.showmarker(fm, m)
329 fm.end()
329 fm.end()
330
330
331 def _debugphaseheads(ui, data, indent=0):
331 def _debugphaseheads(ui, data, indent=0):
332 """display version and markers contained in 'data'"""
332 """display version and markers contained in 'data'"""
333 indent_string = ' ' * indent
333 indent_string = ' ' * indent
334 headsbyphase = phases.binarydecode(data)
334 headsbyphase = phases.binarydecode(data)
335 for phase in phases.allphases:
335 for phase in phases.allphases:
336 for head in headsbyphase[phase]:
336 for head in headsbyphase[phase]:
337 ui.write(indent_string)
337 ui.write(indent_string)
338 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
338 ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
339
339
340 def _quasirepr(thing):
340 def _quasirepr(thing):
341 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
341 if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
342 return '{%s}' % (
342 return '{%s}' % (
343 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
343 b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
344 return pycompat.bytestr(repr(thing))
344 return pycompat.bytestr(repr(thing))
345
345
346 def _debugbundle2(ui, gen, all=None, **opts):
346 def _debugbundle2(ui, gen, all=None, **opts):
347 """lists the contents of a bundle2"""
347 """lists the contents of a bundle2"""
348 if not isinstance(gen, bundle2.unbundle20):
348 if not isinstance(gen, bundle2.unbundle20):
349 raise error.Abort(_('not a bundle2 file'))
349 raise error.Abort(_('not a bundle2 file'))
350 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
350 ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
351 parttypes = opts.get(r'part_type', [])
351 parttypes = opts.get(r'part_type', [])
352 for part in gen.iterparts():
352 for part in gen.iterparts():
353 if parttypes and part.type not in parttypes:
353 if parttypes and part.type not in parttypes:
354 continue
354 continue
355 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
355 ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
356 if part.type == 'changegroup':
356 if part.type == 'changegroup':
357 version = part.params.get('version', '01')
357 version = part.params.get('version', '01')
358 cg = changegroup.getunbundler(version, part, 'UN')
358 cg = changegroup.getunbundler(version, part, 'UN')
359 if not ui.quiet:
359 if not ui.quiet:
360 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
360 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
361 if part.type == 'obsmarkers':
361 if part.type == 'obsmarkers':
362 if not ui.quiet:
362 if not ui.quiet:
363 _debugobsmarkers(ui, part, indent=4, **opts)
363 _debugobsmarkers(ui, part, indent=4, **opts)
364 if part.type == 'phase-heads':
364 if part.type == 'phase-heads':
365 if not ui.quiet:
365 if not ui.quiet:
366 _debugphaseheads(ui, part, indent=4)
366 _debugphaseheads(ui, part, indent=4)
367
367
368 @command('debugbundle',
368 @command('debugbundle',
369 [('a', 'all', None, _('show all details')),
369 [('a', 'all', None, _('show all details')),
370 ('', 'part-type', [], _('show only the named part type')),
370 ('', 'part-type', [], _('show only the named part type')),
371 ('', 'spec', None, _('print the bundlespec of the bundle'))],
371 ('', 'spec', None, _('print the bundlespec of the bundle'))],
372 _('FILE'),
372 _('FILE'),
373 norepo=True)
373 norepo=True)
374 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
374 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
375 """lists the contents of a bundle"""
375 """lists the contents of a bundle"""
376 with hg.openpath(ui, bundlepath) as f:
376 with hg.openpath(ui, bundlepath) as f:
377 if spec:
377 if spec:
378 spec = exchange.getbundlespec(ui, f)
378 spec = exchange.getbundlespec(ui, f)
379 ui.write('%s\n' % spec)
379 ui.write('%s\n' % spec)
380 return
380 return
381
381
382 gen = exchange.readbundle(ui, f, bundlepath)
382 gen = exchange.readbundle(ui, f, bundlepath)
383 if isinstance(gen, bundle2.unbundle20):
383 if isinstance(gen, bundle2.unbundle20):
384 return _debugbundle2(ui, gen, all=all, **opts)
384 return _debugbundle2(ui, gen, all=all, **opts)
385 _debugchangegroup(ui, gen, all=all, **opts)
385 _debugchangegroup(ui, gen, all=all, **opts)
386
386
387 @command('debugcapabilities',
387 @command('debugcapabilities',
388 [], _('PATH'),
388 [], _('PATH'),
389 norepo=True)
389 norepo=True)
390 def debugcapabilities(ui, path, **opts):
390 def debugcapabilities(ui, path, **opts):
391 """lists the capabilities of a remote peer"""
391 """lists the capabilities of a remote peer"""
392 opts = pycompat.byteskwargs(opts)
392 opts = pycompat.byteskwargs(opts)
393 peer = hg.peer(ui, opts, path)
393 peer = hg.peer(ui, opts, path)
394 caps = peer.capabilities()
394 caps = peer.capabilities()
395 ui.write(('Main capabilities:\n'))
395 ui.write(('Main capabilities:\n'))
396 for c in sorted(caps):
396 for c in sorted(caps):
397 ui.write((' %s\n') % c)
397 ui.write((' %s\n') % c)
398 b2caps = bundle2.bundle2caps(peer)
398 b2caps = bundle2.bundle2caps(peer)
399 if b2caps:
399 if b2caps:
400 ui.write(('Bundle2 capabilities:\n'))
400 ui.write(('Bundle2 capabilities:\n'))
401 for key, values in sorted(b2caps.iteritems()):
401 for key, values in sorted(b2caps.iteritems()):
402 ui.write((' %s\n') % key)
402 ui.write((' %s\n') % key)
403 for v in values:
403 for v in values:
404 ui.write((' %s\n') % v)
404 ui.write((' %s\n') % v)
405
405
406 @command('debugcheckstate', [], '')
406 @command('debugcheckstate', [], '')
407 def debugcheckstate(ui, repo):
407 def debugcheckstate(ui, repo):
408 """validate the correctness of the current dirstate"""
408 """validate the correctness of the current dirstate"""
409 parent1, parent2 = repo.dirstate.parents()
409 parent1, parent2 = repo.dirstate.parents()
410 m1 = repo[parent1].manifest()
410 m1 = repo[parent1].manifest()
411 m2 = repo[parent2].manifest()
411 m2 = repo[parent2].manifest()
412 errors = 0
412 errors = 0
413 for f in repo.dirstate:
413 for f in repo.dirstate:
414 state = repo.dirstate[f]
414 state = repo.dirstate[f]
415 if state in "nr" and f not in m1:
415 if state in "nr" and f not in m1:
416 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
416 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
417 errors += 1
417 errors += 1
418 if state in "a" and f in m1:
418 if state in "a" and f in m1:
419 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
419 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
420 errors += 1
420 errors += 1
421 if state in "m" and f not in m1 and f not in m2:
421 if state in "m" and f not in m1 and f not in m2:
422 ui.warn(_("%s in state %s, but not in either manifest\n") %
422 ui.warn(_("%s in state %s, but not in either manifest\n") %
423 (f, state))
423 (f, state))
424 errors += 1
424 errors += 1
425 for f in m1:
425 for f in m1:
426 state = repo.dirstate[f]
426 state = repo.dirstate[f]
427 if state not in "nrm":
427 if state not in "nrm":
428 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
428 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
429 errors += 1
429 errors += 1
430 if errors:
430 if errors:
431 error = _(".hg/dirstate inconsistent with current parent's manifest")
431 error = _(".hg/dirstate inconsistent with current parent's manifest")
432 raise error.Abort(error)
432 raise error.Abort(error)
433
433
434 @command('debugcolor',
434 @command('debugcolor',
435 [('', 'style', None, _('show all configured styles'))],
435 [('', 'style', None, _('show all configured styles'))],
436 'hg debugcolor')
436 'hg debugcolor')
437 def debugcolor(ui, repo, **opts):
437 def debugcolor(ui, repo, **opts):
438 """show available color, effects or style"""
438 """show available color, effects or style"""
439 ui.write(('color mode: %s\n') % ui._colormode)
439 ui.write(('color mode: %s\n') % ui._colormode)
440 if opts.get(r'style'):
440 if opts.get(r'style'):
441 return _debugdisplaystyle(ui)
441 return _debugdisplaystyle(ui)
442 else:
442 else:
443 return _debugdisplaycolor(ui)
443 return _debugdisplaycolor(ui)
444
444
445 def _debugdisplaycolor(ui):
445 def _debugdisplaycolor(ui):
446 ui = ui.copy()
446 ui = ui.copy()
447 ui._styles.clear()
447 ui._styles.clear()
448 for effect in color._activeeffects(ui).keys():
448 for effect in color._activeeffects(ui).keys():
449 ui._styles[effect] = effect
449 ui._styles[effect] = effect
450 if ui._terminfoparams:
450 if ui._terminfoparams:
451 for k, v in ui.configitems('color'):
451 for k, v in ui.configitems('color'):
452 if k.startswith('color.'):
452 if k.startswith('color.'):
453 ui._styles[k] = k[6:]
453 ui._styles[k] = k[6:]
454 elif k.startswith('terminfo.'):
454 elif k.startswith('terminfo.'):
455 ui._styles[k] = k[9:]
455 ui._styles[k] = k[9:]
456 ui.write(_('available colors:\n'))
456 ui.write(_('available colors:\n'))
457 # sort label with a '_' after the other to group '_background' entry.
457 # sort label with a '_' after the other to group '_background' entry.
458 items = sorted(ui._styles.items(),
458 items = sorted(ui._styles.items(),
459 key=lambda i: ('_' in i[0], i[0], i[1]))
459 key=lambda i: ('_' in i[0], i[0], i[1]))
460 for colorname, label in items:
460 for colorname, label in items:
461 ui.write(('%s\n') % colorname, label=label)
461 ui.write(('%s\n') % colorname, label=label)
462
462
463 def _debugdisplaystyle(ui):
463 def _debugdisplaystyle(ui):
464 ui.write(_('available style:\n'))
464 ui.write(_('available style:\n'))
465 width = max(len(s) for s in ui._styles)
465 width = max(len(s) for s in ui._styles)
466 for label, effects in sorted(ui._styles.items()):
466 for label, effects in sorted(ui._styles.items()):
467 ui.write('%s' % label, label=label)
467 ui.write('%s' % label, label=label)
468 if effects:
468 if effects:
469 # 50
469 # 50
470 ui.write(': ')
470 ui.write(': ')
471 ui.write(' ' * (max(0, width - len(label))))
471 ui.write(' ' * (max(0, width - len(label))))
472 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
472 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
473 ui.write('\n')
473 ui.write('\n')
474
474
475 @command('debugcreatestreamclonebundle', [], 'FILE')
475 @command('debugcreatestreamclonebundle', [], 'FILE')
476 def debugcreatestreamclonebundle(ui, repo, fname):
476 def debugcreatestreamclonebundle(ui, repo, fname):
477 """create a stream clone bundle file
477 """create a stream clone bundle file
478
478
479 Stream bundles are special bundles that are essentially archives of
479 Stream bundles are special bundles that are essentially archives of
480 revlog files. They are commonly used for cloning very quickly.
480 revlog files. They are commonly used for cloning very quickly.
481 """
481 """
482 # TODO we may want to turn this into an abort when this functionality
482 # TODO we may want to turn this into an abort when this functionality
483 # is moved into `hg bundle`.
483 # is moved into `hg bundle`.
484 if phases.hassecret(repo):
484 if phases.hassecret(repo):
485 ui.warn(_('(warning: stream clone bundle will contain secret '
485 ui.warn(_('(warning: stream clone bundle will contain secret '
486 'revisions)\n'))
486 'revisions)\n'))
487
487
488 requirements, gen = streamclone.generatebundlev1(repo)
488 requirements, gen = streamclone.generatebundlev1(repo)
489 changegroup.writechunks(ui, gen, fname)
489 changegroup.writechunks(ui, gen, fname)
490
490
491 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
491 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
492
492
493 @command('debugdag',
493 @command('debugdag',
494 [('t', 'tags', None, _('use tags as labels')),
494 [('t', 'tags', None, _('use tags as labels')),
495 ('b', 'branches', None, _('annotate with branch names')),
495 ('b', 'branches', None, _('annotate with branch names')),
496 ('', 'dots', None, _('use dots for runs')),
496 ('', 'dots', None, _('use dots for runs')),
497 ('s', 'spaces', None, _('separate elements by spaces'))],
497 ('s', 'spaces', None, _('separate elements by spaces'))],
498 _('[OPTION]... [FILE [REV]...]'),
498 _('[OPTION]... [FILE [REV]...]'),
499 optionalrepo=True)
499 optionalrepo=True)
500 def debugdag(ui, repo, file_=None, *revs, **opts):
500 def debugdag(ui, repo, file_=None, *revs, **opts):
501 """format the changelog or an index DAG as a concise textual description
501 """format the changelog or an index DAG as a concise textual description
502
502
503 If you pass a revlog index, the revlog's DAG is emitted. If you list
503 If you pass a revlog index, the revlog's DAG is emitted. If you list
504 revision numbers, they get labeled in the output as rN.
504 revision numbers, they get labeled in the output as rN.
505
505
506 Otherwise, the changelog DAG of the current repo is emitted.
506 Otherwise, the changelog DAG of the current repo is emitted.
507 """
507 """
508 spaces = opts.get(r'spaces')
508 spaces = opts.get(r'spaces')
509 dots = opts.get(r'dots')
509 dots = opts.get(r'dots')
510 if file_:
510 if file_:
511 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
511 rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
512 file_)
512 file_)
513 revs = set((int(r) for r in revs))
513 revs = set((int(r) for r in revs))
514 def events():
514 def events():
515 for r in rlog:
515 for r in rlog:
516 yield 'n', (r, list(p for p in rlog.parentrevs(r)
516 yield 'n', (r, list(p for p in rlog.parentrevs(r)
517 if p != -1))
517 if p != -1))
518 if r in revs:
518 if r in revs:
519 yield 'l', (r, "r%i" % r)
519 yield 'l', (r, "r%i" % r)
520 elif repo:
520 elif repo:
521 cl = repo.changelog
521 cl = repo.changelog
522 tags = opts.get(r'tags')
522 tags = opts.get(r'tags')
523 branches = opts.get(r'branches')
523 branches = opts.get(r'branches')
524 if tags:
524 if tags:
525 labels = {}
525 labels = {}
526 for l, n in repo.tags().items():
526 for l, n in repo.tags().items():
527 labels.setdefault(cl.rev(n), []).append(l)
527 labels.setdefault(cl.rev(n), []).append(l)
528 def events():
528 def events():
529 b = "default"
529 b = "default"
530 for r in cl:
530 for r in cl:
531 if branches:
531 if branches:
532 newb = cl.read(cl.node(r))[5]['branch']
532 newb = cl.read(cl.node(r))[5]['branch']
533 if newb != b:
533 if newb != b:
534 yield 'a', newb
534 yield 'a', newb
535 b = newb
535 b = newb
536 yield 'n', (r, list(p for p in cl.parentrevs(r)
536 yield 'n', (r, list(p for p in cl.parentrevs(r)
537 if p != -1))
537 if p != -1))
538 if tags:
538 if tags:
539 ls = labels.get(r)
539 ls = labels.get(r)
540 if ls:
540 if ls:
541 for l in ls:
541 for l in ls:
542 yield 'l', (r, l)
542 yield 'l', (r, l)
543 else:
543 else:
544 raise error.Abort(_('need repo for changelog dag'))
544 raise error.Abort(_('need repo for changelog dag'))
545
545
546 for line in dagparser.dagtextlines(events(),
546 for line in dagparser.dagtextlines(events(),
547 addspaces=spaces,
547 addspaces=spaces,
548 wraplabels=True,
548 wraplabels=True,
549 wrapannotations=True,
549 wrapannotations=True,
550 wrapnonlinear=dots,
550 wrapnonlinear=dots,
551 usedots=dots,
551 usedots=dots,
552 maxlinewidth=70):
552 maxlinewidth=70):
553 ui.write(line)
553 ui.write(line)
554 ui.write("\n")
554 ui.write("\n")
555
555
556 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
556 @command('debugdata', cmdutil.debugrevlogopts, _('-c|-m|FILE REV'))
557 def debugdata(ui, repo, file_, rev=None, **opts):
557 def debugdata(ui, repo, file_, rev=None, **opts):
558 """dump the contents of a data file revision"""
558 """dump the contents of a data file revision"""
559 opts = pycompat.byteskwargs(opts)
559 opts = pycompat.byteskwargs(opts)
560 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
560 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
561 if rev is not None:
561 if rev is not None:
562 raise error.CommandError('debugdata', _('invalid arguments'))
562 raise error.CommandError('debugdata', _('invalid arguments'))
563 file_, rev = None, file_
563 file_, rev = None, file_
564 elif rev is None:
564 elif rev is None:
565 raise error.CommandError('debugdata', _('invalid arguments'))
565 raise error.CommandError('debugdata', _('invalid arguments'))
566 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
566 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
567 try:
567 try:
568 ui.write(r.revision(r.lookup(rev), raw=True))
568 ui.write(r.revision(r.lookup(rev), raw=True))
569 except KeyError:
569 except KeyError:
570 raise error.Abort(_('invalid revision identifier %s') % rev)
570 raise error.Abort(_('invalid revision identifier %s') % rev)
571
571
572 @command('debugdate',
572 @command('debugdate',
573 [('e', 'extended', None, _('try extended date formats'))],
573 [('e', 'extended', None, _('try extended date formats'))],
574 _('[-e] DATE [RANGE]'),
574 _('[-e] DATE [RANGE]'),
575 norepo=True, optionalrepo=True)
575 norepo=True, optionalrepo=True)
576 def debugdate(ui, date, range=None, **opts):
576 def debugdate(ui, date, range=None, **opts):
577 """parse and display a date"""
577 """parse and display a date"""
578 if opts[r"extended"]:
578 if opts[r"extended"]:
579 d = dateutil.parsedate(date, util.extendeddateformats)
579 d = dateutil.parsedate(date, util.extendeddateformats)
580 else:
580 else:
581 d = dateutil.parsedate(date)
581 d = dateutil.parsedate(date)
582 ui.write(("internal: %d %d\n") % d)
582 ui.write(("internal: %d %d\n") % d)
583 ui.write(("standard: %s\n") % dateutil.datestr(d))
583 ui.write(("standard: %s\n") % dateutil.datestr(d))
584 if range:
584 if range:
585 m = dateutil.matchdate(range)
585 m = dateutil.matchdate(range)
586 ui.write(("match: %s\n") % m(d[0]))
586 ui.write(("match: %s\n") % m(d[0]))
587
587
588 @command('debugdeltachain',
588 @command('debugdeltachain',
589 cmdutil.debugrevlogopts + cmdutil.formatteropts,
589 cmdutil.debugrevlogopts + cmdutil.formatteropts,
590 _('-c|-m|FILE'),
590 _('-c|-m|FILE'),
591 optionalrepo=True)
591 optionalrepo=True)
592 def debugdeltachain(ui, repo, file_=None, **opts):
592 def debugdeltachain(ui, repo, file_=None, **opts):
593 """dump information about delta chains in a revlog
593 """dump information about delta chains in a revlog
594
594
595 Output can be templatized. Available template keywords are:
595 Output can be templatized. Available template keywords are:
596
596
597 :``rev``: revision number
597 :``rev``: revision number
598 :``chainid``: delta chain identifier (numbered by unique base)
598 :``chainid``: delta chain identifier (numbered by unique base)
599 :``chainlen``: delta chain length to this revision
599 :``chainlen``: delta chain length to this revision
600 :``prevrev``: previous revision in delta chain
600 :``prevrev``: previous revision in delta chain
601 :``deltatype``: role of delta / how it was computed
601 :``deltatype``: role of delta / how it was computed
602 :``compsize``: compressed size of revision
602 :``compsize``: compressed size of revision
603 :``uncompsize``: uncompressed size of revision
603 :``uncompsize``: uncompressed size of revision
604 :``chainsize``: total size of compressed revisions in chain
604 :``chainsize``: total size of compressed revisions in chain
605 :``chainratio``: total chain size divided by uncompressed revision size
605 :``chainratio``: total chain size divided by uncompressed revision size
606 (new delta chains typically start at ratio 2.00)
606 (new delta chains typically start at ratio 2.00)
607 :``lindist``: linear distance from base revision in delta chain to end
607 :``lindist``: linear distance from base revision in delta chain to end
608 of this revision
608 of this revision
609 :``extradist``: total size of revisions not part of this delta chain from
609 :``extradist``: total size of revisions not part of this delta chain from
610 base of delta chain to end of this revision; a measurement
610 base of delta chain to end of this revision; a measurement
611 of how much extra data we need to read/seek across to read
611 of how much extra data we need to read/seek across to read
612 the delta chain for this revision
612 the delta chain for this revision
613 :``extraratio``: extradist divided by chainsize; another representation of
613 :``extraratio``: extradist divided by chainsize; another representation of
614 how much unrelated data is needed to load this delta chain
614 how much unrelated data is needed to load this delta chain
615
615
616 If the repository is configured to use the sparse read, additional keywords
616 If the repository is configured to use the sparse read, additional keywords
617 are available:
617 are available:
618
618
619 :``readsize``: total size of data read from the disk for a revision
619 :``readsize``: total size of data read from the disk for a revision
620 (sum of the sizes of all the blocks)
620 (sum of the sizes of all the blocks)
621 :``largestblock``: size of the largest block of data read from the disk
621 :``largestblock``: size of the largest block of data read from the disk
622 :``readdensity``: density of useful bytes in the data read from the disk
622 :``readdensity``: density of useful bytes in the data read from the disk
623 :``srchunks``: in how many data hunks the whole revision would be read
623 :``srchunks``: in how many data hunks the whole revision would be read
624
624
625 The sparse read can be enabled with experimental.sparse-read = True
625 The sparse read can be enabled with experimental.sparse-read = True
626 """
626 """
627 opts = pycompat.byteskwargs(opts)
627 opts = pycompat.byteskwargs(opts)
628 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
628 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
629 index = r.index
629 index = r.index
630 generaldelta = r.version & revlog.FLAG_GENERALDELTA
630 generaldelta = r.version & revlog.FLAG_GENERALDELTA
631 withsparseread = getattr(r, '_withsparseread', False)
631 withsparseread = getattr(r, '_withsparseread', False)
632
632
633 def revinfo(rev):
633 def revinfo(rev):
634 e = index[rev]
634 e = index[rev]
635 compsize = e[1]
635 compsize = e[1]
636 uncompsize = e[2]
636 uncompsize = e[2]
637 chainsize = 0
637 chainsize = 0
638
638
639 if generaldelta:
639 if generaldelta:
640 if e[3] == e[5]:
640 if e[3] == e[5]:
641 deltatype = 'p1'
641 deltatype = 'p1'
642 elif e[3] == e[6]:
642 elif e[3] == e[6]:
643 deltatype = 'p2'
643 deltatype = 'p2'
644 elif e[3] == rev - 1:
644 elif e[3] == rev - 1:
645 deltatype = 'prev'
645 deltatype = 'prev'
646 elif e[3] == rev:
646 elif e[3] == rev:
647 deltatype = 'base'
647 deltatype = 'base'
648 else:
648 else:
649 deltatype = 'other'
649 deltatype = 'other'
650 else:
650 else:
651 if e[3] == rev:
651 if e[3] == rev:
652 deltatype = 'base'
652 deltatype = 'base'
653 else:
653 else:
654 deltatype = 'prev'
654 deltatype = 'prev'
655
655
656 chain = r._deltachain(rev)[0]
656 chain = r._deltachain(rev)[0]
657 for iterrev in chain:
657 for iterrev in chain:
658 e = index[iterrev]
658 e = index[iterrev]
659 chainsize += e[1]
659 chainsize += e[1]
660
660
661 return compsize, uncompsize, deltatype, chain, chainsize
661 return compsize, uncompsize, deltatype, chain, chainsize
662
662
663 fm = ui.formatter('debugdeltachain', opts)
663 fm = ui.formatter('debugdeltachain', opts)
664
664
665 fm.plain(' rev chain# chainlen prev delta '
665 fm.plain(' rev chain# chainlen prev delta '
666 'size rawsize chainsize ratio lindist extradist '
666 'size rawsize chainsize ratio lindist extradist '
667 'extraratio')
667 'extraratio')
668 if withsparseread:
668 if withsparseread:
669 fm.plain(' readsize largestblk rddensity srchunks')
669 fm.plain(' readsize largestblk rddensity srchunks')
670 fm.plain('\n')
670 fm.plain('\n')
671
671
672 chainbases = {}
672 chainbases = {}
673 for rev in r:
673 for rev in r:
674 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
674 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
675 chainbase = chain[0]
675 chainbase = chain[0]
676 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
676 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
677 start = r.start
677 start = r.start
678 length = r.length
678 length = r.length
679 basestart = start(chainbase)
679 basestart = start(chainbase)
680 revstart = start(rev)
680 revstart = start(rev)
681 lineardist = revstart + comp - basestart
681 lineardist = revstart + comp - basestart
682 extradist = lineardist - chainsize
682 extradist = lineardist - chainsize
683 try:
683 try:
684 prevrev = chain[-2]
684 prevrev = chain[-2]
685 except IndexError:
685 except IndexError:
686 prevrev = -1
686 prevrev = -1
687
687
688 chainratio = float(chainsize) / float(uncomp)
688 chainratio = float(chainsize) / float(uncomp)
689 extraratio = float(extradist) / float(chainsize)
689 extraratio = float(extradist) / float(chainsize)
690
690
691 fm.startitem()
691 fm.startitem()
692 fm.write('rev chainid chainlen prevrev deltatype compsize '
692 fm.write('rev chainid chainlen prevrev deltatype compsize '
693 'uncompsize chainsize chainratio lindist extradist '
693 'uncompsize chainsize chainratio lindist extradist '
694 'extraratio',
694 'extraratio',
695 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
695 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
696 rev, chainid, len(chain), prevrev, deltatype, comp,
696 rev, chainid, len(chain), prevrev, deltatype, comp,
697 uncomp, chainsize, chainratio, lineardist, extradist,
697 uncomp, chainsize, chainratio, lineardist, extradist,
698 extraratio,
698 extraratio,
699 rev=rev, chainid=chainid, chainlen=len(chain),
699 rev=rev, chainid=chainid, chainlen=len(chain),
700 prevrev=prevrev, deltatype=deltatype, compsize=comp,
700 prevrev=prevrev, deltatype=deltatype, compsize=comp,
701 uncompsize=uncomp, chainsize=chainsize,
701 uncompsize=uncomp, chainsize=chainsize,
702 chainratio=chainratio, lindist=lineardist,
702 chainratio=chainratio, lindist=lineardist,
703 extradist=extradist, extraratio=extraratio)
703 extradist=extradist, extraratio=extraratio)
704 if withsparseread:
704 if withsparseread:
705 readsize = 0
705 readsize = 0
706 largestblock = 0
706 largestblock = 0
707 srchunks = 0
707 srchunks = 0
708
708
709 for revschunk in revlog._slicechunk(r, chain):
709 for revschunk in revlog._slicechunk(r, chain):
710 srchunks += 1
710 srchunks += 1
711 blkend = start(revschunk[-1]) + length(revschunk[-1])
711 blkend = start(revschunk[-1]) + length(revschunk[-1])
712 blksize = blkend - start(revschunk[0])
712 blksize = blkend - start(revschunk[0])
713
713
714 readsize += blksize
714 readsize += blksize
715 if largestblock < blksize:
715 if largestblock < blksize:
716 largestblock = blksize
716 largestblock = blksize
717
717
718 readdensity = float(chainsize) / float(readsize)
718 readdensity = float(chainsize) / float(readsize)
719
719
720 fm.write('readsize largestblock readdensity srchunks',
720 fm.write('readsize largestblock readdensity srchunks',
721 ' %10d %10d %9.5f %8d',
721 ' %10d %10d %9.5f %8d',
722 readsize, largestblock, readdensity, srchunks,
722 readsize, largestblock, readdensity, srchunks,
723 readsize=readsize, largestblock=largestblock,
723 readsize=readsize, largestblock=largestblock,
724 readdensity=readdensity, srchunks=srchunks)
724 readdensity=readdensity, srchunks=srchunks)
725
725
726 fm.plain('\n')
726 fm.plain('\n')
727
727
728 fm.end()
728 fm.end()
729
729
730 @command('debugdirstate|debugstate',
730 @command('debugdirstate|debugstate',
731 [('', 'nodates', None, _('do not display the saved mtime')),
731 [('', 'nodates', None, _('do not display the saved mtime')),
732 ('', 'datesort', None, _('sort by saved mtime'))],
732 ('', 'datesort', None, _('sort by saved mtime'))],
733 _('[OPTION]...'))
733 _('[OPTION]...'))
734 def debugstate(ui, repo, **opts):
734 def debugstate(ui, repo, **opts):
735 """show the contents of the current dirstate"""
735 """show the contents of the current dirstate"""
736
736
737 nodates = opts.get(r'nodates')
737 nodates = opts.get(r'nodates')
738 datesort = opts.get(r'datesort')
738 datesort = opts.get(r'datesort')
739
739
740 timestr = ""
740 timestr = ""
741 if datesort:
741 if datesort:
742 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
742 keyfunc = lambda x: (x[1][3], x[0]) # sort by mtime, then by filename
743 else:
743 else:
744 keyfunc = None # sort by filename
744 keyfunc = None # sort by filename
745 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
745 for file_, ent in sorted(repo.dirstate._map.iteritems(), key=keyfunc):
746 if ent[3] == -1:
746 if ent[3] == -1:
747 timestr = 'unset '
747 timestr = 'unset '
748 elif nodates:
748 elif nodates:
749 timestr = 'set '
749 timestr = 'set '
750 else:
750 else:
751 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
751 timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
752 time.localtime(ent[3]))
752 time.localtime(ent[3]))
753 timestr = encoding.strtolocal(timestr)
753 timestr = encoding.strtolocal(timestr)
754 if ent[1] & 0o20000:
754 if ent[1] & 0o20000:
755 mode = 'lnk'
755 mode = 'lnk'
756 else:
756 else:
757 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
757 mode = '%3o' % (ent[1] & 0o777 & ~util.umask)
758 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
758 ui.write("%c %s %10d %s%s\n" % (ent[0], mode, ent[2], timestr, file_))
759 for f in repo.dirstate.copies():
759 for f in repo.dirstate.copies():
760 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
760 ui.write(_("copy: %s -> %s\n") % (repo.dirstate.copied(f), f))
761
761
762 @command('debugdiscovery',
762 @command('debugdiscovery',
763 [('', 'old', None, _('use old-style discovery')),
763 [('', 'old', None, _('use old-style discovery')),
764 ('', 'nonheads', None,
764 ('', 'nonheads', None,
765 _('use old-style discovery with non-heads included')),
765 _('use old-style discovery with non-heads included')),
766 ('', 'rev', [], 'restrict discovery to this set of revs'),
766 ('', 'rev', [], 'restrict discovery to this set of revs'),
767 ] + cmdutil.remoteopts,
767 ] + cmdutil.remoteopts,
768 _('[--rev REV] [OTHER]'))
768 _('[--rev REV] [OTHER]'))
769 def debugdiscovery(ui, repo, remoteurl="default", **opts):
769 def debugdiscovery(ui, repo, remoteurl="default", **opts):
770 """runs the changeset discovery protocol in isolation"""
770 """runs the changeset discovery protocol in isolation"""
771 opts = pycompat.byteskwargs(opts)
771 opts = pycompat.byteskwargs(opts)
772 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
772 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl))
773 remote = hg.peer(repo, opts, remoteurl)
773 remote = hg.peer(repo, opts, remoteurl)
774 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
774 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
775
775
776 # make sure tests are repeatable
776 # make sure tests are repeatable
777 random.seed(12323)
777 random.seed(12323)
778
778
779 def doit(pushedrevs, remoteheads, remote=remote):
779 def doit(pushedrevs, remoteheads, remote=remote):
780 if opts.get('old'):
780 if opts.get('old'):
781 if not util.safehasattr(remote, 'branches'):
781 if not util.safehasattr(remote, 'branches'):
782 # enable in-client legacy support
782 # enable in-client legacy support
783 remote = localrepo.locallegacypeer(remote.local())
783 remote = localrepo.locallegacypeer(remote.local())
784 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
784 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
785 force=True)
785 force=True)
786 common = set(common)
786 common = set(common)
787 if not opts.get('nonheads'):
787 if not opts.get('nonheads'):
788 ui.write(("unpruned common: %s\n") %
788 ui.write(("unpruned common: %s\n") %
789 " ".join(sorted(short(n) for n in common)))
789 " ".join(sorted(short(n) for n in common)))
790 dag = dagutil.revlogdag(repo.changelog)
790 dag = dagutil.revlogdag(repo.changelog)
791 all = dag.ancestorset(dag.internalizeall(common))
791 all = dag.ancestorset(dag.internalizeall(common))
792 common = dag.externalizeall(dag.headsetofconnecteds(all))
792 common = dag.externalizeall(dag.headsetofconnecteds(all))
793 else:
793 else:
794 nodes = None
794 nodes = None
795 if pushedrevs:
795 if pushedrevs:
796 revs = scmutil.revrange(repo, pushedrevs)
796 revs = scmutil.revrange(repo, pushedrevs)
797 nodes = [repo[r].node() for r in revs]
797 nodes = [repo[r].node() for r in revs]
798 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
798 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote,
799 ancestorsof=nodes)
799 ancestorsof=nodes)
800 common = set(common)
800 common = set(common)
801 rheads = set(hds)
801 rheads = set(hds)
802 lheads = set(repo.heads())
802 lheads = set(repo.heads())
803 ui.write(("common heads: %s\n") %
803 ui.write(("common heads: %s\n") %
804 " ".join(sorted(short(n) for n in common)))
804 " ".join(sorted(short(n) for n in common)))
805 if lheads <= common:
805 if lheads <= common:
806 ui.write(("local is subset\n"))
806 ui.write(("local is subset\n"))
807 elif rheads <= common:
807 elif rheads <= common:
808 ui.write(("remote is subset\n"))
808 ui.write(("remote is subset\n"))
809
809
810 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
810 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches, revs=None)
811 localrevs = opts['rev']
811 localrevs = opts['rev']
812 doit(localrevs, remoterevs)
812 doit(localrevs, remoterevs)
813
813
814 _chunksize = 4 << 10
814 _chunksize = 4 << 10
815
815
816 @command('debugdownload',
816 @command('debugdownload',
817 [
817 [
818 ('o', 'output', '', _('path')),
818 ('o', 'output', '', _('path')),
819 ],
819 ],
820 optionalrepo=True)
820 optionalrepo=True)
821 def debugdownload(ui, repo, url, output=None, **opts):
821 def debugdownload(ui, repo, url, output=None, **opts):
822 """download a resource using Mercurial logic and config
822 """download a resource using Mercurial logic and config
823 """
823 """
824 fh = urlmod.open(ui, url, output)
824 fh = urlmod.open(ui, url, output)
825
825
826 dest = ui
826 dest = ui
827 if output:
827 if output:
828 dest = open(output, "wb", _chunksize)
828 dest = open(output, "wb", _chunksize)
829 try:
829 try:
830 data = fh.read(_chunksize)
830 data = fh.read(_chunksize)
831 while data:
831 while data:
832 dest.write(data)
832 dest.write(data)
833 data = fh.read(_chunksize)
833 data = fh.read(_chunksize)
834 finally:
834 finally:
835 if output:
835 if output:
836 dest.close()
836 dest.close()
837
837
838 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
838 @command('debugextensions', cmdutil.formatteropts, [], norepo=True)
839 def debugextensions(ui, **opts):
839 def debugextensions(ui, **opts):
840 '''show information about active extensions'''
840 '''show information about active extensions'''
841 opts = pycompat.byteskwargs(opts)
841 opts = pycompat.byteskwargs(opts)
842 exts = extensions.extensions(ui)
842 exts = extensions.extensions(ui)
843 hgver = util.version()
843 hgver = util.version()
844 fm = ui.formatter('debugextensions', opts)
844 fm = ui.formatter('debugextensions', opts)
845 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
845 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
846 isinternal = extensions.ismoduleinternal(extmod)
846 isinternal = extensions.ismoduleinternal(extmod)
847 extsource = pycompat.fsencode(extmod.__file__)
847 extsource = pycompat.fsencode(extmod.__file__)
848 if isinternal:
848 if isinternal:
849 exttestedwith = [] # never expose magic string to users
849 exttestedwith = [] # never expose magic string to users
850 else:
850 else:
851 exttestedwith = getattr(extmod, 'testedwith', '').split()
851 exttestedwith = getattr(extmod, 'testedwith', '').split()
852 extbuglink = getattr(extmod, 'buglink', None)
852 extbuglink = getattr(extmod, 'buglink', None)
853
853
854 fm.startitem()
854 fm.startitem()
855
855
856 if ui.quiet or ui.verbose:
856 if ui.quiet or ui.verbose:
857 fm.write('name', '%s\n', extname)
857 fm.write('name', '%s\n', extname)
858 else:
858 else:
859 fm.write('name', '%s', extname)
859 fm.write('name', '%s', extname)
860 if isinternal or hgver in exttestedwith:
860 if isinternal or hgver in exttestedwith:
861 fm.plain('\n')
861 fm.plain('\n')
862 elif not exttestedwith:
862 elif not exttestedwith:
863 fm.plain(_(' (untested!)\n'))
863 fm.plain(_(' (untested!)\n'))
864 else:
864 else:
865 lasttestedversion = exttestedwith[-1]
865 lasttestedversion = exttestedwith[-1]
866 fm.plain(' (%s!)\n' % lasttestedversion)
866 fm.plain(' (%s!)\n' % lasttestedversion)
867
867
868 fm.condwrite(ui.verbose and extsource, 'source',
868 fm.condwrite(ui.verbose and extsource, 'source',
869 _(' location: %s\n'), extsource or "")
869 _(' location: %s\n'), extsource or "")
870
870
871 if ui.verbose:
871 if ui.verbose:
872 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
872 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
873 fm.data(bundled=isinternal)
873 fm.data(bundled=isinternal)
874
874
875 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
875 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
876 _(' tested with: %s\n'),
876 _(' tested with: %s\n'),
877 fm.formatlist(exttestedwith, name='ver'))
877 fm.formatlist(exttestedwith, name='ver'))
878
878
879 fm.condwrite(ui.verbose and extbuglink, 'buglink',
879 fm.condwrite(ui.verbose and extbuglink, 'buglink',
880 _(' bug reporting: %s\n'), extbuglink or "")
880 _(' bug reporting: %s\n'), extbuglink or "")
881
881
882 fm.end()
882 fm.end()
883
883
884 @command('debugfileset',
884 @command('debugfileset',
885 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
885 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
886 _('[-r REV] FILESPEC'))
886 _('[-r REV] FILESPEC'))
887 def debugfileset(ui, repo, expr, **opts):
887 def debugfileset(ui, repo, expr, **opts):
888 '''parse and apply a fileset specification'''
888 '''parse and apply a fileset specification'''
889 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
889 ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
890 if ui.verbose:
890 if ui.verbose:
891 tree = fileset.parse(expr)
891 tree = fileset.parse(expr)
892 ui.note(fileset.prettyformat(tree), "\n")
892 ui.note(fileset.prettyformat(tree), "\n")
893
893
894 for f in ctx.getfileset(expr):
894 for f in ctx.getfileset(expr):
895 ui.write("%s\n" % f)
895 ui.write("%s\n" % f)
896
896
897 @command('debugformat',
897 @command('debugformat',
898 [] + cmdutil.formatteropts,
898 [] + cmdutil.formatteropts,
899 _(''))
899 _(''))
900 def debugformat(ui, repo, **opts):
900 def debugformat(ui, repo, **opts):
901 """display format information about the current repository
901 """display format information about the current repository
902
902
903 Use --verbose to get extra information about current config value and
903 Use --verbose to get extra information about current config value and
904 Mercurial default."""
904 Mercurial default."""
905 opts = pycompat.byteskwargs(opts)
905 opts = pycompat.byteskwargs(opts)
906 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
906 maxvariantlength = max(len(fv.name) for fv in upgrade.allformatvariant)
907 maxvariantlength = max(len('format-variant'), maxvariantlength)
907 maxvariantlength = max(len('format-variant'), maxvariantlength)
908
908
909 def makeformatname(name):
909 def makeformatname(name):
910 return '%s:' + (' ' * (maxvariantlength - len(name)))
910 return '%s:' + (' ' * (maxvariantlength - len(name)))
911
911
912 fm = ui.formatter('debugformat', opts)
912 fm = ui.formatter('debugformat', opts)
913 if fm.isplain():
913 if fm.isplain():
914 def formatvalue(value):
914 def formatvalue(value):
915 if util.safehasattr(value, 'startswith'):
915 if util.safehasattr(value, 'startswith'):
916 return value
916 return value
917 if value:
917 if value:
918 return 'yes'
918 return 'yes'
919 else:
919 else:
920 return 'no'
920 return 'no'
921 else:
921 else:
922 formatvalue = pycompat.identity
922 formatvalue = pycompat.identity
923
923
924 fm.plain('format-variant')
924 fm.plain('format-variant')
925 fm.plain(' ' * (maxvariantlength - len('format-variant')))
925 fm.plain(' ' * (maxvariantlength - len('format-variant')))
926 fm.plain(' repo')
926 fm.plain(' repo')
927 if ui.verbose:
927 if ui.verbose:
928 fm.plain(' config default')
928 fm.plain(' config default')
929 fm.plain('\n')
929 fm.plain('\n')
930 for fv in upgrade.allformatvariant:
930 for fv in upgrade.allformatvariant:
931 fm.startitem()
931 fm.startitem()
932 repovalue = fv.fromrepo(repo)
932 repovalue = fv.fromrepo(repo)
933 configvalue = fv.fromconfig(repo)
933 configvalue = fv.fromconfig(repo)
934
934
935 if repovalue != configvalue:
935 if repovalue != configvalue:
936 namelabel = 'formatvariant.name.mismatchconfig'
936 namelabel = 'formatvariant.name.mismatchconfig'
937 repolabel = 'formatvariant.repo.mismatchconfig'
937 repolabel = 'formatvariant.repo.mismatchconfig'
938 elif repovalue != fv.default:
938 elif repovalue != fv.default:
939 namelabel = 'formatvariant.name.mismatchdefault'
939 namelabel = 'formatvariant.name.mismatchdefault'
940 repolabel = 'formatvariant.repo.mismatchdefault'
940 repolabel = 'formatvariant.repo.mismatchdefault'
941 else:
941 else:
942 namelabel = 'formatvariant.name.uptodate'
942 namelabel = 'formatvariant.name.uptodate'
943 repolabel = 'formatvariant.repo.uptodate'
943 repolabel = 'formatvariant.repo.uptodate'
944
944
945 fm.write('name', makeformatname(fv.name), fv.name,
945 fm.write('name', makeformatname(fv.name), fv.name,
946 label=namelabel)
946 label=namelabel)
947 fm.write('repo', ' %3s', formatvalue(repovalue),
947 fm.write('repo', ' %3s', formatvalue(repovalue),
948 label=repolabel)
948 label=repolabel)
949 if fv.default != configvalue:
949 if fv.default != configvalue:
950 configlabel = 'formatvariant.config.special'
950 configlabel = 'formatvariant.config.special'
951 else:
951 else:
952 configlabel = 'formatvariant.config.default'
952 configlabel = 'formatvariant.config.default'
953 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
953 fm.condwrite(ui.verbose, 'config', ' %6s', formatvalue(configvalue),
954 label=configlabel)
954 label=configlabel)
955 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
955 fm.condwrite(ui.verbose, 'default', ' %7s', formatvalue(fv.default),
956 label='formatvariant.default')
956 label='formatvariant.default')
957 fm.plain('\n')
957 fm.plain('\n')
958 fm.end()
958 fm.end()
959
959
960 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
960 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
961 def debugfsinfo(ui, path="."):
961 def debugfsinfo(ui, path="."):
962 """show information detected about current filesystem"""
962 """show information detected about current filesystem"""
963 ui.write(('path: %s\n') % path)
963 ui.write(('path: %s\n') % path)
964 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
964 ui.write(('mounted on: %s\n') % (util.getfsmountpoint(path) or '(unknown)'))
965 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
965 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
966 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
966 ui.write(('fstype: %s\n') % (util.getfstype(path) or '(unknown)'))
967 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
967 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
968 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
968 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
969 casesensitive = '(unknown)'
969 casesensitive = '(unknown)'
970 try:
970 try:
971 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
971 with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
972 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
972 casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
973 except OSError:
973 except OSError:
974 pass
974 pass
975 ui.write(('case-sensitive: %s\n') % casesensitive)
975 ui.write(('case-sensitive: %s\n') % casesensitive)
976
976
977 @command('debuggetbundle',
977 @command('debuggetbundle',
978 [('H', 'head', [], _('id of head node'), _('ID')),
978 [('H', 'head', [], _('id of head node'), _('ID')),
979 ('C', 'common', [], _('id of common node'), _('ID')),
979 ('C', 'common', [], _('id of common node'), _('ID')),
980 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
980 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
981 _('REPO FILE [-H|-C ID]...'),
981 _('REPO FILE [-H|-C ID]...'),
982 norepo=True)
982 norepo=True)
983 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
983 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
984 """retrieves a bundle from a repo
984 """retrieves a bundle from a repo
985
985
986 Every ID must be a full-length hex node id string. Saves the bundle to the
986 Every ID must be a full-length hex node id string. Saves the bundle to the
987 given file.
987 given file.
988 """
988 """
989 opts = pycompat.byteskwargs(opts)
989 opts = pycompat.byteskwargs(opts)
990 repo = hg.peer(ui, opts, repopath)
990 repo = hg.peer(ui, opts, repopath)
991 if not repo.capable('getbundle'):
991 if not repo.capable('getbundle'):
992 raise error.Abort("getbundle() not supported by target repository")
992 raise error.Abort("getbundle() not supported by target repository")
993 args = {}
993 args = {}
994 if common:
994 if common:
995 args[r'common'] = [bin(s) for s in common]
995 args[r'common'] = [bin(s) for s in common]
996 if head:
996 if head:
997 args[r'heads'] = [bin(s) for s in head]
997 args[r'heads'] = [bin(s) for s in head]
998 # TODO: get desired bundlecaps from command line.
998 # TODO: get desired bundlecaps from command line.
999 args[r'bundlecaps'] = None
999 args[r'bundlecaps'] = None
1000 bundle = repo.getbundle('debug', **args)
1000 bundle = repo.getbundle('debug', **args)
1001
1001
1002 bundletype = opts.get('type', 'bzip2').lower()
1002 bundletype = opts.get('type', 'bzip2').lower()
1003 btypes = {'none': 'HG10UN',
1003 btypes = {'none': 'HG10UN',
1004 'bzip2': 'HG10BZ',
1004 'bzip2': 'HG10BZ',
1005 'gzip': 'HG10GZ',
1005 'gzip': 'HG10GZ',
1006 'bundle2': 'HG20'}
1006 'bundle2': 'HG20'}
1007 bundletype = btypes.get(bundletype)
1007 bundletype = btypes.get(bundletype)
1008 if bundletype not in bundle2.bundletypes:
1008 if bundletype not in bundle2.bundletypes:
1009 raise error.Abort(_('unknown bundle type specified with --type'))
1009 raise error.Abort(_('unknown bundle type specified with --type'))
1010 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1010 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
1011
1011
1012 @command('debugignore', [], '[FILE]')
1012 @command('debugignore', [], '[FILE]')
1013 def debugignore(ui, repo, *files, **opts):
1013 def debugignore(ui, repo, *files, **opts):
1014 """display the combined ignore pattern and information about ignored files
1014 """display the combined ignore pattern and information about ignored files
1015
1015
1016 With no argument display the combined ignore pattern.
1016 With no argument display the combined ignore pattern.
1017
1017
1018 Given space separated file names, shows if the given file is ignored and
1018 Given space separated file names, shows if the given file is ignored and
1019 if so, show the ignore rule (file and line number) that matched it.
1019 if so, show the ignore rule (file and line number) that matched it.
1020 """
1020 """
1021 ignore = repo.dirstate._ignore
1021 ignore = repo.dirstate._ignore
1022 if not files:
1022 if not files:
1023 # Show all the patterns
1023 # Show all the patterns
1024 ui.write("%s\n" % pycompat.byterepr(ignore))
1024 ui.write("%s\n" % pycompat.byterepr(ignore))
1025 else:
1025 else:
1026 m = scmutil.match(repo[None], pats=files)
1026 m = scmutil.match(repo[None], pats=files)
1027 for f in m.files():
1027 for f in m.files():
1028 nf = util.normpath(f)
1028 nf = util.normpath(f)
1029 ignored = None
1029 ignored = None
1030 ignoredata = None
1030 ignoredata = None
1031 if nf != '.':
1031 if nf != '.':
1032 if ignore(nf):
1032 if ignore(nf):
1033 ignored = nf
1033 ignored = nf
1034 ignoredata = repo.dirstate._ignorefileandline(nf)
1034 ignoredata = repo.dirstate._ignorefileandline(nf)
1035 else:
1035 else:
1036 for p in util.finddirs(nf):
1036 for p in util.finddirs(nf):
1037 if ignore(p):
1037 if ignore(p):
1038 ignored = p
1038 ignored = p
1039 ignoredata = repo.dirstate._ignorefileandline(p)
1039 ignoredata = repo.dirstate._ignorefileandline(p)
1040 break
1040 break
1041 if ignored:
1041 if ignored:
1042 if ignored == nf:
1042 if ignored == nf:
1043 ui.write(_("%s is ignored\n") % m.uipath(f))
1043 ui.write(_("%s is ignored\n") % m.uipath(f))
1044 else:
1044 else:
1045 ui.write(_("%s is ignored because of "
1045 ui.write(_("%s is ignored because of "
1046 "containing folder %s\n")
1046 "containing folder %s\n")
1047 % (m.uipath(f), ignored))
1047 % (m.uipath(f), ignored))
1048 ignorefile, lineno, line = ignoredata
1048 ignorefile, lineno, line = ignoredata
1049 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1049 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
1050 % (ignorefile, lineno, line))
1050 % (ignorefile, lineno, line))
1051 else:
1051 else:
1052 ui.write(_("%s is not ignored\n") % m.uipath(f))
1052 ui.write(_("%s is not ignored\n") % m.uipath(f))
1053
1053
1054 @command('debugindex', cmdutil.debugrevlogopts +
1054 @command('debugindex', cmdutil.debugrevlogopts +
1055 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1055 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
1056 _('[-f FORMAT] -c|-m|FILE'),
1056 _('[-f FORMAT] -c|-m|FILE'),
1057 optionalrepo=True)
1057 optionalrepo=True)
1058 def debugindex(ui, repo, file_=None, **opts):
1058 def debugindex(ui, repo, file_=None, **opts):
1059 """dump the contents of an index file"""
1059 """dump the contents of an index file"""
1060 opts = pycompat.byteskwargs(opts)
1060 opts = pycompat.byteskwargs(opts)
1061 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1061 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
1062 format = opts.get('format', 0)
1062 format = opts.get('format', 0)
1063 if format not in (0, 1):
1063 if format not in (0, 1):
1064 raise error.Abort(_("unknown format %d") % format)
1064 raise error.Abort(_("unknown format %d") % format)
1065
1065
1066 if ui.debugflag:
1066 if ui.debugflag:
1067 shortfn = hex
1067 shortfn = hex
1068 else:
1068 else:
1069 shortfn = short
1069 shortfn = short
1070
1070
1071 # There might not be anything in r, so have a sane default
1071 # There might not be anything in r, so have a sane default
1072 idlen = 12
1072 idlen = 12
1073 for i in r:
1073 for i in r:
1074 idlen = len(shortfn(r.node(i)))
1074 idlen = len(shortfn(r.node(i)))
1075 break
1075 break
1076
1076
1077 if format == 0:
1077 if format == 0:
1078 if ui.verbose:
1078 if ui.verbose:
1079 ui.write((" rev offset length linkrev"
1079 ui.write((" rev offset length linkrev"
1080 " %s %s p2\n") % ("nodeid".ljust(idlen),
1080 " %s %s p2\n") % ("nodeid".ljust(idlen),
1081 "p1".ljust(idlen)))
1081 "p1".ljust(idlen)))
1082 else:
1082 else:
1083 ui.write((" rev linkrev %s %s p2\n") % (
1083 ui.write((" rev linkrev %s %s p2\n") % (
1084 "nodeid".ljust(idlen), "p1".ljust(idlen)))
1084 "nodeid".ljust(idlen), "p1".ljust(idlen)))
1085 elif format == 1:
1085 elif format == 1:
1086 if ui.verbose:
1086 if ui.verbose:
1087 ui.write((" rev flag offset length size link p1"
1087 ui.write((" rev flag offset length size link p1"
1088 " p2 %s\n") % "nodeid".rjust(idlen))
1088 " p2 %s\n") % "nodeid".rjust(idlen))
1089 else:
1089 else:
1090 ui.write((" rev flag size link p1 p2 %s\n") %
1090 ui.write((" rev flag size link p1 p2 %s\n") %
1091 "nodeid".rjust(idlen))
1091 "nodeid".rjust(idlen))
1092
1092
1093 for i in r:
1093 for i in r:
1094 node = r.node(i)
1094 node = r.node(i)
1095 if format == 0:
1095 if format == 0:
1096 try:
1096 try:
1097 pp = r.parents(node)
1097 pp = r.parents(node)
1098 except Exception:
1098 except Exception:
1099 pp = [nullid, nullid]
1099 pp = [nullid, nullid]
1100 if ui.verbose:
1100 if ui.verbose:
1101 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
1101 ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
1102 i, r.start(i), r.length(i), r.linkrev(i),
1102 i, r.start(i), r.length(i), r.linkrev(i),
1103 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1103 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
1104 else:
1104 else:
1105 ui.write("% 6d % 7d %s %s %s\n" % (
1105 ui.write("% 6d % 7d %s %s %s\n" % (
1106 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
1106 i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
1107 shortfn(pp[1])))
1107 shortfn(pp[1])))
1108 elif format == 1:
1108 elif format == 1:
1109 pr = r.parentrevs(i)
1109 pr = r.parentrevs(i)
1110 if ui.verbose:
1110 if ui.verbose:
1111 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
1111 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
1112 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1112 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
1113 r.linkrev(i), pr[0], pr[1], shortfn(node)))
1113 r.linkrev(i), pr[0], pr[1], shortfn(node)))
1114 else:
1114 else:
1115 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
1115 ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
1116 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
1116 i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
1117 shortfn(node)))
1117 shortfn(node)))
1118
1118
1119 @command('debugindexdot', cmdutil.debugrevlogopts,
1119 @command('debugindexdot', cmdutil.debugrevlogopts,
1120 _('-c|-m|FILE'), optionalrepo=True)
1120 _('-c|-m|FILE'), optionalrepo=True)
1121 def debugindexdot(ui, repo, file_=None, **opts):
1121 def debugindexdot(ui, repo, file_=None, **opts):
1122 """dump an index DAG as a graphviz dot file"""
1122 """dump an index DAG as a graphviz dot file"""
1123 opts = pycompat.byteskwargs(opts)
1123 opts = pycompat.byteskwargs(opts)
1124 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1124 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
1125 ui.write(("digraph G {\n"))
1125 ui.write(("digraph G {\n"))
1126 for i in r:
1126 for i in r:
1127 node = r.node(i)
1127 node = r.node(i)
1128 pp = r.parents(node)
1128 pp = r.parents(node)
1129 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1129 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
1130 if pp[1] != nullid:
1130 if pp[1] != nullid:
1131 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1131 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
1132 ui.write("}\n")
1132 ui.write("}\n")
1133
1133
1134 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1134 @command('debuginstall', [] + cmdutil.formatteropts, '', norepo=True)
1135 def debuginstall(ui, **opts):
1135 def debuginstall(ui, **opts):
1136 '''test Mercurial installation
1136 '''test Mercurial installation
1137
1137
1138 Returns 0 on success.
1138 Returns 0 on success.
1139 '''
1139 '''
1140 opts = pycompat.byteskwargs(opts)
1140 opts = pycompat.byteskwargs(opts)
1141
1141
1142 def writetemp(contents):
1142 def writetemp(contents):
1143 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1143 (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
1144 f = os.fdopen(fd, r"wb")
1144 f = os.fdopen(fd, r"wb")
1145 f.write(contents)
1145 f.write(contents)
1146 f.close()
1146 f.close()
1147 return name
1147 return name
1148
1148
1149 problems = 0
1149 problems = 0
1150
1150
1151 fm = ui.formatter('debuginstall', opts)
1151 fm = ui.formatter('debuginstall', opts)
1152 fm.startitem()
1152 fm.startitem()
1153
1153
1154 # encoding
1154 # encoding
1155 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1155 fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
1156 err = None
1156 err = None
1157 try:
1157 try:
1158 codecs.lookup(pycompat.sysstr(encoding.encoding))
1158 codecs.lookup(pycompat.sysstr(encoding.encoding))
1159 except LookupError as inst:
1159 except LookupError as inst:
1160 err = stringutil.forcebytestr(inst)
1160 err = stringutil.forcebytestr(inst)
1161 problems += 1
1161 problems += 1
1162 fm.condwrite(err, 'encodingerror', _(" %s\n"
1162 fm.condwrite(err, 'encodingerror', _(" %s\n"
1163 " (check that your locale is properly set)\n"), err)
1163 " (check that your locale is properly set)\n"), err)
1164
1164
1165 # Python
1165 # Python
1166 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1166 fm.write('pythonexe', _("checking Python executable (%s)\n"),
1167 pycompat.sysexecutable)
1167 pycompat.sysexecutable)
1168 fm.write('pythonver', _("checking Python version (%s)\n"),
1168 fm.write('pythonver', _("checking Python version (%s)\n"),
1169 ("%d.%d.%d" % sys.version_info[:3]))
1169 ("%d.%d.%d" % sys.version_info[:3]))
1170 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1170 fm.write('pythonlib', _("checking Python lib (%s)...\n"),
1171 os.path.dirname(pycompat.fsencode(os.__file__)))
1171 os.path.dirname(pycompat.fsencode(os.__file__)))
1172
1172
1173 security = set(sslutil.supportedprotocols)
1173 security = set(sslutil.supportedprotocols)
1174 if sslutil.hassni:
1174 if sslutil.hassni:
1175 security.add('sni')
1175 security.add('sni')
1176
1176
1177 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1177 fm.write('pythonsecurity', _("checking Python security support (%s)\n"),
1178 fm.formatlist(sorted(security), name='protocol',
1178 fm.formatlist(sorted(security), name='protocol',
1179 fmt='%s', sep=','))
1179 fmt='%s', sep=','))
1180
1180
1181 # These are warnings, not errors. So don't increment problem count. This
1181 # These are warnings, not errors. So don't increment problem count. This
1182 # may change in the future.
1182 # may change in the future.
1183 if 'tls1.2' not in security:
1183 if 'tls1.2' not in security:
1184 fm.plain(_(' TLS 1.2 not supported by Python install; '
1184 fm.plain(_(' TLS 1.2 not supported by Python install; '
1185 'network connections lack modern security\n'))
1185 'network connections lack modern security\n'))
1186 if 'sni' not in security:
1186 if 'sni' not in security:
1187 fm.plain(_(' SNI not supported by Python install; may have '
1187 fm.plain(_(' SNI not supported by Python install; may have '
1188 'connectivity issues with some servers\n'))
1188 'connectivity issues with some servers\n'))
1189
1189
1190 # TODO print CA cert info
1190 # TODO print CA cert info
1191
1191
1192 # hg version
1192 # hg version
1193 hgver = util.version()
1193 hgver = util.version()
1194 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1194 fm.write('hgver', _("checking Mercurial version (%s)\n"),
1195 hgver.split('+')[0])
1195 hgver.split('+')[0])
1196 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1196 fm.write('hgverextra', _("checking Mercurial custom build (%s)\n"),
1197 '+'.join(hgver.split('+')[1:]))
1197 '+'.join(hgver.split('+')[1:]))
1198
1198
1199 # compiled modules
1199 # compiled modules
1200 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1200 fm.write('hgmodulepolicy', _("checking module policy (%s)\n"),
1201 policy.policy)
1201 policy.policy)
1202 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1202 fm.write('hgmodules', _("checking installed modules (%s)...\n"),
1203 os.path.dirname(pycompat.fsencode(__file__)))
1203 os.path.dirname(pycompat.fsencode(__file__)))
1204
1204
1205 if policy.policy in ('c', 'allow'):
1205 if policy.policy in ('c', 'allow'):
1206 err = None
1206 err = None
1207 try:
1207 try:
1208 from .cext import (
1208 from .cext import (
1209 base85,
1209 base85,
1210 bdiff,
1210 bdiff,
1211 mpatch,
1211 mpatch,
1212 osutil,
1212 osutil,
1213 )
1213 )
1214 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1214 dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
1215 except Exception as inst:
1215 except Exception as inst:
1216 err = stringutil.forcebytestr(inst)
1216 err = stringutil.forcebytestr(inst)
1217 problems += 1
1217 problems += 1
1218 fm.condwrite(err, 'extensionserror', " %s\n", err)
1218 fm.condwrite(err, 'extensionserror', " %s\n", err)
1219
1219
1220 compengines = util.compengines._engines.values()
1220 compengines = util.compengines._engines.values()
1221 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1221 fm.write('compengines', _('checking registered compression engines (%s)\n'),
1222 fm.formatlist(sorted(e.name() for e in compengines),
1222 fm.formatlist(sorted(e.name() for e in compengines),
1223 name='compengine', fmt='%s', sep=', '))
1223 name='compengine', fmt='%s', sep=', '))
1224 fm.write('compenginesavail', _('checking available compression engines '
1224 fm.write('compenginesavail', _('checking available compression engines '
1225 '(%s)\n'),
1225 '(%s)\n'),
1226 fm.formatlist(sorted(e.name() for e in compengines
1226 fm.formatlist(sorted(e.name() for e in compengines
1227 if e.available()),
1227 if e.available()),
1228 name='compengine', fmt='%s', sep=', '))
1228 name='compengine', fmt='%s', sep=', '))
1229 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1229 wirecompengines = util.compengines.supportedwireengines(util.SERVERROLE)
1230 fm.write('compenginesserver', _('checking available compression engines '
1230 fm.write('compenginesserver', _('checking available compression engines '
1231 'for wire protocol (%s)\n'),
1231 'for wire protocol (%s)\n'),
1232 fm.formatlist([e.name() for e in wirecompengines
1232 fm.formatlist([e.name() for e in wirecompengines
1233 if e.wireprotosupport()],
1233 if e.wireprotosupport()],
1234 name='compengine', fmt='%s', sep=', '))
1234 name='compengine', fmt='%s', sep=', '))
1235 re2 = 'missing'
1235 re2 = 'missing'
1236 if util._re2:
1236 if util._re2:
1237 re2 = 'available'
1237 re2 = 'available'
1238 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1238 fm.plain(_('checking "re2" regexp engine (%s)\n') % re2)
1239 fm.data(re2=bool(util._re2))
1239 fm.data(re2=bool(util._re2))
1240
1240
1241 # templates
1241 # templates
1242 p = templater.templatepaths()
1242 p = templater.templatepaths()
1243 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1243 fm.write('templatedirs', 'checking templates (%s)...\n', ' '.join(p))
1244 fm.condwrite(not p, '', _(" no template directories found\n"))
1244 fm.condwrite(not p, '', _(" no template directories found\n"))
1245 if p:
1245 if p:
1246 m = templater.templatepath("map-cmdline.default")
1246 m = templater.templatepath("map-cmdline.default")
1247 if m:
1247 if m:
1248 # template found, check if it is working
1248 # template found, check if it is working
1249 err = None
1249 err = None
1250 try:
1250 try:
1251 templater.templater.frommapfile(m)
1251 templater.templater.frommapfile(m)
1252 except Exception as inst:
1252 except Exception as inst:
1253 err = stringutil.forcebytestr(inst)
1253 err = stringutil.forcebytestr(inst)
1254 p = None
1254 p = None
1255 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1255 fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
1256 else:
1256 else:
1257 p = None
1257 p = None
1258 fm.condwrite(p, 'defaulttemplate',
1258 fm.condwrite(p, 'defaulttemplate',
1259 _("checking default template (%s)\n"), m)
1259 _("checking default template (%s)\n"), m)
1260 fm.condwrite(not m, 'defaulttemplatenotfound',
1260 fm.condwrite(not m, 'defaulttemplatenotfound',
1261 _(" template '%s' not found\n"), "default")
1261 _(" template '%s' not found\n"), "default")
1262 if not p:
1262 if not p:
1263 problems += 1
1263 problems += 1
1264 fm.condwrite(not p, '',
1264 fm.condwrite(not p, '',
1265 _(" (templates seem to have been installed incorrectly)\n"))
1265 _(" (templates seem to have been installed incorrectly)\n"))
1266
1266
1267 # editor
1267 # editor
1268 editor = ui.geteditor()
1268 editor = ui.geteditor()
1269 editor = util.expandpath(editor)
1269 editor = util.expandpath(editor)
1270 editorbin = procutil.shellsplit(editor)[0]
1270 editorbin = procutil.shellsplit(editor)[0]
1271 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1271 fm.write('editor', _("checking commit editor... (%s)\n"), editorbin)
1272 cmdpath = procutil.findexe(editorbin)
1272 cmdpath = procutil.findexe(editorbin)
1273 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1273 fm.condwrite(not cmdpath and editor == 'vi', 'vinotfound',
1274 _(" No commit editor set and can't find %s in PATH\n"
1274 _(" No commit editor set and can't find %s in PATH\n"
1275 " (specify a commit editor in your configuration"
1275 " (specify a commit editor in your configuration"
1276 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1276 " file)\n"), not cmdpath and editor == 'vi' and editorbin)
1277 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1277 fm.condwrite(not cmdpath and editor != 'vi', 'editornotfound',
1278 _(" Can't find editor '%s' in PATH\n"
1278 _(" Can't find editor '%s' in PATH\n"
1279 " (specify a commit editor in your configuration"
1279 " (specify a commit editor in your configuration"
1280 " file)\n"), not cmdpath and editorbin)
1280 " file)\n"), not cmdpath and editorbin)
1281 if not cmdpath and editor != 'vi':
1281 if not cmdpath and editor != 'vi':
1282 problems += 1
1282 problems += 1
1283
1283
1284 # check username
1284 # check username
1285 username = None
1285 username = None
1286 err = None
1286 err = None
1287 try:
1287 try:
1288 username = ui.username()
1288 username = ui.username()
1289 except error.Abort as e:
1289 except error.Abort as e:
1290 err = stringutil.forcebytestr(e)
1290 err = stringutil.forcebytestr(e)
1291 problems += 1
1291 problems += 1
1292
1292
1293 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1293 fm.condwrite(username, 'username', _("checking username (%s)\n"), username)
1294 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1294 fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
1295 " (specify a username in your configuration file)\n"), err)
1295 " (specify a username in your configuration file)\n"), err)
1296
1296
1297 fm.condwrite(not problems, '',
1297 fm.condwrite(not problems, '',
1298 _("no problems detected\n"))
1298 _("no problems detected\n"))
1299 if not problems:
1299 if not problems:
1300 fm.data(problems=problems)
1300 fm.data(problems=problems)
1301 fm.condwrite(problems, 'problems',
1301 fm.condwrite(problems, 'problems',
1302 _("%d problems detected,"
1302 _("%d problems detected,"
1303 " please check your install!\n"), problems)
1303 " please check your install!\n"), problems)
1304 fm.end()
1304 fm.end()
1305
1305
1306 return problems
1306 return problems
1307
1307
1308 @command('debugknown', [], _('REPO ID...'), norepo=True)
1308 @command('debugknown', [], _('REPO ID...'), norepo=True)
1309 def debugknown(ui, repopath, *ids, **opts):
1309 def debugknown(ui, repopath, *ids, **opts):
1310 """test whether node ids are known to a repo
1310 """test whether node ids are known to a repo
1311
1311
1312 Every ID must be a full-length hex node id string. Returns a list of 0s
1312 Every ID must be a full-length hex node id string. Returns a list of 0s
1313 and 1s indicating unknown/known.
1313 and 1s indicating unknown/known.
1314 """
1314 """
1315 opts = pycompat.byteskwargs(opts)
1315 opts = pycompat.byteskwargs(opts)
1316 repo = hg.peer(ui, opts, repopath)
1316 repo = hg.peer(ui, opts, repopath)
1317 if not repo.capable('known'):
1317 if not repo.capable('known'):
1318 raise error.Abort("known() not supported by target repository")
1318 raise error.Abort("known() not supported by target repository")
1319 flags = repo.known([bin(s) for s in ids])
1319 flags = repo.known([bin(s) for s in ids])
1320 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1320 ui.write("%s\n" % ("".join([f and "1" or "0" for f in flags])))
1321
1321
1322 @command('debuglabelcomplete', [], _('LABEL...'))
1322 @command('debuglabelcomplete', [], _('LABEL...'))
1323 def debuglabelcomplete(ui, repo, *args):
1323 def debuglabelcomplete(ui, repo, *args):
1324 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1324 '''backwards compatibility with old bash completion scripts (DEPRECATED)'''
1325 debugnamecomplete(ui, repo, *args)
1325 debugnamecomplete(ui, repo, *args)
1326
1326
1327 @command('debuglocks',
1327 @command('debuglocks',
1328 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1328 [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
1329 ('W', 'force-wlock', None,
1329 ('W', 'force-wlock', None,
1330 _('free the working state lock (DANGEROUS)')),
1330 _('free the working state lock (DANGEROUS)')),
1331 ('s', 'set-lock', None, _('set the store lock until stopped')),
1331 ('s', 'set-lock', None, _('set the store lock until stopped')),
1332 ('S', 'set-wlock', None,
1332 ('S', 'set-wlock', None,
1333 _('set the working state lock until stopped'))],
1333 _('set the working state lock until stopped'))],
1334 _('[OPTION]...'))
1334 _('[OPTION]...'))
1335 def debuglocks(ui, repo, **opts):
1335 def debuglocks(ui, repo, **opts):
1336 """show or modify state of locks
1336 """show or modify state of locks
1337
1337
1338 By default, this command will show which locks are held. This
1338 By default, this command will show which locks are held. This
1339 includes the user and process holding the lock, the amount of time
1339 includes the user and process holding the lock, the amount of time
1340 the lock has been held, and the machine name where the process is
1340 the lock has been held, and the machine name where the process is
1341 running if it's not local.
1341 running if it's not local.
1342
1342
1343 Locks protect the integrity of Mercurial's data, so should be
1343 Locks protect the integrity of Mercurial's data, so should be
1344 treated with care. System crashes or other interruptions may cause
1344 treated with care. System crashes or other interruptions may cause
1345 locks to not be properly released, though Mercurial will usually
1345 locks to not be properly released, though Mercurial will usually
1346 detect and remove such stale locks automatically.
1346 detect and remove such stale locks automatically.
1347
1347
1348 However, detecting stale locks may not always be possible (for
1348 However, detecting stale locks may not always be possible (for
1349 instance, on a shared filesystem). Removing locks may also be
1349 instance, on a shared filesystem). Removing locks may also be
1350 blocked by filesystem permissions.
1350 blocked by filesystem permissions.
1351
1351
1352 Setting a lock will prevent other commands from changing the data.
1352 Setting a lock will prevent other commands from changing the data.
1353 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1353 The command will wait until an interruption (SIGINT, SIGTERM, ...) occurs.
1354 The set locks are removed when the command exits.
1354 The set locks are removed when the command exits.
1355
1355
1356 Returns 0 if no locks are held.
1356 Returns 0 if no locks are held.
1357
1357
1358 """
1358 """
1359
1359
1360 if opts.get(r'force_lock'):
1360 if opts.get(r'force_lock'):
1361 repo.svfs.unlink('lock')
1361 repo.svfs.unlink('lock')
1362 if opts.get(r'force_wlock'):
1362 if opts.get(r'force_wlock'):
1363 repo.vfs.unlink('wlock')
1363 repo.vfs.unlink('wlock')
1364 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1364 if opts.get(r'force_lock') or opts.get(r'force_wlock'):
1365 return 0
1365 return 0
1366
1366
1367 locks = []
1367 locks = []
1368 try:
1368 try:
1369 if opts.get(r'set_wlock'):
1369 if opts.get(r'set_wlock'):
1370 try:
1370 try:
1371 locks.append(repo.wlock(False))
1371 locks.append(repo.wlock(False))
1372 except error.LockHeld:
1372 except error.LockHeld:
1373 raise error.Abort(_('wlock is already held'))
1373 raise error.Abort(_('wlock is already held'))
1374 if opts.get(r'set_lock'):
1374 if opts.get(r'set_lock'):
1375 try:
1375 try:
1376 locks.append(repo.lock(False))
1376 locks.append(repo.lock(False))
1377 except error.LockHeld:
1377 except error.LockHeld:
1378 raise error.Abort(_('lock is already held'))
1378 raise error.Abort(_('lock is already held'))
1379 if len(locks):
1379 if len(locks):
1380 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1380 ui.promptchoice(_("ready to release the lock (y)? $$ &Yes"))
1381 return 0
1381 return 0
1382 finally:
1382 finally:
1383 release(*locks)
1383 release(*locks)
1384
1384
1385 now = time.time()
1385 now = time.time()
1386 held = 0
1386 held = 0
1387
1387
1388 def report(vfs, name, method):
1388 def report(vfs, name, method):
1389 # this causes stale locks to get reaped for more accurate reporting
1389 # this causes stale locks to get reaped for more accurate reporting
1390 try:
1390 try:
1391 l = method(False)
1391 l = method(False)
1392 except error.LockHeld:
1392 except error.LockHeld:
1393 l = None
1393 l = None
1394
1394
1395 if l:
1395 if l:
1396 l.release()
1396 l.release()
1397 else:
1397 else:
1398 try:
1398 try:
1399 st = vfs.lstat(name)
1399 st = vfs.lstat(name)
1400 age = now - st[stat.ST_MTIME]
1400 age = now - st[stat.ST_MTIME]
1401 user = util.username(st.st_uid)
1401 user = util.username(st.st_uid)
1402 locker = vfs.readlock(name)
1402 locker = vfs.readlock(name)
1403 if ":" in locker:
1403 if ":" in locker:
1404 host, pid = locker.split(':')
1404 host, pid = locker.split(':')
1405 if host == socket.gethostname():
1405 if host == socket.gethostname():
1406 locker = 'user %s, process %s' % (user, pid)
1406 locker = 'user %s, process %s' % (user, pid)
1407 else:
1407 else:
1408 locker = 'user %s, process %s, host %s' \
1408 locker = 'user %s, process %s, host %s' \
1409 % (user, pid, host)
1409 % (user, pid, host)
1410 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1410 ui.write(("%-6s %s (%ds)\n") % (name + ":", locker, age))
1411 return 1
1411 return 1
1412 except OSError as e:
1412 except OSError as e:
1413 if e.errno != errno.ENOENT:
1413 if e.errno != errno.ENOENT:
1414 raise
1414 raise
1415
1415
1416 ui.write(("%-6s free\n") % (name + ":"))
1416 ui.write(("%-6s free\n") % (name + ":"))
1417 return 0
1417 return 0
1418
1418
1419 held += report(repo.svfs, "lock", repo.lock)
1419 held += report(repo.svfs, "lock", repo.lock)
1420 held += report(repo.vfs, "wlock", repo.wlock)
1420 held += report(repo.vfs, "wlock", repo.wlock)
1421
1421
1422 return held
1422 return held
1423
1423
1424 @command('debugmergestate', [], '')
1424 @command('debugmergestate', [], '')
1425 def debugmergestate(ui, repo, *args):
1425 def debugmergestate(ui, repo, *args):
1426 """print merge state
1426 """print merge state
1427
1427
1428 Use --verbose to print out information about whether v1 or v2 merge state
1428 Use --verbose to print out information about whether v1 or v2 merge state
1429 was chosen."""
1429 was chosen."""
1430 def _hashornull(h):
1430 def _hashornull(h):
1431 if h == nullhex:
1431 if h == nullhex:
1432 return 'null'
1432 return 'null'
1433 else:
1433 else:
1434 return h
1434 return h
1435
1435
1436 def printrecords(version):
1436 def printrecords(version):
1437 ui.write(('* version %d records\n') % version)
1437 ui.write(('* version %d records\n') % version)
1438 if version == 1:
1438 if version == 1:
1439 records = v1records
1439 records = v1records
1440 else:
1440 else:
1441 records = v2records
1441 records = v2records
1442
1442
1443 for rtype, record in records:
1443 for rtype, record in records:
1444 # pretty print some record types
1444 # pretty print some record types
1445 if rtype == 'L':
1445 if rtype == 'L':
1446 ui.write(('local: %s\n') % record)
1446 ui.write(('local: %s\n') % record)
1447 elif rtype == 'O':
1447 elif rtype == 'O':
1448 ui.write(('other: %s\n') % record)
1448 ui.write(('other: %s\n') % record)
1449 elif rtype == 'm':
1449 elif rtype == 'm':
1450 driver, mdstate = record.split('\0', 1)
1450 driver, mdstate = record.split('\0', 1)
1451 ui.write(('merge driver: %s (state "%s")\n')
1451 ui.write(('merge driver: %s (state "%s")\n')
1452 % (driver, mdstate))
1452 % (driver, mdstate))
1453 elif rtype in 'FDC':
1453 elif rtype in 'FDC':
1454 r = record.split('\0')
1454 r = record.split('\0')
1455 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1455 f, state, hash, lfile, afile, anode, ofile = r[0:7]
1456 if version == 1:
1456 if version == 1:
1457 onode = 'not stored in v1 format'
1457 onode = 'not stored in v1 format'
1458 flags = r[7]
1458 flags = r[7]
1459 else:
1459 else:
1460 onode, flags = r[7:9]
1460 onode, flags = r[7:9]
1461 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1461 ui.write(('file: %s (record type "%s", state "%s", hash %s)\n')
1462 % (f, rtype, state, _hashornull(hash)))
1462 % (f, rtype, state, _hashornull(hash)))
1463 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1463 ui.write((' local path: %s (flags "%s")\n') % (lfile, flags))
1464 ui.write((' ancestor path: %s (node %s)\n')
1464 ui.write((' ancestor path: %s (node %s)\n')
1465 % (afile, _hashornull(anode)))
1465 % (afile, _hashornull(anode)))
1466 ui.write((' other path: %s (node %s)\n')
1466 ui.write((' other path: %s (node %s)\n')
1467 % (ofile, _hashornull(onode)))
1467 % (ofile, _hashornull(onode)))
1468 elif rtype == 'f':
1468 elif rtype == 'f':
1469 filename, rawextras = record.split('\0', 1)
1469 filename, rawextras = record.split('\0', 1)
1470 extras = rawextras.split('\0')
1470 extras = rawextras.split('\0')
1471 i = 0
1471 i = 0
1472 extrastrings = []
1472 extrastrings = []
1473 while i < len(extras):
1473 while i < len(extras):
1474 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1474 extrastrings.append('%s = %s' % (extras[i], extras[i + 1]))
1475 i += 2
1475 i += 2
1476
1476
1477 ui.write(('file extras: %s (%s)\n')
1477 ui.write(('file extras: %s (%s)\n')
1478 % (filename, ', '.join(extrastrings)))
1478 % (filename, ', '.join(extrastrings)))
1479 elif rtype == 'l':
1479 elif rtype == 'l':
1480 labels = record.split('\0', 2)
1480 labels = record.split('\0', 2)
1481 labels = [l for l in labels if len(l) > 0]
1481 labels = [l for l in labels if len(l) > 0]
1482 ui.write(('labels:\n'))
1482 ui.write(('labels:\n'))
1483 ui.write((' local: %s\n' % labels[0]))
1483 ui.write((' local: %s\n' % labels[0]))
1484 ui.write((' other: %s\n' % labels[1]))
1484 ui.write((' other: %s\n' % labels[1]))
1485 if len(labels) > 2:
1485 if len(labels) > 2:
1486 ui.write((' base: %s\n' % labels[2]))
1486 ui.write((' base: %s\n' % labels[2]))
1487 else:
1487 else:
1488 ui.write(('unrecognized entry: %s\t%s\n')
1488 ui.write(('unrecognized entry: %s\t%s\n')
1489 % (rtype, record.replace('\0', '\t')))
1489 % (rtype, record.replace('\0', '\t')))
1490
1490
1491 # Avoid mergestate.read() since it may raise an exception for unsupported
1491 # Avoid mergestate.read() since it may raise an exception for unsupported
1492 # merge state records. We shouldn't be doing this, but this is OK since this
1492 # merge state records. We shouldn't be doing this, but this is OK since this
1493 # command is pretty low-level.
1493 # command is pretty low-level.
1494 ms = mergemod.mergestate(repo)
1494 ms = mergemod.mergestate(repo)
1495
1495
1496 # sort so that reasonable information is on top
1496 # sort so that reasonable information is on top
1497 v1records = ms._readrecordsv1()
1497 v1records = ms._readrecordsv1()
1498 v2records = ms._readrecordsv2()
1498 v2records = ms._readrecordsv2()
1499 order = 'LOml'
1499 order = 'LOml'
1500 def key(r):
1500 def key(r):
1501 idx = order.find(r[0])
1501 idx = order.find(r[0])
1502 if idx == -1:
1502 if idx == -1:
1503 return (1, r[1])
1503 return (1, r[1])
1504 else:
1504 else:
1505 return (0, idx)
1505 return (0, idx)
1506 v1records.sort(key=key)
1506 v1records.sort(key=key)
1507 v2records.sort(key=key)
1507 v2records.sort(key=key)
1508
1508
1509 if not v1records and not v2records:
1509 if not v1records and not v2records:
1510 ui.write(('no merge state found\n'))
1510 ui.write(('no merge state found\n'))
1511 elif not v2records:
1511 elif not v2records:
1512 ui.note(('no version 2 merge state\n'))
1512 ui.note(('no version 2 merge state\n'))
1513 printrecords(1)
1513 printrecords(1)
1514 elif ms._v1v2match(v1records, v2records):
1514 elif ms._v1v2match(v1records, v2records):
1515 ui.note(('v1 and v2 states match: using v2\n'))
1515 ui.note(('v1 and v2 states match: using v2\n'))
1516 printrecords(2)
1516 printrecords(2)
1517 else:
1517 else:
1518 ui.note(('v1 and v2 states mismatch: using v1\n'))
1518 ui.note(('v1 and v2 states mismatch: using v1\n'))
1519 printrecords(1)
1519 printrecords(1)
1520 if ui.verbose:
1520 if ui.verbose:
1521 printrecords(2)
1521 printrecords(2)
1522
1522
1523 @command('debugnamecomplete', [], _('NAME...'))
1523 @command('debugnamecomplete', [], _('NAME...'))
1524 def debugnamecomplete(ui, repo, *args):
1524 def debugnamecomplete(ui, repo, *args):
1525 '''complete "names" - tags, open branch names, bookmark names'''
1525 '''complete "names" - tags, open branch names, bookmark names'''
1526
1526
1527 names = set()
1527 names = set()
1528 # since we previously only listed open branches, we will handle that
1528 # since we previously only listed open branches, we will handle that
1529 # specially (after this for loop)
1529 # specially (after this for loop)
1530 for name, ns in repo.names.iteritems():
1530 for name, ns in repo.names.iteritems():
1531 if name != 'branches':
1531 if name != 'branches':
1532 names.update(ns.listnames(repo))
1532 names.update(ns.listnames(repo))
1533 names.update(tag for (tag, heads, tip, closed)
1533 names.update(tag for (tag, heads, tip, closed)
1534 in repo.branchmap().iterbranches() if not closed)
1534 in repo.branchmap().iterbranches() if not closed)
1535 completions = set()
1535 completions = set()
1536 if not args:
1536 if not args:
1537 args = ['']
1537 args = ['']
1538 for a in args:
1538 for a in args:
1539 completions.update(n for n in names if n.startswith(a))
1539 completions.update(n for n in names if n.startswith(a))
1540 ui.write('\n'.join(sorted(completions)))
1540 ui.write('\n'.join(sorted(completions)))
1541 ui.write('\n')
1541 ui.write('\n')
1542
1542
1543 @command('debugobsolete',
1543 @command('debugobsolete',
1544 [('', 'flags', 0, _('markers flag')),
1544 [('', 'flags', 0, _('markers flag')),
1545 ('', 'record-parents', False,
1545 ('', 'record-parents', False,
1546 _('record parent information for the precursor')),
1546 _('record parent information for the precursor')),
1547 ('r', 'rev', [], _('display markers relevant to REV')),
1547 ('r', 'rev', [], _('display markers relevant to REV')),
1548 ('', 'exclusive', False, _('restrict display to markers only '
1548 ('', 'exclusive', False, _('restrict display to markers only '
1549 'relevant to REV')),
1549 'relevant to REV')),
1550 ('', 'index', False, _('display index of the marker')),
1550 ('', 'index', False, _('display index of the marker')),
1551 ('', 'delete', [], _('delete markers specified by indices')),
1551 ('', 'delete', [], _('delete markers specified by indices')),
1552 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1552 ] + cmdutil.commitopts2 + cmdutil.formatteropts,
1553 _('[OBSOLETED [REPLACEMENT ...]]'))
1553 _('[OBSOLETED [REPLACEMENT ...]]'))
1554 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1554 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
1555 """create arbitrary obsolete marker
1555 """create arbitrary obsolete marker
1556
1556
1557 With no arguments, displays the list of obsolescence markers."""
1557 With no arguments, displays the list of obsolescence markers."""
1558
1558
1559 opts = pycompat.byteskwargs(opts)
1559 opts = pycompat.byteskwargs(opts)
1560
1560
1561 def parsenodeid(s):
1561 def parsenodeid(s):
1562 try:
1562 try:
1563 # We do not use revsingle/revrange functions here to accept
1563 # We do not use revsingle/revrange functions here to accept
1564 # arbitrary node identifiers, possibly not present in the
1564 # arbitrary node identifiers, possibly not present in the
1565 # local repository.
1565 # local repository.
1566 n = bin(s)
1566 n = bin(s)
1567 if len(n) != len(nullid):
1567 if len(n) != len(nullid):
1568 raise TypeError()
1568 raise TypeError()
1569 return n
1569 return n
1570 except TypeError:
1570 except TypeError:
1571 raise error.Abort('changeset references must be full hexadecimal '
1571 raise error.Abort('changeset references must be full hexadecimal '
1572 'node identifiers')
1572 'node identifiers')
1573
1573
1574 if opts.get('delete'):
1574 if opts.get('delete'):
1575 indices = []
1575 indices = []
1576 for v in opts.get('delete'):
1576 for v in opts.get('delete'):
1577 try:
1577 try:
1578 indices.append(int(v))
1578 indices.append(int(v))
1579 except ValueError:
1579 except ValueError:
1580 raise error.Abort(_('invalid index value: %r') % v,
1580 raise error.Abort(_('invalid index value: %r') % v,
1581 hint=_('use integers for indices'))
1581 hint=_('use integers for indices'))
1582
1582
1583 if repo.currenttransaction():
1583 if repo.currenttransaction():
1584 raise error.Abort(_('cannot delete obsmarkers in the middle '
1584 raise error.Abort(_('cannot delete obsmarkers in the middle '
1585 'of transaction.'))
1585 'of transaction.'))
1586
1586
1587 with repo.lock():
1587 with repo.lock():
1588 n = repair.deleteobsmarkers(repo.obsstore, indices)
1588 n = repair.deleteobsmarkers(repo.obsstore, indices)
1589 ui.write(_('deleted %i obsolescence markers\n') % n)
1589 ui.write(_('deleted %i obsolescence markers\n') % n)
1590
1590
1591 return
1591 return
1592
1592
1593 if precursor is not None:
1593 if precursor is not None:
1594 if opts['rev']:
1594 if opts['rev']:
1595 raise error.Abort('cannot select revision when creating marker')
1595 raise error.Abort('cannot select revision when creating marker')
1596 metadata = {}
1596 metadata = {}
1597 metadata['user'] = opts['user'] or ui.username()
1597 metadata['user'] = opts['user'] or ui.username()
1598 succs = tuple(parsenodeid(succ) for succ in successors)
1598 succs = tuple(parsenodeid(succ) for succ in successors)
1599 l = repo.lock()
1599 l = repo.lock()
1600 try:
1600 try:
1601 tr = repo.transaction('debugobsolete')
1601 tr = repo.transaction('debugobsolete')
1602 try:
1602 try:
1603 date = opts.get('date')
1603 date = opts.get('date')
1604 if date:
1604 if date:
1605 date = dateutil.parsedate(date)
1605 date = dateutil.parsedate(date)
1606 else:
1606 else:
1607 date = None
1607 date = None
1608 prec = parsenodeid(precursor)
1608 prec = parsenodeid(precursor)
1609 parents = None
1609 parents = None
1610 if opts['record_parents']:
1610 if opts['record_parents']:
1611 if prec not in repo.unfiltered():
1611 if prec not in repo.unfiltered():
1612 raise error.Abort('cannot used --record-parents on '
1612 raise error.Abort('cannot used --record-parents on '
1613 'unknown changesets')
1613 'unknown changesets')
1614 parents = repo.unfiltered()[prec].parents()
1614 parents = repo.unfiltered()[prec].parents()
1615 parents = tuple(p.node() for p in parents)
1615 parents = tuple(p.node() for p in parents)
1616 repo.obsstore.create(tr, prec, succs, opts['flags'],
1616 repo.obsstore.create(tr, prec, succs, opts['flags'],
1617 parents=parents, date=date,
1617 parents=parents, date=date,
1618 metadata=metadata, ui=ui)
1618 metadata=metadata, ui=ui)
1619 tr.close()
1619 tr.close()
1620 except ValueError as exc:
1620 except ValueError as exc:
1621 raise error.Abort(_('bad obsmarker input: %s') %
1621 raise error.Abort(_('bad obsmarker input: %s') %
1622 pycompat.bytestr(exc))
1622 pycompat.bytestr(exc))
1623 finally:
1623 finally:
1624 tr.release()
1624 tr.release()
1625 finally:
1625 finally:
1626 l.release()
1626 l.release()
1627 else:
1627 else:
1628 if opts['rev']:
1628 if opts['rev']:
1629 revs = scmutil.revrange(repo, opts['rev'])
1629 revs = scmutil.revrange(repo, opts['rev'])
1630 nodes = [repo[r].node() for r in revs]
1630 nodes = [repo[r].node() for r in revs]
1631 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1631 markers = list(obsutil.getmarkers(repo, nodes=nodes,
1632 exclusive=opts['exclusive']))
1632 exclusive=opts['exclusive']))
1633 markers.sort(key=lambda x: x._data)
1633 markers.sort(key=lambda x: x._data)
1634 else:
1634 else:
1635 markers = obsutil.getmarkers(repo)
1635 markers = obsutil.getmarkers(repo)
1636
1636
1637 markerstoiter = markers
1637 markerstoiter = markers
1638 isrelevant = lambda m: True
1638 isrelevant = lambda m: True
1639 if opts.get('rev') and opts.get('index'):
1639 if opts.get('rev') and opts.get('index'):
1640 markerstoiter = obsutil.getmarkers(repo)
1640 markerstoiter = obsutil.getmarkers(repo)
1641 markerset = set(markers)
1641 markerset = set(markers)
1642 isrelevant = lambda m: m in markerset
1642 isrelevant = lambda m: m in markerset
1643
1643
1644 fm = ui.formatter('debugobsolete', opts)
1644 fm = ui.formatter('debugobsolete', opts)
1645 for i, m in enumerate(markerstoiter):
1645 for i, m in enumerate(markerstoiter):
1646 if not isrelevant(m):
1646 if not isrelevant(m):
1647 # marker can be irrelevant when we're iterating over a set
1647 # marker can be irrelevant when we're iterating over a set
1648 # of markers (markerstoiter) which is bigger than the set
1648 # of markers (markerstoiter) which is bigger than the set
1649 # of markers we want to display (markers)
1649 # of markers we want to display (markers)
1650 # this can happen if both --index and --rev options are
1650 # this can happen if both --index and --rev options are
1651 # provided and thus we need to iterate over all of the markers
1651 # provided and thus we need to iterate over all of the markers
1652 # to get the correct indices, but only display the ones that
1652 # to get the correct indices, but only display the ones that
1653 # are relevant to --rev value
1653 # are relevant to --rev value
1654 continue
1654 continue
1655 fm.startitem()
1655 fm.startitem()
1656 ind = i if opts.get('index') else None
1656 ind = i if opts.get('index') else None
1657 cmdutil.showmarker(fm, m, index=ind)
1657 cmdutil.showmarker(fm, m, index=ind)
1658 fm.end()
1658 fm.end()
1659
1659
1660 @command('debugpathcomplete',
1660 @command('debugpathcomplete',
1661 [('f', 'full', None, _('complete an entire path')),
1661 [('f', 'full', None, _('complete an entire path')),
1662 ('n', 'normal', None, _('show only normal files')),
1662 ('n', 'normal', None, _('show only normal files')),
1663 ('a', 'added', None, _('show only added files')),
1663 ('a', 'added', None, _('show only added files')),
1664 ('r', 'removed', None, _('show only removed files'))],
1664 ('r', 'removed', None, _('show only removed files'))],
1665 _('FILESPEC...'))
1665 _('FILESPEC...'))
1666 def debugpathcomplete(ui, repo, *specs, **opts):
1666 def debugpathcomplete(ui, repo, *specs, **opts):
1667 '''complete part or all of a tracked path
1667 '''complete part or all of a tracked path
1668
1668
1669 This command supports shells that offer path name completion. It
1669 This command supports shells that offer path name completion. It
1670 currently completes only files already known to the dirstate.
1670 currently completes only files already known to the dirstate.
1671
1671
1672 Completion extends only to the next path segment unless
1672 Completion extends only to the next path segment unless
1673 --full is specified, in which case entire paths are used.'''
1673 --full is specified, in which case entire paths are used.'''
1674
1674
1675 def complete(path, acceptable):
1675 def complete(path, acceptable):
1676 dirstate = repo.dirstate
1676 dirstate = repo.dirstate
1677 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1677 spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
1678 rootdir = repo.root + pycompat.ossep
1678 rootdir = repo.root + pycompat.ossep
1679 if spec != repo.root and not spec.startswith(rootdir):
1679 if spec != repo.root and not spec.startswith(rootdir):
1680 return [], []
1680 return [], []
1681 if os.path.isdir(spec):
1681 if os.path.isdir(spec):
1682 spec += '/'
1682 spec += '/'
1683 spec = spec[len(rootdir):]
1683 spec = spec[len(rootdir):]
1684 fixpaths = pycompat.ossep != '/'
1684 fixpaths = pycompat.ossep != '/'
1685 if fixpaths:
1685 if fixpaths:
1686 spec = spec.replace(pycompat.ossep, '/')
1686 spec = spec.replace(pycompat.ossep, '/')
1687 speclen = len(spec)
1687 speclen = len(spec)
1688 fullpaths = opts[r'full']
1688 fullpaths = opts[r'full']
1689 files, dirs = set(), set()
1689 files, dirs = set(), set()
1690 adddir, addfile = dirs.add, files.add
1690 adddir, addfile = dirs.add, files.add
1691 for f, st in dirstate.iteritems():
1691 for f, st in dirstate.iteritems():
1692 if f.startswith(spec) and st[0] in acceptable:
1692 if f.startswith(spec) and st[0] in acceptable:
1693 if fixpaths:
1693 if fixpaths:
1694 f = f.replace('/', pycompat.ossep)
1694 f = f.replace('/', pycompat.ossep)
1695 if fullpaths:
1695 if fullpaths:
1696 addfile(f)
1696 addfile(f)
1697 continue
1697 continue
1698 s = f.find(pycompat.ossep, speclen)
1698 s = f.find(pycompat.ossep, speclen)
1699 if s >= 0:
1699 if s >= 0:
1700 adddir(f[:s])
1700 adddir(f[:s])
1701 else:
1701 else:
1702 addfile(f)
1702 addfile(f)
1703 return files, dirs
1703 return files, dirs
1704
1704
1705 acceptable = ''
1705 acceptable = ''
1706 if opts[r'normal']:
1706 if opts[r'normal']:
1707 acceptable += 'nm'
1707 acceptable += 'nm'
1708 if opts[r'added']:
1708 if opts[r'added']:
1709 acceptable += 'a'
1709 acceptable += 'a'
1710 if opts[r'removed']:
1710 if opts[r'removed']:
1711 acceptable += 'r'
1711 acceptable += 'r'
1712 cwd = repo.getcwd()
1712 cwd = repo.getcwd()
1713 if not specs:
1713 if not specs:
1714 specs = ['.']
1714 specs = ['.']
1715
1715
1716 files, dirs = set(), set()
1716 files, dirs = set(), set()
1717 for spec in specs:
1717 for spec in specs:
1718 f, d = complete(spec, acceptable or 'nmar')
1718 f, d = complete(spec, acceptable or 'nmar')
1719 files.update(f)
1719 files.update(f)
1720 dirs.update(d)
1720 dirs.update(d)
1721 files.update(dirs)
1721 files.update(dirs)
1722 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1722 ui.write('\n'.join(repo.pathto(p, cwd) for p in sorted(files)))
1723 ui.write('\n')
1723 ui.write('\n')
1724
1724
1725 @command('debugpeer', [], _('PATH'), norepo=True)
1725 @command('debugpeer', [], _('PATH'), norepo=True)
1726 def debugpeer(ui, path):
1726 def debugpeer(ui, path):
1727 """establish a connection to a peer repository"""
1727 """establish a connection to a peer repository"""
1728 # Always enable peer request logging. Requires --debug to display
1728 # Always enable peer request logging. Requires --debug to display
1729 # though.
1729 # though.
1730 overrides = {
1730 overrides = {
1731 ('devel', 'debug.peer-request'): True,
1731 ('devel', 'debug.peer-request'): True,
1732 }
1732 }
1733
1733
1734 with ui.configoverride(overrides):
1734 with ui.configoverride(overrides):
1735 peer = hg.peer(ui, {}, path)
1735 peer = hg.peer(ui, {}, path)
1736
1736
1737 local = peer.local() is not None
1737 local = peer.local() is not None
1738 canpush = peer.canpush()
1738 canpush = peer.canpush()
1739
1739
1740 ui.write(_('url: %s\n') % peer.url())
1740 ui.write(_('url: %s\n') % peer.url())
1741 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1741 ui.write(_('local: %s\n') % (_('yes') if local else _('no')))
1742 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1742 ui.write(_('pushable: %s\n') % (_('yes') if canpush else _('no')))
1743
1743
1744 @command('debugpickmergetool',
1744 @command('debugpickmergetool',
1745 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1745 [('r', 'rev', '', _('check for files in this revision'), _('REV')),
1746 ('', 'changedelete', None, _('emulate merging change and delete')),
1746 ('', 'changedelete', None, _('emulate merging change and delete')),
1747 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1747 ] + cmdutil.walkopts + cmdutil.mergetoolopts,
1748 _('[PATTERN]...'),
1748 _('[PATTERN]...'),
1749 inferrepo=True)
1749 inferrepo=True)
1750 def debugpickmergetool(ui, repo, *pats, **opts):
1750 def debugpickmergetool(ui, repo, *pats, **opts):
1751 """examine which merge tool is chosen for specified file
1751 """examine which merge tool is chosen for specified file
1752
1752
1753 As described in :hg:`help merge-tools`, Mercurial examines
1753 As described in :hg:`help merge-tools`, Mercurial examines
1754 configurations below in this order to decide which merge tool is
1754 configurations below in this order to decide which merge tool is
1755 chosen for specified file.
1755 chosen for specified file.
1756
1756
1757 1. ``--tool`` option
1757 1. ``--tool`` option
1758 2. ``HGMERGE`` environment variable
1758 2. ``HGMERGE`` environment variable
1759 3. configurations in ``merge-patterns`` section
1759 3. configurations in ``merge-patterns`` section
1760 4. configuration of ``ui.merge``
1760 4. configuration of ``ui.merge``
1761 5. configurations in ``merge-tools`` section
1761 5. configurations in ``merge-tools`` section
1762 6. ``hgmerge`` tool (for historical reason only)
1762 6. ``hgmerge`` tool (for historical reason only)
1763 7. default tool for fallback (``:merge`` or ``:prompt``)
1763 7. default tool for fallback (``:merge`` or ``:prompt``)
1764
1764
1765 This command writes out examination result in the style below::
1765 This command writes out examination result in the style below::
1766
1766
1767 FILE = MERGETOOL
1767 FILE = MERGETOOL
1768
1768
1769 By default, all files known in the first parent context of the
1769 By default, all files known in the first parent context of the
1770 working directory are examined. Use file patterns and/or -I/-X
1770 working directory are examined. Use file patterns and/or -I/-X
1771 options to limit target files. -r/--rev is also useful to examine
1771 options to limit target files. -r/--rev is also useful to examine
1772 files in another context without actual updating to it.
1772 files in another context without actual updating to it.
1773
1773
1774 With --debug, this command shows warning messages while matching
1774 With --debug, this command shows warning messages while matching
1775 against ``merge-patterns`` and so on, too. It is recommended to
1775 against ``merge-patterns`` and so on, too. It is recommended to
1776 use this option with explicit file patterns and/or -I/-X options,
1776 use this option with explicit file patterns and/or -I/-X options,
1777 because this option increases amount of output per file according
1777 because this option increases amount of output per file according
1778 to configurations in hgrc.
1778 to configurations in hgrc.
1779
1779
1780 With -v/--verbose, this command shows configurations below at
1780 With -v/--verbose, this command shows configurations below at
1781 first (only if specified).
1781 first (only if specified).
1782
1782
1783 - ``--tool`` option
1783 - ``--tool`` option
1784 - ``HGMERGE`` environment variable
1784 - ``HGMERGE`` environment variable
1785 - configuration of ``ui.merge``
1785 - configuration of ``ui.merge``
1786
1786
1787 If merge tool is chosen before matching against
1787 If merge tool is chosen before matching against
1788 ``merge-patterns``, this command can't show any helpful
1788 ``merge-patterns``, this command can't show any helpful
1789 information, even with --debug. In such case, information above is
1789 information, even with --debug. In such case, information above is
1790 useful to know why a merge tool is chosen.
1790 useful to know why a merge tool is chosen.
1791 """
1791 """
1792 opts = pycompat.byteskwargs(opts)
1792 opts = pycompat.byteskwargs(opts)
1793 overrides = {}
1793 overrides = {}
1794 if opts['tool']:
1794 if opts['tool']:
1795 overrides[('ui', 'forcemerge')] = opts['tool']
1795 overrides[('ui', 'forcemerge')] = opts['tool']
1796 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1796 ui.note(('with --tool %r\n') % (pycompat.bytestr(opts['tool'])))
1797
1797
1798 with ui.configoverride(overrides, 'debugmergepatterns'):
1798 with ui.configoverride(overrides, 'debugmergepatterns'):
1799 hgmerge = encoding.environ.get("HGMERGE")
1799 hgmerge = encoding.environ.get("HGMERGE")
1800 if hgmerge is not None:
1800 if hgmerge is not None:
1801 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1801 ui.note(('with HGMERGE=%r\n') % (pycompat.bytestr(hgmerge)))
1802 uimerge = ui.config("ui", "merge")
1802 uimerge = ui.config("ui", "merge")
1803 if uimerge:
1803 if uimerge:
1804 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1804 ui.note(('with ui.merge=%r\n') % (pycompat.bytestr(uimerge)))
1805
1805
1806 ctx = scmutil.revsingle(repo, opts.get('rev'))
1806 ctx = scmutil.revsingle(repo, opts.get('rev'))
1807 m = scmutil.match(ctx, pats, opts)
1807 m = scmutil.match(ctx, pats, opts)
1808 changedelete = opts['changedelete']
1808 changedelete = opts['changedelete']
1809 for path in ctx.walk(m):
1809 for path in ctx.walk(m):
1810 fctx = ctx[path]
1810 fctx = ctx[path]
1811 try:
1811 try:
1812 if not ui.debugflag:
1812 if not ui.debugflag:
1813 ui.pushbuffer(error=True)
1813 ui.pushbuffer(error=True)
1814 tool, toolpath = filemerge._picktool(repo, ui, path,
1814 tool, toolpath = filemerge._picktool(repo, ui, path,
1815 fctx.isbinary(),
1815 fctx.isbinary(),
1816 'l' in fctx.flags(),
1816 'l' in fctx.flags(),
1817 changedelete)
1817 changedelete)
1818 finally:
1818 finally:
1819 if not ui.debugflag:
1819 if not ui.debugflag:
1820 ui.popbuffer()
1820 ui.popbuffer()
1821 ui.write(('%s = %s\n') % (path, tool))
1821 ui.write(('%s = %s\n') % (path, tool))
1822
1822
1823 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1823 @command('debugpushkey', [], _('REPO NAMESPACE [KEY OLD NEW]'), norepo=True)
1824 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1824 def debugpushkey(ui, repopath, namespace, *keyinfo, **opts):
1825 '''access the pushkey key/value protocol
1825 '''access the pushkey key/value protocol
1826
1826
1827 With two args, list the keys in the given namespace.
1827 With two args, list the keys in the given namespace.
1828
1828
1829 With five args, set a key to new if it currently is set to old.
1829 With five args, set a key to new if it currently is set to old.
1830 Reports success or failure.
1830 Reports success or failure.
1831 '''
1831 '''
1832
1832
1833 target = hg.peer(ui, {}, repopath)
1833 target = hg.peer(ui, {}, repopath)
1834 if keyinfo:
1834 if keyinfo:
1835 key, old, new = keyinfo
1835 key, old, new = keyinfo
1836 r = target.pushkey(namespace, key, old, new)
1836 r = target.pushkey(namespace, key, old, new)
1837 ui.status(pycompat.bytestr(r) + '\n')
1837 ui.status(pycompat.bytestr(r) + '\n')
1838 return not r
1838 return not r
1839 else:
1839 else:
1840 for k, v in sorted(target.listkeys(namespace).iteritems()):
1840 for k, v in sorted(target.listkeys(namespace).iteritems()):
1841 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1841 ui.write("%s\t%s\n" % (stringutil.escapestr(k),
1842 stringutil.escapestr(v)))
1842 stringutil.escapestr(v)))
1843
1843
1844 @command('debugpvec', [], _('A B'))
1844 @command('debugpvec', [], _('A B'))
1845 def debugpvec(ui, repo, a, b=None):
1845 def debugpvec(ui, repo, a, b=None):
1846 ca = scmutil.revsingle(repo, a)
1846 ca = scmutil.revsingle(repo, a)
1847 cb = scmutil.revsingle(repo, b)
1847 cb = scmutil.revsingle(repo, b)
1848 pa = pvec.ctxpvec(ca)
1848 pa = pvec.ctxpvec(ca)
1849 pb = pvec.ctxpvec(cb)
1849 pb = pvec.ctxpvec(cb)
1850 if pa == pb:
1850 if pa == pb:
1851 rel = "="
1851 rel = "="
1852 elif pa > pb:
1852 elif pa > pb:
1853 rel = ">"
1853 rel = ">"
1854 elif pa < pb:
1854 elif pa < pb:
1855 rel = "<"
1855 rel = "<"
1856 elif pa | pb:
1856 elif pa | pb:
1857 rel = "|"
1857 rel = "|"
1858 ui.write(_("a: %s\n") % pa)
1858 ui.write(_("a: %s\n") % pa)
1859 ui.write(_("b: %s\n") % pb)
1859 ui.write(_("b: %s\n") % pb)
1860 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1860 ui.write(_("depth(a): %d depth(b): %d\n") % (pa._depth, pb._depth))
1861 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1861 ui.write(_("delta: %d hdist: %d distance: %d relation: %s\n") %
1862 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1862 (abs(pa._depth - pb._depth), pvec._hamming(pa._vec, pb._vec),
1863 pa.distance(pb), rel))
1863 pa.distance(pb), rel))
1864
1864
1865 @command('debugrebuilddirstate|debugrebuildstate',
1865 @command('debugrebuilddirstate|debugrebuildstate',
1866 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1866 [('r', 'rev', '', _('revision to rebuild to'), _('REV')),
1867 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1867 ('', 'minimal', None, _('only rebuild files that are inconsistent with '
1868 'the working copy parent')),
1868 'the working copy parent')),
1869 ],
1869 ],
1870 _('[-r REV]'))
1870 _('[-r REV]'))
1871 def debugrebuilddirstate(ui, repo, rev, **opts):
1871 def debugrebuilddirstate(ui, repo, rev, **opts):
1872 """rebuild the dirstate as it would look like for the given revision
1872 """rebuild the dirstate as it would look like for the given revision
1873
1873
1874 If no revision is specified the first current parent will be used.
1874 If no revision is specified the first current parent will be used.
1875
1875
1876 The dirstate will be set to the files of the given revision.
1876 The dirstate will be set to the files of the given revision.
1877 The actual working directory content or existing dirstate
1877 The actual working directory content or existing dirstate
1878 information such as adds or removes is not considered.
1878 information such as adds or removes is not considered.
1879
1879
1880 ``minimal`` will only rebuild the dirstate status for files that claim to be
1880 ``minimal`` will only rebuild the dirstate status for files that claim to be
1881 tracked but are not in the parent manifest, or that exist in the parent
1881 tracked but are not in the parent manifest, or that exist in the parent
1882 manifest but are not in the dirstate. It will not change adds, removes, or
1882 manifest but are not in the dirstate. It will not change adds, removes, or
1883 modified files that are in the working copy parent.
1883 modified files that are in the working copy parent.
1884
1884
1885 One use of this command is to make the next :hg:`status` invocation
1885 One use of this command is to make the next :hg:`status` invocation
1886 check the actual file content.
1886 check the actual file content.
1887 """
1887 """
1888 ctx = scmutil.revsingle(repo, rev)
1888 ctx = scmutil.revsingle(repo, rev)
1889 with repo.wlock():
1889 with repo.wlock():
1890 dirstate = repo.dirstate
1890 dirstate = repo.dirstate
1891 changedfiles = None
1891 changedfiles = None
1892 # See command doc for what minimal does.
1892 # See command doc for what minimal does.
1893 if opts.get(r'minimal'):
1893 if opts.get(r'minimal'):
1894 manifestfiles = set(ctx.manifest().keys())
1894 manifestfiles = set(ctx.manifest().keys())
1895 dirstatefiles = set(dirstate)
1895 dirstatefiles = set(dirstate)
1896 manifestonly = manifestfiles - dirstatefiles
1896 manifestonly = manifestfiles - dirstatefiles
1897 dsonly = dirstatefiles - manifestfiles
1897 dsonly = dirstatefiles - manifestfiles
1898 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1898 dsnotadded = set(f for f in dsonly if dirstate[f] != 'a')
1899 changedfiles = manifestonly | dsnotadded
1899 changedfiles = manifestonly | dsnotadded
1900
1900
1901 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1901 dirstate.rebuild(ctx.node(), ctx.manifest(), changedfiles)
1902
1902
1903 @command('debugrebuildfncache', [], '')
1903 @command('debugrebuildfncache', [], '')
1904 def debugrebuildfncache(ui, repo):
1904 def debugrebuildfncache(ui, repo):
1905 """rebuild the fncache file"""
1905 """rebuild the fncache file"""
1906 repair.rebuildfncache(ui, repo)
1906 repair.rebuildfncache(ui, repo)
1907
1907
1908 @command('debugrename',
1908 @command('debugrename',
1909 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1909 [('r', 'rev', '', _('revision to debug'), _('REV'))],
1910 _('[-r REV] FILE'))
1910 _('[-r REV] FILE'))
1911 def debugrename(ui, repo, file1, *pats, **opts):
1911 def debugrename(ui, repo, file1, *pats, **opts):
1912 """dump rename information"""
1912 """dump rename information"""
1913
1913
1914 opts = pycompat.byteskwargs(opts)
1914 opts = pycompat.byteskwargs(opts)
1915 ctx = scmutil.revsingle(repo, opts.get('rev'))
1915 ctx = scmutil.revsingle(repo, opts.get('rev'))
1916 m = scmutil.match(ctx, (file1,) + pats, opts)
1916 m = scmutil.match(ctx, (file1,) + pats, opts)
1917 for abs in ctx.walk(m):
1917 for abs in ctx.walk(m):
1918 fctx = ctx[abs]
1918 fctx = ctx[abs]
1919 o = fctx.filelog().renamed(fctx.filenode())
1919 o = fctx.filelog().renamed(fctx.filenode())
1920 rel = m.rel(abs)
1920 rel = m.rel(abs)
1921 if o:
1921 if o:
1922 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1922 ui.write(_("%s renamed from %s:%s\n") % (rel, o[0], hex(o[1])))
1923 else:
1923 else:
1924 ui.write(_("%s not renamed\n") % rel)
1924 ui.write(_("%s not renamed\n") % rel)
1925
1925
1926 @command('debugrevlog', cmdutil.debugrevlogopts +
1926 @command('debugrevlog', cmdutil.debugrevlogopts +
1927 [('d', 'dump', False, _('dump index data'))],
1927 [('d', 'dump', False, _('dump index data'))],
1928 _('-c|-m|FILE'),
1928 _('-c|-m|FILE'),
1929 optionalrepo=True)
1929 optionalrepo=True)
1930 def debugrevlog(ui, repo, file_=None, **opts):
1930 def debugrevlog(ui, repo, file_=None, **opts):
1931 """show data and statistics about a revlog"""
1931 """show data and statistics about a revlog"""
1932 opts = pycompat.byteskwargs(opts)
1932 opts = pycompat.byteskwargs(opts)
1933 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1933 r = cmdutil.openrevlog(repo, 'debugrevlog', file_, opts)
1934
1934
1935 if opts.get("dump"):
1935 if opts.get("dump"):
1936 numrevs = len(r)
1936 numrevs = len(r)
1937 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1937 ui.write(("# rev p1rev p2rev start end deltastart base p1 p2"
1938 " rawsize totalsize compression heads chainlen\n"))
1938 " rawsize totalsize compression heads chainlen\n"))
1939 ts = 0
1939 ts = 0
1940 heads = set()
1940 heads = set()
1941
1941
1942 for rev in xrange(numrevs):
1942 for rev in xrange(numrevs):
1943 dbase = r.deltaparent(rev)
1943 dbase = r.deltaparent(rev)
1944 if dbase == -1:
1944 if dbase == -1:
1945 dbase = rev
1945 dbase = rev
1946 cbase = r.chainbase(rev)
1946 cbase = r.chainbase(rev)
1947 clen = r.chainlen(rev)
1947 clen = r.chainlen(rev)
1948 p1, p2 = r.parentrevs(rev)
1948 p1, p2 = r.parentrevs(rev)
1949 rs = r.rawsize(rev)
1949 rs = r.rawsize(rev)
1950 ts = ts + rs
1950 ts = ts + rs
1951 heads -= set(r.parentrevs(rev))
1951 heads -= set(r.parentrevs(rev))
1952 heads.add(rev)
1952 heads.add(rev)
1953 try:
1953 try:
1954 compression = ts / r.end(rev)
1954 compression = ts / r.end(rev)
1955 except ZeroDivisionError:
1955 except ZeroDivisionError:
1956 compression = 0
1956 compression = 0
1957 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1957 ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
1958 "%11d %5d %8d\n" %
1958 "%11d %5d %8d\n" %
1959 (rev, p1, p2, r.start(rev), r.end(rev),
1959 (rev, p1, p2, r.start(rev), r.end(rev),
1960 r.start(dbase), r.start(cbase),
1960 r.start(dbase), r.start(cbase),
1961 r.start(p1), r.start(p2),
1961 r.start(p1), r.start(p2),
1962 rs, ts, compression, len(heads), clen))
1962 rs, ts, compression, len(heads), clen))
1963 return 0
1963 return 0
1964
1964
1965 v = r.version
1965 v = r.version
1966 format = v & 0xFFFF
1966 format = v & 0xFFFF
1967 flags = []
1967 flags = []
1968 gdelta = False
1968 gdelta = False
1969 if v & revlog.FLAG_INLINE_DATA:
1969 if v & revlog.FLAG_INLINE_DATA:
1970 flags.append('inline')
1970 flags.append('inline')
1971 if v & revlog.FLAG_GENERALDELTA:
1971 if v & revlog.FLAG_GENERALDELTA:
1972 gdelta = True
1972 gdelta = True
1973 flags.append('generaldelta')
1973 flags.append('generaldelta')
1974 if not flags:
1974 if not flags:
1975 flags = ['(none)']
1975 flags = ['(none)']
1976
1976
1977 nummerges = 0
1977 nummerges = 0
1978 numfull = 0
1978 numfull = 0
1979 numprev = 0
1979 numprev = 0
1980 nump1 = 0
1980 nump1 = 0
1981 nump2 = 0
1981 nump2 = 0
1982 numother = 0
1982 numother = 0
1983 nump1prev = 0
1983 nump1prev = 0
1984 nump2prev = 0
1984 nump2prev = 0
1985 chainlengths = []
1985 chainlengths = []
1986 chainbases = []
1986 chainbases = []
1987 chainspans = []
1987 chainspans = []
1988
1988
1989 datasize = [None, 0, 0]
1989 datasize = [None, 0, 0]
1990 fullsize = [None, 0, 0]
1990 fullsize = [None, 0, 0]
1991 deltasize = [None, 0, 0]
1991 deltasize = [None, 0, 0]
1992 chunktypecounts = {}
1992 chunktypecounts = {}
1993 chunktypesizes = {}
1993 chunktypesizes = {}
1994
1994
1995 def addsize(size, l):
1995 def addsize(size, l):
1996 if l[0] is None or size < l[0]:
1996 if l[0] is None or size < l[0]:
1997 l[0] = size
1997 l[0] = size
1998 if size > l[1]:
1998 if size > l[1]:
1999 l[1] = size
1999 l[1] = size
2000 l[2] += size
2000 l[2] += size
2001
2001
2002 numrevs = len(r)
2002 numrevs = len(r)
2003 for rev in xrange(numrevs):
2003 for rev in xrange(numrevs):
2004 p1, p2 = r.parentrevs(rev)
2004 p1, p2 = r.parentrevs(rev)
2005 delta = r.deltaparent(rev)
2005 delta = r.deltaparent(rev)
2006 if format > 0:
2006 if format > 0:
2007 addsize(r.rawsize(rev), datasize)
2007 addsize(r.rawsize(rev), datasize)
2008 if p2 != nullrev:
2008 if p2 != nullrev:
2009 nummerges += 1
2009 nummerges += 1
2010 size = r.length(rev)
2010 size = r.length(rev)
2011 if delta == nullrev:
2011 if delta == nullrev:
2012 chainlengths.append(0)
2012 chainlengths.append(0)
2013 chainbases.append(r.start(rev))
2013 chainbases.append(r.start(rev))
2014 chainspans.append(size)
2014 chainspans.append(size)
2015 numfull += 1
2015 numfull += 1
2016 addsize(size, fullsize)
2016 addsize(size, fullsize)
2017 else:
2017 else:
2018 chainlengths.append(chainlengths[delta] + 1)
2018 chainlengths.append(chainlengths[delta] + 1)
2019 baseaddr = chainbases[delta]
2019 baseaddr = chainbases[delta]
2020 revaddr = r.start(rev)
2020 revaddr = r.start(rev)
2021 chainbases.append(baseaddr)
2021 chainbases.append(baseaddr)
2022 chainspans.append((revaddr - baseaddr) + size)
2022 chainspans.append((revaddr - baseaddr) + size)
2023 addsize(size, deltasize)
2023 addsize(size, deltasize)
2024 if delta == rev - 1:
2024 if delta == rev - 1:
2025 numprev += 1
2025 numprev += 1
2026 if delta == p1:
2026 if delta == p1:
2027 nump1prev += 1
2027 nump1prev += 1
2028 elif delta == p2:
2028 elif delta == p2:
2029 nump2prev += 1
2029 nump2prev += 1
2030 elif delta == p1:
2030 elif delta == p1:
2031 nump1 += 1
2031 nump1 += 1
2032 elif delta == p2:
2032 elif delta == p2:
2033 nump2 += 1
2033 nump2 += 1
2034 elif delta != nullrev:
2034 elif delta != nullrev:
2035 numother += 1
2035 numother += 1
2036
2036
2037 # Obtain data on the raw chunks in the revlog.
2037 # Obtain data on the raw chunks in the revlog.
2038 segment = r._getsegmentforrevs(rev, rev)[1]
2038 segment = r._getsegmentforrevs(rev, rev)[1]
2039 if segment:
2039 if segment:
2040 chunktype = bytes(segment[0:1])
2040 chunktype = bytes(segment[0:1])
2041 else:
2041 else:
2042 chunktype = 'empty'
2042 chunktype = 'empty'
2043
2043
2044 if chunktype not in chunktypecounts:
2044 if chunktype not in chunktypecounts:
2045 chunktypecounts[chunktype] = 0
2045 chunktypecounts[chunktype] = 0
2046 chunktypesizes[chunktype] = 0
2046 chunktypesizes[chunktype] = 0
2047
2047
2048 chunktypecounts[chunktype] += 1
2048 chunktypecounts[chunktype] += 1
2049 chunktypesizes[chunktype] += size
2049 chunktypesizes[chunktype] += size
2050
2050
2051 # Adjust size min value for empty cases
2051 # Adjust size min value for empty cases
2052 for size in (datasize, fullsize, deltasize):
2052 for size in (datasize, fullsize, deltasize):
2053 if size[0] is None:
2053 if size[0] is None:
2054 size[0] = 0
2054 size[0] = 0
2055
2055
2056 numdeltas = numrevs - numfull
2056 numdeltas = numrevs - numfull
2057 numoprev = numprev - nump1prev - nump2prev
2057 numoprev = numprev - nump1prev - nump2prev
2058 totalrawsize = datasize[2]
2058 totalrawsize = datasize[2]
2059 datasize[2] /= numrevs
2059 datasize[2] /= numrevs
2060 fulltotal = fullsize[2]
2060 fulltotal = fullsize[2]
2061 fullsize[2] /= numfull
2061 fullsize[2] /= numfull
2062 deltatotal = deltasize[2]
2062 deltatotal = deltasize[2]
2063 if numrevs - numfull > 0:
2063 if numrevs - numfull > 0:
2064 deltasize[2] /= numrevs - numfull
2064 deltasize[2] /= numrevs - numfull
2065 totalsize = fulltotal + deltatotal
2065 totalsize = fulltotal + deltatotal
2066 avgchainlen = sum(chainlengths) / numrevs
2066 avgchainlen = sum(chainlengths) / numrevs
2067 maxchainlen = max(chainlengths)
2067 maxchainlen = max(chainlengths)
2068 maxchainspan = max(chainspans)
2068 maxchainspan = max(chainspans)
2069 compratio = 1
2069 compratio = 1
2070 if totalsize:
2070 if totalsize:
2071 compratio = totalrawsize / totalsize
2071 compratio = totalrawsize / totalsize
2072
2072
2073 basedfmtstr = '%%%dd\n'
2073 basedfmtstr = '%%%dd\n'
2074 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2074 basepcfmtstr = '%%%dd %s(%%5.2f%%%%)\n'
2075
2075
2076 def dfmtstr(max):
2076 def dfmtstr(max):
2077 return basedfmtstr % len(str(max))
2077 return basedfmtstr % len(str(max))
2078 def pcfmtstr(max, padding=0):
2078 def pcfmtstr(max, padding=0):
2079 return basepcfmtstr % (len(str(max)), ' ' * padding)
2079 return basepcfmtstr % (len(str(max)), ' ' * padding)
2080
2080
2081 def pcfmt(value, total):
2081 def pcfmt(value, total):
2082 if total:
2082 if total:
2083 return (value, 100 * float(value) / total)
2083 return (value, 100 * float(value) / total)
2084 else:
2084 else:
2085 return value, 100.0
2085 return value, 100.0
2086
2086
2087 ui.write(('format : %d\n') % format)
2087 ui.write(('format : %d\n') % format)
2088 ui.write(('flags : %s\n') % ', '.join(flags))
2088 ui.write(('flags : %s\n') % ', '.join(flags))
2089
2089
2090 ui.write('\n')
2090 ui.write('\n')
2091 fmt = pcfmtstr(totalsize)
2091 fmt = pcfmtstr(totalsize)
2092 fmt2 = dfmtstr(totalsize)
2092 fmt2 = dfmtstr(totalsize)
2093 ui.write(('revisions : ') + fmt2 % numrevs)
2093 ui.write(('revisions : ') + fmt2 % numrevs)
2094 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2094 ui.write((' merges : ') + fmt % pcfmt(nummerges, numrevs))
2095 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2095 ui.write((' normal : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
2096 ui.write(('revisions : ') + fmt2 % numrevs)
2096 ui.write(('revisions : ') + fmt2 % numrevs)
2097 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2097 ui.write((' full : ') + fmt % pcfmt(numfull, numrevs))
2098 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2098 ui.write((' deltas : ') + fmt % pcfmt(numdeltas, numrevs))
2099 ui.write(('revision size : ') + fmt2 % totalsize)
2099 ui.write(('revision size : ') + fmt2 % totalsize)
2100 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2100 ui.write((' full : ') + fmt % pcfmt(fulltotal, totalsize))
2101 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2101 ui.write((' deltas : ') + fmt % pcfmt(deltatotal, totalsize))
2102
2102
2103 def fmtchunktype(chunktype):
2103 def fmtchunktype(chunktype):
2104 if chunktype == 'empty':
2104 if chunktype == 'empty':
2105 return ' %s : ' % chunktype
2105 return ' %s : ' % chunktype
2106 elif chunktype in pycompat.bytestr(string.ascii_letters):
2106 elif chunktype in pycompat.bytestr(string.ascii_letters):
2107 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2107 return ' 0x%s (%s) : ' % (hex(chunktype), chunktype)
2108 else:
2108 else:
2109 return ' 0x%s : ' % hex(chunktype)
2109 return ' 0x%s : ' % hex(chunktype)
2110
2110
2111 ui.write('\n')
2111 ui.write('\n')
2112 ui.write(('chunks : ') + fmt2 % numrevs)
2112 ui.write(('chunks : ') + fmt2 % numrevs)
2113 for chunktype in sorted(chunktypecounts):
2113 for chunktype in sorted(chunktypecounts):
2114 ui.write(fmtchunktype(chunktype))
2114 ui.write(fmtchunktype(chunktype))
2115 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2115 ui.write(fmt % pcfmt(chunktypecounts[chunktype], numrevs))
2116 ui.write(('chunks size : ') + fmt2 % totalsize)
2116 ui.write(('chunks size : ') + fmt2 % totalsize)
2117 for chunktype in sorted(chunktypecounts):
2117 for chunktype in sorted(chunktypecounts):
2118 ui.write(fmtchunktype(chunktype))
2118 ui.write(fmtchunktype(chunktype))
2119 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2119 ui.write(fmt % pcfmt(chunktypesizes[chunktype], totalsize))
2120
2120
2121 ui.write('\n')
2121 ui.write('\n')
2122 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2122 fmt = dfmtstr(max(avgchainlen, maxchainlen, maxchainspan, compratio))
2123 ui.write(('avg chain length : ') + fmt % avgchainlen)
2123 ui.write(('avg chain length : ') + fmt % avgchainlen)
2124 ui.write(('max chain length : ') + fmt % maxchainlen)
2124 ui.write(('max chain length : ') + fmt % maxchainlen)
2125 ui.write(('max chain reach : ') + fmt % maxchainspan)
2125 ui.write(('max chain reach : ') + fmt % maxchainspan)
2126 ui.write(('compression ratio : ') + fmt % compratio)
2126 ui.write(('compression ratio : ') + fmt % compratio)
2127
2127
2128 if format > 0:
2128 if format > 0:
2129 ui.write('\n')
2129 ui.write('\n')
2130 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2130 ui.write(('uncompressed data size (min/max/avg) : %d / %d / %d\n')
2131 % tuple(datasize))
2131 % tuple(datasize))
2132 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2132 ui.write(('full revision size (min/max/avg) : %d / %d / %d\n')
2133 % tuple(fullsize))
2133 % tuple(fullsize))
2134 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2134 ui.write(('delta size (min/max/avg) : %d / %d / %d\n')
2135 % tuple(deltasize))
2135 % tuple(deltasize))
2136
2136
2137 if numdeltas > 0:
2137 if numdeltas > 0:
2138 ui.write('\n')
2138 ui.write('\n')
2139 fmt = pcfmtstr(numdeltas)
2139 fmt = pcfmtstr(numdeltas)
2140 fmt2 = pcfmtstr(numdeltas, 4)
2140 fmt2 = pcfmtstr(numdeltas, 4)
2141 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2141 ui.write(('deltas against prev : ') + fmt % pcfmt(numprev, numdeltas))
2142 if numprev > 0:
2142 if numprev > 0:
2143 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2143 ui.write((' where prev = p1 : ') + fmt2 % pcfmt(nump1prev,
2144 numprev))
2144 numprev))
2145 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2145 ui.write((' where prev = p2 : ') + fmt2 % pcfmt(nump2prev,
2146 numprev))
2146 numprev))
2147 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2147 ui.write((' other : ') + fmt2 % pcfmt(numoprev,
2148 numprev))
2148 numprev))
2149 if gdelta:
2149 if gdelta:
2150 ui.write(('deltas against p1 : ')
2150 ui.write(('deltas against p1 : ')
2151 + fmt % pcfmt(nump1, numdeltas))
2151 + fmt % pcfmt(nump1, numdeltas))
2152 ui.write(('deltas against p2 : ')
2152 ui.write(('deltas against p2 : ')
2153 + fmt % pcfmt(nump2, numdeltas))
2153 + fmt % pcfmt(nump2, numdeltas))
2154 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2154 ui.write(('deltas against other : ') + fmt % pcfmt(numother,
2155 numdeltas))
2155 numdeltas))
2156
2156
2157 @command('debugrevspec',
2157 @command('debugrevspec',
2158 [('', 'optimize', None,
2158 [('', 'optimize', None,
2159 _('print parsed tree after optimizing (DEPRECATED)')),
2159 _('print parsed tree after optimizing (DEPRECATED)')),
2160 ('', 'show-revs', True, _('print list of result revisions (default)')),
2160 ('', 'show-revs', True, _('print list of result revisions (default)')),
2161 ('s', 'show-set', None, _('print internal representation of result set')),
2161 ('s', 'show-set', None, _('print internal representation of result set')),
2162 ('p', 'show-stage', [],
2162 ('p', 'show-stage', [],
2163 _('print parsed tree at the given stage'), _('NAME')),
2163 _('print parsed tree at the given stage'), _('NAME')),
2164 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2164 ('', 'no-optimized', False, _('evaluate tree without optimization')),
2165 ('', 'verify-optimized', False, _('verify optimized result')),
2165 ('', 'verify-optimized', False, _('verify optimized result')),
2166 ],
2166 ],
2167 ('REVSPEC'))
2167 ('REVSPEC'))
2168 def debugrevspec(ui, repo, expr, **opts):
2168 def debugrevspec(ui, repo, expr, **opts):
2169 """parse and apply a revision specification
2169 """parse and apply a revision specification
2170
2170
2171 Use -p/--show-stage option to print the parsed tree at the given stages.
2171 Use -p/--show-stage option to print the parsed tree at the given stages.
2172 Use -p all to print tree at every stage.
2172 Use -p all to print tree at every stage.
2173
2173
2174 Use --no-show-revs option with -s or -p to print only the set
2174 Use --no-show-revs option with -s or -p to print only the set
2175 representation or the parsed tree respectively.
2175 representation or the parsed tree respectively.
2176
2176
2177 Use --verify-optimized to compare the optimized result with the unoptimized
2177 Use --verify-optimized to compare the optimized result with the unoptimized
2178 one. Returns 1 if the optimized result differs.
2178 one. Returns 1 if the optimized result differs.
2179 """
2179 """
2180 opts = pycompat.byteskwargs(opts)
2180 opts = pycompat.byteskwargs(opts)
2181 aliases = ui.configitems('revsetalias')
2181 aliases = ui.configitems('revsetalias')
2182 stages = [
2182 stages = [
2183 ('parsed', lambda tree: tree),
2183 ('parsed', lambda tree: tree),
2184 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2184 ('expanded', lambda tree: revsetlang.expandaliases(tree, aliases,
2185 ui.warn)),
2185 ui.warn)),
2186 ('concatenated', revsetlang.foldconcat),
2186 ('concatenated', revsetlang.foldconcat),
2187 ('analyzed', revsetlang.analyze),
2187 ('analyzed', revsetlang.analyze),
2188 ('optimized', revsetlang.optimize),
2188 ('optimized', revsetlang.optimize),
2189 ]
2189 ]
2190 if opts['no_optimized']:
2190 if opts['no_optimized']:
2191 stages = stages[:-1]
2191 stages = stages[:-1]
2192 if opts['verify_optimized'] and opts['no_optimized']:
2192 if opts['verify_optimized'] and opts['no_optimized']:
2193 raise error.Abort(_('cannot use --verify-optimized with '
2193 raise error.Abort(_('cannot use --verify-optimized with '
2194 '--no-optimized'))
2194 '--no-optimized'))
2195 stagenames = set(n for n, f in stages)
2195 stagenames = set(n for n, f in stages)
2196
2196
2197 showalways = set()
2197 showalways = set()
2198 showchanged = set()
2198 showchanged = set()
2199 if ui.verbose and not opts['show_stage']:
2199 if ui.verbose and not opts['show_stage']:
2200 # show parsed tree by --verbose (deprecated)
2200 # show parsed tree by --verbose (deprecated)
2201 showalways.add('parsed')
2201 showalways.add('parsed')
2202 showchanged.update(['expanded', 'concatenated'])
2202 showchanged.update(['expanded', 'concatenated'])
2203 if opts['optimize']:
2203 if opts['optimize']:
2204 showalways.add('optimized')
2204 showalways.add('optimized')
2205 if opts['show_stage'] and opts['optimize']:
2205 if opts['show_stage'] and opts['optimize']:
2206 raise error.Abort(_('cannot use --optimize with --show-stage'))
2206 raise error.Abort(_('cannot use --optimize with --show-stage'))
2207 if opts['show_stage'] == ['all']:
2207 if opts['show_stage'] == ['all']:
2208 showalways.update(stagenames)
2208 showalways.update(stagenames)
2209 else:
2209 else:
2210 for n in opts['show_stage']:
2210 for n in opts['show_stage']:
2211 if n not in stagenames:
2211 if n not in stagenames:
2212 raise error.Abort(_('invalid stage name: %s') % n)
2212 raise error.Abort(_('invalid stage name: %s') % n)
2213 showalways.update(opts['show_stage'])
2213 showalways.update(opts['show_stage'])
2214
2214
2215 treebystage = {}
2215 treebystage = {}
2216 printedtree = None
2216 printedtree = None
2217 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2217 tree = revsetlang.parse(expr, lookup=revset.lookupfn(repo))
2218 for n, f in stages:
2218 for n, f in stages:
2219 treebystage[n] = tree = f(tree)
2219 treebystage[n] = tree = f(tree)
2220 if n in showalways or (n in showchanged and tree != printedtree):
2220 if n in showalways or (n in showchanged and tree != printedtree):
2221 if opts['show_stage'] or n != 'parsed':
2221 if opts['show_stage'] or n != 'parsed':
2222 ui.write(("* %s:\n") % n)
2222 ui.write(("* %s:\n") % n)
2223 ui.write(revsetlang.prettyformat(tree), "\n")
2223 ui.write(revsetlang.prettyformat(tree), "\n")
2224 printedtree = tree
2224 printedtree = tree
2225
2225
2226 if opts['verify_optimized']:
2226 if opts['verify_optimized']:
2227 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2227 arevs = revset.makematcher(treebystage['analyzed'])(repo)
2228 brevs = revset.makematcher(treebystage['optimized'])(repo)
2228 brevs = revset.makematcher(treebystage['optimized'])(repo)
2229 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2229 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2230 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2230 ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
2231 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2231 ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
2232 arevs = list(arevs)
2232 arevs = list(arevs)
2233 brevs = list(brevs)
2233 brevs = list(brevs)
2234 if arevs == brevs:
2234 if arevs == brevs:
2235 return 0
2235 return 0
2236 ui.write(('--- analyzed\n'), label='diff.file_a')
2236 ui.write(('--- analyzed\n'), label='diff.file_a')
2237 ui.write(('+++ optimized\n'), label='diff.file_b')
2237 ui.write(('+++ optimized\n'), label='diff.file_b')
2238 sm = difflib.SequenceMatcher(None, arevs, brevs)
2238 sm = difflib.SequenceMatcher(None, arevs, brevs)
2239 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2239 for tag, alo, ahi, blo, bhi in sm.get_opcodes():
2240 if tag in ('delete', 'replace'):
2240 if tag in ('delete', 'replace'):
2241 for c in arevs[alo:ahi]:
2241 for c in arevs[alo:ahi]:
2242 ui.write('-%s\n' % c, label='diff.deleted')
2242 ui.write('-%s\n' % c, label='diff.deleted')
2243 if tag in ('insert', 'replace'):
2243 if tag in ('insert', 'replace'):
2244 for c in brevs[blo:bhi]:
2244 for c in brevs[blo:bhi]:
2245 ui.write('+%s\n' % c, label='diff.inserted')
2245 ui.write('+%s\n' % c, label='diff.inserted')
2246 if tag == 'equal':
2246 if tag == 'equal':
2247 for c in arevs[alo:ahi]:
2247 for c in arevs[alo:ahi]:
2248 ui.write(' %s\n' % c)
2248 ui.write(' %s\n' % c)
2249 return 1
2249 return 1
2250
2250
2251 func = revset.makematcher(tree)
2251 func = revset.makematcher(tree)
2252 revs = func(repo)
2252 revs = func(repo)
2253 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2253 if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
2254 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2254 ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
2255 if not opts['show_revs']:
2255 if not opts['show_revs']:
2256 return
2256 return
2257 for c in revs:
2257 for c in revs:
2258 ui.write("%d\n" % c)
2258 ui.write("%d\n" % c)
2259
2259
2260 @command('debugserve', [
2260 @command('debugserve', [
2261 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2261 ('', 'sshstdio', False, _('run an SSH server bound to process handles')),
2262 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2262 ('', 'logiofd', '', _('file descriptor to log server I/O to')),
2263 ('', 'logiofile', '', _('file to log server I/O to')),
2263 ('', 'logiofile', '', _('file to log server I/O to')),
2264 ], '')
2264 ], '')
2265 def debugserve(ui, repo, **opts):
2265 def debugserve(ui, repo, **opts):
2266 """run a server with advanced settings
2266 """run a server with advanced settings
2267
2267
2268 This command is similar to :hg:`serve`. It exists partially as a
2268 This command is similar to :hg:`serve`. It exists partially as a
2269 workaround to the fact that ``hg serve --stdio`` must have specific
2269 workaround to the fact that ``hg serve --stdio`` must have specific
2270 arguments for security reasons.
2270 arguments for security reasons.
2271 """
2271 """
2272 opts = pycompat.byteskwargs(opts)
2272 opts = pycompat.byteskwargs(opts)
2273
2273
2274 if not opts['sshstdio']:
2274 if not opts['sshstdio']:
2275 raise error.Abort(_('only --sshstdio is currently supported'))
2275 raise error.Abort(_('only --sshstdio is currently supported'))
2276
2276
2277 logfh = None
2277 logfh = None
2278
2278
2279 if opts['logiofd'] and opts['logiofile']:
2279 if opts['logiofd'] and opts['logiofile']:
2280 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2280 raise error.Abort(_('cannot use both --logiofd and --logiofile'))
2281
2281
2282 if opts['logiofd']:
2282 if opts['logiofd']:
2283 # Line buffered because output is line based.
2283 # Line buffered because output is line based.
2284 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2284 logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
2285 elif opts['logiofile']:
2285 elif opts['logiofile']:
2286 logfh = open(opts['logiofile'], 'ab', 1)
2286 logfh = open(opts['logiofile'], 'ab', 1)
2287
2287
2288 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2288 s = wireprotoserver.sshserver(ui, repo, logfh=logfh)
2289 s.serve_forever()
2289 s.serve_forever()
2290
2290
2291 @command('debugsetparents', [], _('REV1 [REV2]'))
2291 @command('debugsetparents', [], _('REV1 [REV2]'))
2292 def debugsetparents(ui, repo, rev1, rev2=None):
2292 def debugsetparents(ui, repo, rev1, rev2=None):
2293 """manually set the parents of the current working directory
2293 """manually set the parents of the current working directory
2294
2294
2295 This is useful for writing repository conversion tools, but should
2295 This is useful for writing repository conversion tools, but should
2296 be used with care. For example, neither the working directory nor the
2296 be used with care. For example, neither the working directory nor the
2297 dirstate is updated, so file status may be incorrect after running this
2297 dirstate is updated, so file status may be incorrect after running this
2298 command.
2298 command.
2299
2299
2300 Returns 0 on success.
2300 Returns 0 on success.
2301 """
2301 """
2302
2302
2303 node1 = scmutil.revsingle(repo, rev1).node()
2303 node1 = scmutil.revsingle(repo, rev1).node()
2304 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2304 node2 = scmutil.revsingle(repo, rev2, 'null').node()
2305
2305
2306 with repo.wlock():
2306 with repo.wlock():
2307 repo.setparents(node1, node2)
2307 repo.setparents(node1, node2)
2308
2308
2309 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2309 @command('debugssl', [], '[SOURCE]', optionalrepo=True)
2310 def debugssl(ui, repo, source=None, **opts):
2310 def debugssl(ui, repo, source=None, **opts):
2311 '''test a secure connection to a server
2311 '''test a secure connection to a server
2312
2312
2313 This builds the certificate chain for the server on Windows, installing the
2313 This builds the certificate chain for the server on Windows, installing the
2314 missing intermediates and trusted root via Windows Update if necessary. It
2314 missing intermediates and trusted root via Windows Update if necessary. It
2315 does nothing on other platforms.
2315 does nothing on other platforms.
2316
2316
2317 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2317 If SOURCE is omitted, the 'default' path will be used. If a URL is given,
2318 that server is used. See :hg:`help urls` for more information.
2318 that server is used. See :hg:`help urls` for more information.
2319
2319
2320 If the update succeeds, retry the original operation. Otherwise, the cause
2320 If the update succeeds, retry the original operation. Otherwise, the cause
2321 of the SSL error is likely another issue.
2321 of the SSL error is likely another issue.
2322 '''
2322 '''
2323 if not pycompat.iswindows:
2323 if not pycompat.iswindows:
2324 raise error.Abort(_('certificate chain building is only possible on '
2324 raise error.Abort(_('certificate chain building is only possible on '
2325 'Windows'))
2325 'Windows'))
2326
2326
2327 if not source:
2327 if not source:
2328 if not repo:
2328 if not repo:
2329 raise error.Abort(_("there is no Mercurial repository here, and no "
2329 raise error.Abort(_("there is no Mercurial repository here, and no "
2330 "server specified"))
2330 "server specified"))
2331 source = "default"
2331 source = "default"
2332
2332
2333 source, branches = hg.parseurl(ui.expandpath(source))
2333 source, branches = hg.parseurl(ui.expandpath(source))
2334 url = util.url(source)
2334 url = util.url(source)
2335 addr = None
2335 addr = None
2336
2336
2337 defaultport = {'https': 443, 'ssh': 22}
2337 defaultport = {'https': 443, 'ssh': 22}
2338 if url.scheme in defaultport:
2338 if url.scheme in defaultport:
2339 try:
2339 try:
2340 addr = (url.host, int(url.port or defaultport[url.scheme]))
2340 addr = (url.host, int(url.port or defaultport[url.scheme]))
2341 except ValueError:
2341 except ValueError:
2342 raise error.Abort(_("malformed port number in URL"))
2342 raise error.Abort(_("malformed port number in URL"))
2343 else:
2343 else:
2344 raise error.Abort(_("only https and ssh connections are supported"))
2344 raise error.Abort(_("only https and ssh connections are supported"))
2345
2345
2346 from . import win32
2346 from . import win32
2347
2347
2348 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2348 s = ssl.wrap_socket(socket.socket(), ssl_version=ssl.PROTOCOL_TLS,
2349 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2349 cert_reqs=ssl.CERT_NONE, ca_certs=None)
2350
2350
2351 try:
2351 try:
2352 s.connect(addr)
2352 s.connect(addr)
2353 cert = s.getpeercert(True)
2353 cert = s.getpeercert(True)
2354
2354
2355 ui.status(_('checking the certificate chain for %s\n') % url.host)
2355 ui.status(_('checking the certificate chain for %s\n') % url.host)
2356
2356
2357 complete = win32.checkcertificatechain(cert, build=False)
2357 complete = win32.checkcertificatechain(cert, build=False)
2358
2358
2359 if not complete:
2359 if not complete:
2360 ui.status(_('certificate chain is incomplete, updating... '))
2360 ui.status(_('certificate chain is incomplete, updating... '))
2361
2361
2362 if not win32.checkcertificatechain(cert):
2362 if not win32.checkcertificatechain(cert):
2363 ui.status(_('failed.\n'))
2363 ui.status(_('failed.\n'))
2364 else:
2364 else:
2365 ui.status(_('done.\n'))
2365 ui.status(_('done.\n'))
2366 else:
2366 else:
2367 ui.status(_('full certificate chain is available\n'))
2367 ui.status(_('full certificate chain is available\n'))
2368 finally:
2368 finally:
2369 s.close()
2369 s.close()
2370
2370
2371 @command('debugsub',
2371 @command('debugsub',
2372 [('r', 'rev', '',
2372 [('r', 'rev', '',
2373 _('revision to check'), _('REV'))],
2373 _('revision to check'), _('REV'))],
2374 _('[-r REV] [REV]'))
2374 _('[-r REV] [REV]'))
2375 def debugsub(ui, repo, rev=None):
2375 def debugsub(ui, repo, rev=None):
2376 ctx = scmutil.revsingle(repo, rev, None)
2376 ctx = scmutil.revsingle(repo, rev, None)
2377 for k, v in sorted(ctx.substate.items()):
2377 for k, v in sorted(ctx.substate.items()):
2378 ui.write(('path %s\n') % k)
2378 ui.write(('path %s\n') % k)
2379 ui.write((' source %s\n') % v[0])
2379 ui.write((' source %s\n') % v[0])
2380 ui.write((' revision %s\n') % v[1])
2380 ui.write((' revision %s\n') % v[1])
2381
2381
2382 @command('debugsuccessorssets',
2382 @command('debugsuccessorssets',
2383 [('', 'closest', False, _('return closest successors sets only'))],
2383 [('', 'closest', False, _('return closest successors sets only'))],
2384 _('[REV]'))
2384 _('[REV]'))
2385 def debugsuccessorssets(ui, repo, *revs, **opts):
2385 def debugsuccessorssets(ui, repo, *revs, **opts):
2386 """show set of successors for revision
2386 """show set of successors for revision
2387
2387
2388 A successors set of changeset A is a consistent group of revisions that
2388 A successors set of changeset A is a consistent group of revisions that
2389 succeed A. It contains non-obsolete changesets only unless closests
2389 succeed A. It contains non-obsolete changesets only unless closests
2390 successors set is set.
2390 successors set is set.
2391
2391
2392 In most cases a changeset A has a single successors set containing a single
2392 In most cases a changeset A has a single successors set containing a single
2393 successor (changeset A replaced by A').
2393 successor (changeset A replaced by A').
2394
2394
2395 A changeset that is made obsolete with no successors are called "pruned".
2395 A changeset that is made obsolete with no successors are called "pruned".
2396 Such changesets have no successors sets at all.
2396 Such changesets have no successors sets at all.
2397
2397
2398 A changeset that has been "split" will have a successors set containing
2398 A changeset that has been "split" will have a successors set containing
2399 more than one successor.
2399 more than one successor.
2400
2400
2401 A changeset that has been rewritten in multiple different ways is called
2401 A changeset that has been rewritten in multiple different ways is called
2402 "divergent". Such changesets have multiple successor sets (each of which
2402 "divergent". Such changesets have multiple successor sets (each of which
2403 may also be split, i.e. have multiple successors).
2403 may also be split, i.e. have multiple successors).
2404
2404
2405 Results are displayed as follows::
2405 Results are displayed as follows::
2406
2406
2407 <rev1>
2407 <rev1>
2408 <successors-1A>
2408 <successors-1A>
2409 <rev2>
2409 <rev2>
2410 <successors-2A>
2410 <successors-2A>
2411 <successors-2B1> <successors-2B2> <successors-2B3>
2411 <successors-2B1> <successors-2B2> <successors-2B3>
2412
2412
2413 Here rev2 has two possible (i.e. divergent) successors sets. The first
2413 Here rev2 has two possible (i.e. divergent) successors sets. The first
2414 holds one element, whereas the second holds three (i.e. the changeset has
2414 holds one element, whereas the second holds three (i.e. the changeset has
2415 been split).
2415 been split).
2416 """
2416 """
2417 # passed to successorssets caching computation from one call to another
2417 # passed to successorssets caching computation from one call to another
2418 cache = {}
2418 cache = {}
2419 ctx2str = bytes
2419 ctx2str = bytes
2420 node2str = short
2420 node2str = short
2421 for rev in scmutil.revrange(repo, revs):
2421 for rev in scmutil.revrange(repo, revs):
2422 ctx = repo[rev]
2422 ctx = repo[rev]
2423 ui.write('%s\n'% ctx2str(ctx))
2423 ui.write('%s\n'% ctx2str(ctx))
2424 for succsset in obsutil.successorssets(repo, ctx.node(),
2424 for succsset in obsutil.successorssets(repo, ctx.node(),
2425 closest=opts[r'closest'],
2425 closest=opts[r'closest'],
2426 cache=cache):
2426 cache=cache):
2427 if succsset:
2427 if succsset:
2428 ui.write(' ')
2428 ui.write(' ')
2429 ui.write(node2str(succsset[0]))
2429 ui.write(node2str(succsset[0]))
2430 for node in succsset[1:]:
2430 for node in succsset[1:]:
2431 ui.write(' ')
2431 ui.write(' ')
2432 ui.write(node2str(node))
2432 ui.write(node2str(node))
2433 ui.write('\n')
2433 ui.write('\n')
2434
2434
2435 @command('debugtemplate',
2435 @command('debugtemplate',
2436 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2436 [('r', 'rev', [], _('apply template on changesets'), _('REV')),
2437 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2437 ('D', 'define', [], _('define template keyword'), _('KEY=VALUE'))],
2438 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2438 _('[-r REV]... [-D KEY=VALUE]... TEMPLATE'),
2439 optionalrepo=True)
2439 optionalrepo=True)
2440 def debugtemplate(ui, repo, tmpl, **opts):
2440 def debugtemplate(ui, repo, tmpl, **opts):
2441 """parse and apply a template
2441 """parse and apply a template
2442
2442
2443 If -r/--rev is given, the template is processed as a log template and
2443 If -r/--rev is given, the template is processed as a log template and
2444 applied to the given changesets. Otherwise, it is processed as a generic
2444 applied to the given changesets. Otherwise, it is processed as a generic
2445 template.
2445 template.
2446
2446
2447 Use --verbose to print the parsed tree.
2447 Use --verbose to print the parsed tree.
2448 """
2448 """
2449 revs = None
2449 revs = None
2450 if opts[r'rev']:
2450 if opts[r'rev']:
2451 if repo is None:
2451 if repo is None:
2452 raise error.RepoError(_('there is no Mercurial repository here '
2452 raise error.RepoError(_('there is no Mercurial repository here '
2453 '(.hg not found)'))
2453 '(.hg not found)'))
2454 revs = scmutil.revrange(repo, opts[r'rev'])
2454 revs = scmutil.revrange(repo, opts[r'rev'])
2455
2455
2456 props = {}
2456 props = {}
2457 for d in opts[r'define']:
2457 for d in opts[r'define']:
2458 try:
2458 try:
2459 k, v = (e.strip() for e in d.split('=', 1))
2459 k, v = (e.strip() for e in d.split('=', 1))
2460 if not k or k == 'ui':
2460 if not k or k == 'ui':
2461 raise ValueError
2461 raise ValueError
2462 props[k] = v
2462 props[k] = v
2463 except ValueError:
2463 except ValueError:
2464 raise error.Abort(_('malformed keyword definition: %s') % d)
2464 raise error.Abort(_('malformed keyword definition: %s') % d)
2465
2465
2466 if ui.verbose:
2466 if ui.verbose:
2467 aliases = ui.configitems('templatealias')
2467 aliases = ui.configitems('templatealias')
2468 tree = templater.parse(tmpl)
2468 tree = templater.parse(tmpl)
2469 ui.note(templater.prettyformat(tree), '\n')
2469 ui.note(templater.prettyformat(tree), '\n')
2470 newtree = templater.expandaliases(tree, aliases)
2470 newtree = templater.expandaliases(tree, aliases)
2471 if newtree != tree:
2471 if newtree != tree:
2472 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2472 ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n')
2473
2473
2474 if revs is None:
2474 if revs is None:
2475 tres = formatter.templateresources(ui, repo)
2475 tres = formatter.templateresources(ui, repo)
2476 t = formatter.maketemplater(ui, tmpl, resources=tres)
2476 t = formatter.maketemplater(ui, tmpl, resources=tres)
2477 ui.write(t.renderdefault(props))
2477 ui.write(t.renderdefault(props))
2478 else:
2478 else:
2479 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2479 displayer = logcmdutil.maketemplater(ui, repo, tmpl)
2480 for r in revs:
2480 for r in revs:
2481 displayer.show(repo[r], **pycompat.strkwargs(props))
2481 displayer.show(repo[r], **pycompat.strkwargs(props))
2482 displayer.close()
2482 displayer.close()
2483
2483
2484 @command('debuguigetpass', [
2484 @command('debuguigetpass', [
2485 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2485 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2486 ], _('[-p TEXT]'), norepo=True)
2486 ], _('[-p TEXT]'), norepo=True)
2487 def debuguigetpass(ui, prompt=''):
2487 def debuguigetpass(ui, prompt=''):
2488 """show prompt to type password"""
2488 """show prompt to type password"""
2489 r = ui.getpass(prompt)
2489 r = ui.getpass(prompt)
2490 ui.write(('respose: %s\n') % r)
2490 ui.write(('respose: %s\n') % r)
2491
2491
2492 @command('debuguiprompt', [
2492 @command('debuguiprompt', [
2493 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2493 ('p', 'prompt', '', _('prompt text'), _('TEXT')),
2494 ], _('[-p TEXT]'), norepo=True)
2494 ], _('[-p TEXT]'), norepo=True)
2495 def debuguiprompt(ui, prompt=''):
2495 def debuguiprompt(ui, prompt=''):
2496 """show plain prompt"""
2496 """show plain prompt"""
2497 r = ui.prompt(prompt)
2497 r = ui.prompt(prompt)
2498 ui.write(('response: %s\n') % r)
2498 ui.write(('response: %s\n') % r)
2499
2499
2500 @command('debugupdatecaches', [])
2500 @command('debugupdatecaches', [])
2501 def debugupdatecaches(ui, repo, *pats, **opts):
2501 def debugupdatecaches(ui, repo, *pats, **opts):
2502 """warm all known caches in the repository"""
2502 """warm all known caches in the repository"""
2503 with repo.wlock(), repo.lock():
2503 with repo.wlock(), repo.lock():
2504 repo.updatecaches(full=True)
2504 repo.updatecaches(full=True)
2505
2505
2506 @command('debugupgraderepo', [
2506 @command('debugupgraderepo', [
2507 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2507 ('o', 'optimize', [], _('extra optimization to perform'), _('NAME')),
2508 ('', 'run', False, _('performs an upgrade')),
2508 ('', 'run', False, _('performs an upgrade')),
2509 ])
2509 ])
2510 def debugupgraderepo(ui, repo, run=False, optimize=None):
2510 def debugupgraderepo(ui, repo, run=False, optimize=None):
2511 """upgrade a repository to use different features
2511 """upgrade a repository to use different features
2512
2512
2513 If no arguments are specified, the repository is evaluated for upgrade
2513 If no arguments are specified, the repository is evaluated for upgrade
2514 and a list of problems and potential optimizations is printed.
2514 and a list of problems and potential optimizations is printed.
2515
2515
2516 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2516 With ``--run``, a repository upgrade is performed. Behavior of the upgrade
2517 can be influenced via additional arguments. More details will be provided
2517 can be influenced via additional arguments. More details will be provided
2518 by the command output when run without ``--run``.
2518 by the command output when run without ``--run``.
2519
2519
2520 During the upgrade, the repository will be locked and no writes will be
2520 During the upgrade, the repository will be locked and no writes will be
2521 allowed.
2521 allowed.
2522
2522
2523 At the end of the upgrade, the repository may not be readable while new
2523 At the end of the upgrade, the repository may not be readable while new
2524 repository data is swapped in. This window will be as long as it takes to
2524 repository data is swapped in. This window will be as long as it takes to
2525 rename some directories inside the ``.hg`` directory. On most machines, this
2525 rename some directories inside the ``.hg`` directory. On most machines, this
2526 should complete almost instantaneously and the chances of a consumer being
2526 should complete almost instantaneously and the chances of a consumer being
2527 unable to access the repository should be low.
2527 unable to access the repository should be low.
2528 """
2528 """
2529 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2529 return upgrade.upgraderepo(ui, repo, run=run, optimize=optimize)
2530
2530
2531 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2531 @command('debugwalk', cmdutil.walkopts, _('[OPTION]... [FILE]...'),
2532 inferrepo=True)
2532 inferrepo=True)
2533 def debugwalk(ui, repo, *pats, **opts):
2533 def debugwalk(ui, repo, *pats, **opts):
2534 """show how files match on given patterns"""
2534 """show how files match on given patterns"""
2535 opts = pycompat.byteskwargs(opts)
2535 opts = pycompat.byteskwargs(opts)
2536 m = scmutil.match(repo[None], pats, opts)
2536 m = scmutil.match(repo[None], pats, opts)
2537 ui.write(('matcher: %r\n' % m))
2537 ui.write(('matcher: %r\n' % m))
2538 items = list(repo[None].walk(m))
2538 items = list(repo[None].walk(m))
2539 if not items:
2539 if not items:
2540 return
2540 return
2541 f = lambda fn: fn
2541 f = lambda fn: fn
2542 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2542 if ui.configbool('ui', 'slash') and pycompat.ossep != '/':
2543 f = lambda fn: util.normpath(fn)
2543 f = lambda fn: util.normpath(fn)
2544 fmt = 'f %%-%ds %%-%ds %%s' % (
2544 fmt = 'f %%-%ds %%-%ds %%s' % (
2545 max([len(abs) for abs in items]),
2545 max([len(abs) for abs in items]),
2546 max([len(m.rel(abs)) for abs in items]))
2546 max([len(m.rel(abs)) for abs in items]))
2547 for abs in items:
2547 for abs in items:
2548 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2548 line = fmt % (abs, f(m.rel(abs)), m.exact(abs) and 'exact' or '')
2549 ui.write("%s\n" % line.rstrip())
2549 ui.write("%s\n" % line.rstrip())
2550
2550
2551 @command('debugwhyunstable', [], _('REV'))
2551 @command('debugwhyunstable', [], _('REV'))
2552 def debugwhyunstable(ui, repo, rev):
2552 def debugwhyunstable(ui, repo, rev):
2553 """explain instabilities of a changeset"""
2553 """explain instabilities of a changeset"""
2554 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2554 for entry in obsutil.whyunstable(repo, scmutil.revsingle(repo, rev)):
2555 dnodes = ''
2555 dnodes = ''
2556 if entry.get('divergentnodes'):
2556 if entry.get('divergentnodes'):
2557 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2557 dnodes = ' '.join('%s (%s)' % (ctx.hex(), ctx.phasestr())
2558 for ctx in entry['divergentnodes']) + ' '
2558 for ctx in entry['divergentnodes']) + ' '
2559 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2559 ui.write('%s: %s%s %s\n' % (entry['instability'], dnodes,
2560 entry['reason'], entry['node']))
2560 entry['reason'], entry['node']))
2561
2561
2562 @command('debugwireargs',
2562 @command('debugwireargs',
2563 [('', 'three', '', 'three'),
2563 [('', 'three', '', 'three'),
2564 ('', 'four', '', 'four'),
2564 ('', 'four', '', 'four'),
2565 ('', 'five', '', 'five'),
2565 ('', 'five', '', 'five'),
2566 ] + cmdutil.remoteopts,
2566 ] + cmdutil.remoteopts,
2567 _('REPO [OPTIONS]... [ONE [TWO]]'),
2567 _('REPO [OPTIONS]... [ONE [TWO]]'),
2568 norepo=True)
2568 norepo=True)
2569 def debugwireargs(ui, repopath, *vals, **opts):
2569 def debugwireargs(ui, repopath, *vals, **opts):
2570 opts = pycompat.byteskwargs(opts)
2570 opts = pycompat.byteskwargs(opts)
2571 repo = hg.peer(ui, opts, repopath)
2571 repo = hg.peer(ui, opts, repopath)
2572 for opt in cmdutil.remoteopts:
2572 for opt in cmdutil.remoteopts:
2573 del opts[opt[1]]
2573 del opts[opt[1]]
2574 args = {}
2574 args = {}
2575 for k, v in opts.iteritems():
2575 for k, v in opts.iteritems():
2576 if v:
2576 if v:
2577 args[k] = v
2577 args[k] = v
2578 args = pycompat.strkwargs(args)
2578 args = pycompat.strkwargs(args)
2579 # run twice to check that we don't mess up the stream for the next command
2579 # run twice to check that we don't mess up the stream for the next command
2580 res1 = repo.debugwireargs(*vals, **args)
2580 res1 = repo.debugwireargs(*vals, **args)
2581 res2 = repo.debugwireargs(*vals, **args)
2581 res2 = repo.debugwireargs(*vals, **args)
2582 ui.write("%s\n" % res1)
2582 ui.write("%s\n" % res1)
2583 if res1 != res2:
2583 if res1 != res2:
2584 ui.warn("%s\n" % res2)
2584 ui.warn("%s\n" % res2)
2585
2585
2586 def _parsewirelangblocks(fh):
2586 def _parsewirelangblocks(fh):
2587 activeaction = None
2587 activeaction = None
2588 blocklines = []
2588 blocklines = []
2589
2589
2590 for line in fh:
2590 for line in fh:
2591 line = line.rstrip()
2591 line = line.rstrip()
2592 if not line:
2592 if not line:
2593 continue
2593 continue
2594
2594
2595 if line.startswith(b'#'):
2595 if line.startswith(b'#'):
2596 continue
2596 continue
2597
2597
2598 if not line.startswith(' '):
2598 if not line.startswith(' '):
2599 # New block. Flush previous one.
2599 # New block. Flush previous one.
2600 if activeaction:
2600 if activeaction:
2601 yield activeaction, blocklines
2601 yield activeaction, blocklines
2602
2602
2603 activeaction = line
2603 activeaction = line
2604 blocklines = []
2604 blocklines = []
2605 continue
2605 continue
2606
2606
2607 # Else we start with an indent.
2607 # Else we start with an indent.
2608
2608
2609 if not activeaction:
2609 if not activeaction:
2610 raise error.Abort(_('indented line outside of block'))
2610 raise error.Abort(_('indented line outside of block'))
2611
2611
2612 blocklines.append(line)
2612 blocklines.append(line)
2613
2613
2614 # Flush last block.
2614 # Flush last block.
2615 if activeaction:
2615 if activeaction:
2616 yield activeaction, blocklines
2616 yield activeaction, blocklines
2617
2617
2618 @command('debugwireproto',
2618 @command('debugwireproto',
2619 [
2619 [
2620 ('', 'localssh', False, _('start an SSH server for this repo')),
2620 ('', 'localssh', False, _('start an SSH server for this repo')),
2621 ('', 'peer', '', _('construct a specific version of the peer')),
2621 ('', 'peer', '', _('construct a specific version of the peer')),
2622 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2622 ('', 'noreadstderr', False, _('do not read from stderr of the remote')),
2623 ] + cmdutil.remoteopts,
2623 ] + cmdutil.remoteopts,
2624 _('[PATH]'),
2624 _('[PATH]'),
2625 optionalrepo=True)
2625 optionalrepo=True)
2626 def debugwireproto(ui, repo, path=None, **opts):
2626 def debugwireproto(ui, repo, path=None, **opts):
2627 """send wire protocol commands to a server
2627 """send wire protocol commands to a server
2628
2628
2629 This command can be used to issue wire protocol commands to remote
2629 This command can be used to issue wire protocol commands to remote
2630 peers and to debug the raw data being exchanged.
2630 peers and to debug the raw data being exchanged.
2631
2631
2632 ``--localssh`` will start an SSH server against the current repository
2632 ``--localssh`` will start an SSH server against the current repository
2633 and connect to that. By default, the connection will perform a handshake
2633 and connect to that. By default, the connection will perform a handshake
2634 and establish an appropriate peer instance.
2634 and establish an appropriate peer instance.
2635
2635
2636 ``--peer`` can be used to bypass the handshake protocol and construct a
2636 ``--peer`` can be used to bypass the handshake protocol and construct a
2637 peer instance using the specified class type. Valid values are ``raw``,
2637 peer instance using the specified class type. Valid values are ``raw``,
2638 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2638 ``http2``, ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending
2639 raw data payloads and don't support higher-level command actions.
2639 raw data payloads and don't support higher-level command actions.
2640
2640
2641 ``--noreadstderr`` can be used to disable automatic reading from stderr
2641 ``--noreadstderr`` can be used to disable automatic reading from stderr
2642 of the peer (for SSH connections only). Disabling automatic reading of
2642 of the peer (for SSH connections only). Disabling automatic reading of
2643 stderr is useful for making output more deterministic.
2643 stderr is useful for making output more deterministic.
2644
2644
2645 Commands are issued via a mini language which is specified via stdin.
2645 Commands are issued via a mini language which is specified via stdin.
2646 The language consists of individual actions to perform. An action is
2646 The language consists of individual actions to perform. An action is
2647 defined by a block. A block is defined as a line with no leading
2647 defined by a block. A block is defined as a line with no leading
2648 space followed by 0 or more lines with leading space. Blocks are
2648 space followed by 0 or more lines with leading space. Blocks are
2649 effectively a high-level command with additional metadata.
2649 effectively a high-level command with additional metadata.
2650
2650
2651 Lines beginning with ``#`` are ignored.
2651 Lines beginning with ``#`` are ignored.
2652
2652
2653 The following sections denote available actions.
2653 The following sections denote available actions.
2654
2654
2655 raw
2655 raw
2656 ---
2656 ---
2657
2657
2658 Send raw data to the server.
2658 Send raw data to the server.
2659
2659
2660 The block payload contains the raw data to send as one atomic send
2660 The block payload contains the raw data to send as one atomic send
2661 operation. The data may not actually be delivered in a single system
2661 operation. The data may not actually be delivered in a single system
2662 call: it depends on the abilities of the transport being used.
2662 call: it depends on the abilities of the transport being used.
2663
2663
2664 Each line in the block is de-indented and concatenated. Then, that
2664 Each line in the block is de-indented and concatenated. Then, that
2665 value is evaluated as a Python b'' literal. This allows the use of
2665 value is evaluated as a Python b'' literal. This allows the use of
2666 backslash escaping, etc.
2666 backslash escaping, etc.
2667
2667
2668 raw+
2668 raw+
2669 ----
2669 ----
2670
2670
2671 Behaves like ``raw`` except flushes output afterwards.
2671 Behaves like ``raw`` except flushes output afterwards.
2672
2672
2673 command <X>
2673 command <X>
2674 -----------
2674 -----------
2675
2675
2676 Send a request to run a named command, whose name follows the ``command``
2676 Send a request to run a named command, whose name follows the ``command``
2677 string.
2677 string.
2678
2678
2679 Arguments to the command are defined as lines in this block. The format of
2679 Arguments to the command are defined as lines in this block. The format of
2680 each line is ``<key> <value>``. e.g.::
2680 each line is ``<key> <value>``. e.g.::
2681
2681
2682 command listkeys
2682 command listkeys
2683 namespace bookmarks
2683 namespace bookmarks
2684
2684
2685 If the value begins with ``eval:``, it will be interpreted as a Python
2685 If the value begins with ``eval:``, it will be interpreted as a Python
2686 literal expression. Otherwise values are interpreted as Python b'' literals.
2686 literal expression. Otherwise values are interpreted as Python b'' literals.
2687 This allows sending complex types and encoding special byte sequences via
2687 This allows sending complex types and encoding special byte sequences via
2688 backslash escaping.
2688 backslash escaping.
2689
2689
2690 The following arguments have special meaning:
2690 The following arguments have special meaning:
2691
2691
2692 ``PUSHFILE``
2692 ``PUSHFILE``
2693 When defined, the *push* mechanism of the peer will be used instead
2693 When defined, the *push* mechanism of the peer will be used instead
2694 of the static request-response mechanism and the content of the
2694 of the static request-response mechanism and the content of the
2695 file specified in the value of this argument will be sent as the
2695 file specified in the value of this argument will be sent as the
2696 command payload.
2696 command payload.
2697
2697
2698 This can be used to submit a local bundle file to the remote.
2698 This can be used to submit a local bundle file to the remote.
2699
2699
2700 batchbegin
2700 batchbegin
2701 ----------
2701 ----------
2702
2702
2703 Instruct the peer to begin a batched send.
2703 Instruct the peer to begin a batched send.
2704
2704
2705 All ``command`` blocks are queued for execution until the next
2705 All ``command`` blocks are queued for execution until the next
2706 ``batchsubmit`` block.
2706 ``batchsubmit`` block.
2707
2707
2708 batchsubmit
2708 batchsubmit
2709 -----------
2709 -----------
2710
2710
2711 Submit previously queued ``command`` blocks as a batch request.
2711 Submit previously queued ``command`` blocks as a batch request.
2712
2712
2713 This action MUST be paired with a ``batchbegin`` action.
2713 This action MUST be paired with a ``batchbegin`` action.
2714
2714
2715 httprequest <method> <path>
2715 httprequest <method> <path>
2716 ---------------------------
2716 ---------------------------
2717
2717
2718 (HTTP peer only)
2718 (HTTP peer only)
2719
2719
2720 Send an HTTP request to the peer.
2720 Send an HTTP request to the peer.
2721
2721
2722 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2722 The HTTP request line follows the ``httprequest`` action. e.g. ``GET /foo``.
2723
2723
2724 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2724 Arguments of the form ``<key>: <value>`` are interpreted as HTTP request
2725 headers to add to the request. e.g. ``Accept: foo``.
2725 headers to add to the request. e.g. ``Accept: foo``.
2726
2726
2727 The following arguments are special:
2727 The following arguments are special:
2728
2728
2729 ``BODYFILE``
2729 ``BODYFILE``
2730 The content of the file defined as the value to this argument will be
2730 The content of the file defined as the value to this argument will be
2731 transferred verbatim as the HTTP request body.
2731 transferred verbatim as the HTTP request body.
2732
2732
2733 ``frame <type> <flags> <payload>``
2733 ``frame <type> <flags> <payload>``
2734 Send a unified protocol frame as part of the request body.
2734 Send a unified protocol frame as part of the request body.
2735
2735
2736 All frames will be collected and sent as the body to the HTTP
2736 All frames will be collected and sent as the body to the HTTP
2737 request.
2737 request.
2738
2738
2739 close
2739 close
2740 -----
2740 -----
2741
2741
2742 Close the connection to the server.
2742 Close the connection to the server.
2743
2743
2744 flush
2744 flush
2745 -----
2745 -----
2746
2746
2747 Flush data written to the server.
2747 Flush data written to the server.
2748
2748
2749 readavailable
2749 readavailable
2750 -------------
2750 -------------
2751
2751
2752 Close the write end of the connection and read all available data from
2752 Close the write end of the connection and read all available data from
2753 the server.
2753 the server.
2754
2754
2755 If the connection to the server encompasses multiple pipes, we poll both
2755 If the connection to the server encompasses multiple pipes, we poll both
2756 pipes and read available data.
2756 pipes and read available data.
2757
2757
2758 readline
2758 readline
2759 --------
2759 --------
2760
2760
2761 Read a line of output from the server. If there are multiple output
2761 Read a line of output from the server. If there are multiple output
2762 pipes, reads only the main pipe.
2762 pipes, reads only the main pipe.
2763
2763
2764 ereadline
2764 ereadline
2765 ---------
2765 ---------
2766
2766
2767 Like ``readline``, but read from the stderr pipe, if available.
2767 Like ``readline``, but read from the stderr pipe, if available.
2768
2768
2769 read <X>
2769 read <X>
2770 --------
2770 --------
2771
2771
2772 ``read()`` N bytes from the server's main output pipe.
2772 ``read()`` N bytes from the server's main output pipe.
2773
2773
2774 eread <X>
2774 eread <X>
2775 ---------
2775 ---------
2776
2776
2777 ``read()`` N bytes from the server's stderr pipe, if available.
2777 ``read()`` N bytes from the server's stderr pipe, if available.
2778
2778
2779 Specifying Unified Frame-Based Protocol Frames
2779 Specifying Unified Frame-Based Protocol Frames
2780 ----------------------------------------------
2780 ----------------------------------------------
2781
2781
2782 It is possible to emit a *Unified Frame-Based Protocol* by using special
2782 It is possible to emit a *Unified Frame-Based Protocol* by using special
2783 syntax.
2783 syntax.
2784
2784
2785 A frame is composed as a type, flags, and payload. These can be parsed
2785 A frame is composed as a type, flags, and payload. These can be parsed
2786 from a string of the form:
2786 from a string of the form:
2787
2787
2788 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
2788 <request-id> <stream-id> <stream-flags> <type> <flags> <payload>
2789
2789
2790 ``request-id`` and ``stream-id`` are integers defining the request and
2790 ``request-id`` and ``stream-id`` are integers defining the request and
2791 stream identifiers.
2791 stream identifiers.
2792
2792
2793 ``type`` can be an integer value for the frame type or the string name
2793 ``type`` can be an integer value for the frame type or the string name
2794 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2794 of the type. The strings are defined in ``wireprotoframing.py``. e.g.
2795 ``command-name``.
2795 ``command-name``.
2796
2796
2797 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
2797 ``stream-flags`` and ``flags`` are a ``|`` delimited list of flag
2798 components. Each component (and there can be just one) can be an integer
2798 components. Each component (and there can be just one) can be an integer
2799 or a flag name for stream flags or frame flags, respectively. Values are
2799 or a flag name for stream flags or frame flags, respectively. Values are
2800 resolved to integers and then bitwise OR'd together.
2800 resolved to integers and then bitwise OR'd together.
2801
2801
2802 ``payload`` represents the raw frame payload. If it begins with
2802 ``payload`` represents the raw frame payload. If it begins with
2803 ``cbor:``, the following string is evaluated as Python code and the
2803 ``cbor:``, the following string is evaluated as Python code and the
2804 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
2804 resulting object is fed into a CBOR encoder. Otherwise it is interpreted
2805 as a Python byte string literal.
2805 as a Python byte string literal.
2806 """
2806 """
2807 opts = pycompat.byteskwargs(opts)
2807 opts = pycompat.byteskwargs(opts)
2808
2808
2809 if opts['localssh'] and not repo:
2809 if opts['localssh'] and not repo:
2810 raise error.Abort(_('--localssh requires a repository'))
2810 raise error.Abort(_('--localssh requires a repository'))
2811
2811
2812 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
2812 if opts['peer'] and opts['peer'] not in ('raw', 'http2', 'ssh1', 'ssh2'):
2813 raise error.Abort(_('invalid value for --peer'),
2813 raise error.Abort(_('invalid value for --peer'),
2814 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2814 hint=_('valid values are "raw", "ssh1", and "ssh2"'))
2815
2815
2816 if path and opts['localssh']:
2816 if path and opts['localssh']:
2817 raise error.Abort(_('cannot specify --localssh with an explicit '
2817 raise error.Abort(_('cannot specify --localssh with an explicit '
2818 'path'))
2818 'path'))
2819
2819
2820 if ui.interactive():
2820 if ui.interactive():
2821 ui.write(_('(waiting for commands on stdin)\n'))
2821 ui.write(_('(waiting for commands on stdin)\n'))
2822
2822
2823 blocks = list(_parsewirelangblocks(ui.fin))
2823 blocks = list(_parsewirelangblocks(ui.fin))
2824
2824
2825 proc = None
2825 proc = None
2826 stdin = None
2826 stdin = None
2827 stdout = None
2827 stdout = None
2828 stderr = None
2828 stderr = None
2829 opener = None
2829 opener = None
2830
2830
2831 if opts['localssh']:
2831 if opts['localssh']:
2832 # We start the SSH server in its own process so there is process
2832 # We start the SSH server in its own process so there is process
2833 # separation. This prevents a whole class of potential bugs around
2833 # separation. This prevents a whole class of potential bugs around
2834 # shared state from interfering with server operation.
2834 # shared state from interfering with server operation.
2835 args = procutil.hgcmd() + [
2835 args = procutil.hgcmd() + [
2836 '-R', repo.root,
2836 '-R', repo.root,
2837 'debugserve', '--sshstdio',
2837 'debugserve', '--sshstdio',
2838 ]
2838 ]
2839 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2839 proc = subprocess.Popen(args, stdin=subprocess.PIPE,
2840 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2840 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
2841 bufsize=0)
2841 bufsize=0)
2842
2842
2843 stdin = proc.stdin
2843 stdin = proc.stdin
2844 stdout = proc.stdout
2844 stdout = proc.stdout
2845 stderr = proc.stderr
2845 stderr = proc.stderr
2846
2846
2847 # We turn the pipes into observers so we can log I/O.
2847 # We turn the pipes into observers so we can log I/O.
2848 if ui.verbose or opts['peer'] == 'raw':
2848 if ui.verbose or opts['peer'] == 'raw':
2849 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2849 stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
2850 logdata=True)
2850 logdata=True)
2851 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2851 stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
2852 logdata=True)
2852 logdata=True)
2853 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2853 stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
2854 logdata=True)
2854 logdata=True)
2855
2855
2856 # --localssh also implies the peer connection settings.
2856 # --localssh also implies the peer connection settings.
2857
2857
2858 url = 'ssh://localserver'
2858 url = 'ssh://localserver'
2859 autoreadstderr = not opts['noreadstderr']
2859 autoreadstderr = not opts['noreadstderr']
2860
2860
2861 if opts['peer'] == 'ssh1':
2861 if opts['peer'] == 'ssh1':
2862 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2862 ui.write(_('creating ssh peer for wire protocol version 1\n'))
2863 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2863 peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
2864 None, autoreadstderr=autoreadstderr)
2864 None, autoreadstderr=autoreadstderr)
2865 elif opts['peer'] == 'ssh2':
2865 elif opts['peer'] == 'ssh2':
2866 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2866 ui.write(_('creating ssh peer for wire protocol version 2\n'))
2867 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2867 peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
2868 None, autoreadstderr=autoreadstderr)
2868 None, autoreadstderr=autoreadstderr)
2869 elif opts['peer'] == 'raw':
2869 elif opts['peer'] == 'raw':
2870 ui.write(_('using raw connection to peer\n'))
2870 ui.write(_('using raw connection to peer\n'))
2871 peer = None
2871 peer = None
2872 else:
2872 else:
2873 ui.write(_('creating ssh peer from handshake results\n'))
2873 ui.write(_('creating ssh peer from handshake results\n'))
2874 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2874 peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr,
2875 autoreadstderr=autoreadstderr)
2875 autoreadstderr=autoreadstderr)
2876
2876
2877 elif path:
2877 elif path:
2878 # We bypass hg.peer() so we can proxy the sockets.
2878 # We bypass hg.peer() so we can proxy the sockets.
2879 # TODO consider not doing this because we skip
2879 # TODO consider not doing this because we skip
2880 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2880 # ``hg.wirepeersetupfuncs`` and potentially other useful functionality.
2881 u = util.url(path)
2881 u = util.url(path)
2882 if u.scheme != 'http':
2882 if u.scheme != 'http':
2883 raise error.Abort(_('only http:// paths are currently supported'))
2883 raise error.Abort(_('only http:// paths are currently supported'))
2884
2884
2885 url, authinfo = u.authinfo()
2885 url, authinfo = u.authinfo()
2886 openerargs = {
2886 openerargs = {
2887 r'useragent': b'Mercurial debugwireproto',
2887 r'useragent': b'Mercurial debugwireproto',
2888 }
2888 }
2889
2889
2890 # Turn pipes/sockets into observers so we can log I/O.
2890 # Turn pipes/sockets into observers so we can log I/O.
2891 if ui.verbose:
2891 if ui.verbose:
2892 openerargs.update({
2892 openerargs.update({
2893 r'loggingfh': ui,
2893 r'loggingfh': ui,
2894 r'loggingname': b's',
2894 r'loggingname': b's',
2895 r'loggingopts': {
2895 r'loggingopts': {
2896 r'logdata': True,
2896 r'logdata': True,
2897 r'logdataapis': False,
2897 r'logdataapis': False,
2898 },
2898 },
2899 })
2899 })
2900
2900
2901 if ui.debugflag:
2901 if ui.debugflag:
2902 openerargs[r'loggingopts'][r'logdataapis'] = True
2902 openerargs[r'loggingopts'][r'logdataapis'] = True
2903
2903
2904 # Don't send default headers when in raw mode. This allows us to
2904 # Don't send default headers when in raw mode. This allows us to
2905 # bypass most of the behavior of our URL handling code so we can
2905 # bypass most of the behavior of our URL handling code so we can
2906 # have near complete control over what's sent on the wire.
2906 # have near complete control over what's sent on the wire.
2907 if opts['peer'] == 'raw':
2907 if opts['peer'] == 'raw':
2908 openerargs[r'sendaccept'] = False
2908 openerargs[r'sendaccept'] = False
2909
2909
2910 opener = urlmod.opener(ui, authinfo, **openerargs)
2910 opener = urlmod.opener(ui, authinfo, **openerargs)
2911
2911
2912 if opts['peer'] == 'http2':
2912 if opts['peer'] == 'http2':
2913 ui.write(_('creating http peer for wire protocol version 2\n'))
2913 ui.write(_('creating http peer for wire protocol version 2\n'))
2914 peer = httppeer.httpv2peer(
2914 peer = httppeer.httpv2peer(
2915 ui, path, 'api/%s' % wireprototypes.HTTPV2,
2915 ui, path, 'api/%s' % wireprototypes.HTTP_WIREPROTO_V2,
2916 opener, httppeer.urlreq.request, {})
2916 opener, httppeer.urlreq.request, {})
2917 elif opts['peer'] == 'raw':
2917 elif opts['peer'] == 'raw':
2918 ui.write(_('using raw connection to peer\n'))
2918 ui.write(_('using raw connection to peer\n'))
2919 peer = None
2919 peer = None
2920 elif opts['peer']:
2920 elif opts['peer']:
2921 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2921 raise error.Abort(_('--peer %s not supported with HTTP peers') %
2922 opts['peer'])
2922 opts['peer'])
2923 else:
2923 else:
2924 peer = httppeer.makepeer(ui, path, opener=opener)
2924 peer = httppeer.makepeer(ui, path, opener=opener)
2925
2925
2926 # We /could/ populate stdin/stdout with sock.makefile()...
2926 # We /could/ populate stdin/stdout with sock.makefile()...
2927 else:
2927 else:
2928 raise error.Abort(_('unsupported connection configuration'))
2928 raise error.Abort(_('unsupported connection configuration'))
2929
2929
2930 batchedcommands = None
2930 batchedcommands = None
2931
2931
2932 # Now perform actions based on the parsed wire language instructions.
2932 # Now perform actions based on the parsed wire language instructions.
2933 for action, lines in blocks:
2933 for action, lines in blocks:
2934 if action in ('raw', 'raw+'):
2934 if action in ('raw', 'raw+'):
2935 if not stdin:
2935 if not stdin:
2936 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2936 raise error.Abort(_('cannot call raw/raw+ on this peer'))
2937
2937
2938 # Concatenate the data together.
2938 # Concatenate the data together.
2939 data = ''.join(l.lstrip() for l in lines)
2939 data = ''.join(l.lstrip() for l in lines)
2940 data = stringutil.unescapestr(data)
2940 data = stringutil.unescapestr(data)
2941 stdin.write(data)
2941 stdin.write(data)
2942
2942
2943 if action == 'raw+':
2943 if action == 'raw+':
2944 stdin.flush()
2944 stdin.flush()
2945 elif action == 'flush':
2945 elif action == 'flush':
2946 if not stdin:
2946 if not stdin:
2947 raise error.Abort(_('cannot call flush on this peer'))
2947 raise error.Abort(_('cannot call flush on this peer'))
2948 stdin.flush()
2948 stdin.flush()
2949 elif action.startswith('command'):
2949 elif action.startswith('command'):
2950 if not peer:
2950 if not peer:
2951 raise error.Abort(_('cannot send commands unless peer instance '
2951 raise error.Abort(_('cannot send commands unless peer instance '
2952 'is available'))
2952 'is available'))
2953
2953
2954 command = action.split(' ', 1)[1]
2954 command = action.split(' ', 1)[1]
2955
2955
2956 args = {}
2956 args = {}
2957 for line in lines:
2957 for line in lines:
2958 # We need to allow empty values.
2958 # We need to allow empty values.
2959 fields = line.lstrip().split(' ', 1)
2959 fields = line.lstrip().split(' ', 1)
2960 if len(fields) == 1:
2960 if len(fields) == 1:
2961 key = fields[0]
2961 key = fields[0]
2962 value = ''
2962 value = ''
2963 else:
2963 else:
2964 key, value = fields
2964 key, value = fields
2965
2965
2966 if value.startswith('eval:'):
2966 if value.startswith('eval:'):
2967 value = stringutil.evalpythonliteral(value[5:])
2967 value = stringutil.evalpythonliteral(value[5:])
2968 else:
2968 else:
2969 value = stringutil.unescapestr(value)
2969 value = stringutil.unescapestr(value)
2970
2970
2971 args[key] = value
2971 args[key] = value
2972
2972
2973 if batchedcommands is not None:
2973 if batchedcommands is not None:
2974 batchedcommands.append((command, args))
2974 batchedcommands.append((command, args))
2975 continue
2975 continue
2976
2976
2977 ui.status(_('sending %s command\n') % command)
2977 ui.status(_('sending %s command\n') % command)
2978
2978
2979 if 'PUSHFILE' in args:
2979 if 'PUSHFILE' in args:
2980 with open(args['PUSHFILE'], r'rb') as fh:
2980 with open(args['PUSHFILE'], r'rb') as fh:
2981 del args['PUSHFILE']
2981 del args['PUSHFILE']
2982 res, output = peer._callpush(command, fh,
2982 res, output = peer._callpush(command, fh,
2983 **pycompat.strkwargs(args))
2983 **pycompat.strkwargs(args))
2984 ui.status(_('result: %s\n') % stringutil.escapestr(res))
2984 ui.status(_('result: %s\n') % stringutil.escapestr(res))
2985 ui.status(_('remote output: %s\n') %
2985 ui.status(_('remote output: %s\n') %
2986 stringutil.escapestr(output))
2986 stringutil.escapestr(output))
2987 else:
2987 else:
2988 res = peer._call(command, **pycompat.strkwargs(args))
2988 res = peer._call(command, **pycompat.strkwargs(args))
2989 ui.status(_('response: %s\n') % stringutil.pprint(res))
2989 ui.status(_('response: %s\n') % stringutil.pprint(res))
2990
2990
2991 elif action == 'batchbegin':
2991 elif action == 'batchbegin':
2992 if batchedcommands is not None:
2992 if batchedcommands is not None:
2993 raise error.Abort(_('nested batchbegin not allowed'))
2993 raise error.Abort(_('nested batchbegin not allowed'))
2994
2994
2995 batchedcommands = []
2995 batchedcommands = []
2996 elif action == 'batchsubmit':
2996 elif action == 'batchsubmit':
2997 # There is a batching API we could go through. But it would be
2997 # There is a batching API we could go through. But it would be
2998 # difficult to normalize requests into function calls. It is easier
2998 # difficult to normalize requests into function calls. It is easier
2999 # to bypass this layer and normalize to commands + args.
2999 # to bypass this layer and normalize to commands + args.
3000 ui.status(_('sending batch with %d sub-commands\n') %
3000 ui.status(_('sending batch with %d sub-commands\n') %
3001 len(batchedcommands))
3001 len(batchedcommands))
3002 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3002 for i, chunk in enumerate(peer._submitbatch(batchedcommands)):
3003 ui.status(_('response #%d: %s\n') %
3003 ui.status(_('response #%d: %s\n') %
3004 (i, stringutil.escapestr(chunk)))
3004 (i, stringutil.escapestr(chunk)))
3005
3005
3006 batchedcommands = None
3006 batchedcommands = None
3007
3007
3008 elif action.startswith('httprequest '):
3008 elif action.startswith('httprequest '):
3009 if not opener:
3009 if not opener:
3010 raise error.Abort(_('cannot use httprequest without an HTTP '
3010 raise error.Abort(_('cannot use httprequest without an HTTP '
3011 'peer'))
3011 'peer'))
3012
3012
3013 request = action.split(' ', 2)
3013 request = action.split(' ', 2)
3014 if len(request) != 3:
3014 if len(request) != 3:
3015 raise error.Abort(_('invalid httprequest: expected format is '
3015 raise error.Abort(_('invalid httprequest: expected format is '
3016 '"httprequest <method> <path>'))
3016 '"httprequest <method> <path>'))
3017
3017
3018 method, httppath = request[1:]
3018 method, httppath = request[1:]
3019 headers = {}
3019 headers = {}
3020 body = None
3020 body = None
3021 frames = []
3021 frames = []
3022 for line in lines:
3022 for line in lines:
3023 line = line.lstrip()
3023 line = line.lstrip()
3024 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3024 m = re.match(b'^([a-zA-Z0-9_-]+): (.*)$', line)
3025 if m:
3025 if m:
3026 headers[m.group(1)] = m.group(2)
3026 headers[m.group(1)] = m.group(2)
3027 continue
3027 continue
3028
3028
3029 if line.startswith(b'BODYFILE '):
3029 if line.startswith(b'BODYFILE '):
3030 with open(line.split(b' ', 1), 'rb') as fh:
3030 with open(line.split(b' ', 1), 'rb') as fh:
3031 body = fh.read()
3031 body = fh.read()
3032 elif line.startswith(b'frame '):
3032 elif line.startswith(b'frame '):
3033 frame = wireprotoframing.makeframefromhumanstring(
3033 frame = wireprotoframing.makeframefromhumanstring(
3034 line[len(b'frame '):])
3034 line[len(b'frame '):])
3035
3035
3036 frames.append(frame)
3036 frames.append(frame)
3037 else:
3037 else:
3038 raise error.Abort(_('unknown argument to httprequest: %s') %
3038 raise error.Abort(_('unknown argument to httprequest: %s') %
3039 line)
3039 line)
3040
3040
3041 url = path + httppath
3041 url = path + httppath
3042
3042
3043 if frames:
3043 if frames:
3044 body = b''.join(bytes(f) for f in frames)
3044 body = b''.join(bytes(f) for f in frames)
3045
3045
3046 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3046 req = urlmod.urlreq.request(pycompat.strurl(url), body, headers)
3047
3047
3048 # urllib.Request insists on using has_data() as a proxy for
3048 # urllib.Request insists on using has_data() as a proxy for
3049 # determining the request method. Override that to use our
3049 # determining the request method. Override that to use our
3050 # explicitly requested method.
3050 # explicitly requested method.
3051 req.get_method = lambda: method
3051 req.get_method = lambda: method
3052
3052
3053 try:
3053 try:
3054 res = opener.open(req)
3054 res = opener.open(req)
3055 body = res.read()
3055 body = res.read()
3056 except util.urlerr.urlerror as e:
3056 except util.urlerr.urlerror as e:
3057 e.read()
3057 e.read()
3058 continue
3058 continue
3059
3059
3060 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3060 if res.headers.get('Content-Type') == 'application/mercurial-cbor':
3061 ui.write(_('cbor> %s\n') % stringutil.pprint(cbor.loads(body)))
3061 ui.write(_('cbor> %s\n') % stringutil.pprint(cbor.loads(body)))
3062
3062
3063 elif action == 'close':
3063 elif action == 'close':
3064 peer.close()
3064 peer.close()
3065 elif action == 'readavailable':
3065 elif action == 'readavailable':
3066 if not stdout or not stderr:
3066 if not stdout or not stderr:
3067 raise error.Abort(_('readavailable not available on this peer'))
3067 raise error.Abort(_('readavailable not available on this peer'))
3068
3068
3069 stdin.close()
3069 stdin.close()
3070 stdout.read()
3070 stdout.read()
3071 stderr.read()
3071 stderr.read()
3072
3072
3073 elif action == 'readline':
3073 elif action == 'readline':
3074 if not stdout:
3074 if not stdout:
3075 raise error.Abort(_('readline not available on this peer'))
3075 raise error.Abort(_('readline not available on this peer'))
3076 stdout.readline()
3076 stdout.readline()
3077 elif action == 'ereadline':
3077 elif action == 'ereadline':
3078 if not stderr:
3078 if not stderr:
3079 raise error.Abort(_('ereadline not available on this peer'))
3079 raise error.Abort(_('ereadline not available on this peer'))
3080 stderr.readline()
3080 stderr.readline()
3081 elif action.startswith('read '):
3081 elif action.startswith('read '):
3082 count = int(action.split(' ', 1)[1])
3082 count = int(action.split(' ', 1)[1])
3083 if not stdout:
3083 if not stdout:
3084 raise error.Abort(_('read not available on this peer'))
3084 raise error.Abort(_('read not available on this peer'))
3085 stdout.read(count)
3085 stdout.read(count)
3086 elif action.startswith('eread '):
3086 elif action.startswith('eread '):
3087 count = int(action.split(' ', 1)[1])
3087 count = int(action.split(' ', 1)[1])
3088 if not stderr:
3088 if not stderr:
3089 raise error.Abort(_('eread not available on this peer'))
3089 raise error.Abort(_('eread not available on this peer'))
3090 stderr.read(count)
3090 stderr.read(count)
3091 else:
3091 else:
3092 raise error.Abort(_('unknown action: %s') % action)
3092 raise error.Abort(_('unknown action: %s') % action)
3093
3093
3094 if batchedcommands is not None:
3094 if batchedcommands is not None:
3095 raise error.Abort(_('unclosed "batchbegin" request'))
3095 raise error.Abort(_('unclosed "batchbegin" request'))
3096
3096
3097 if peer:
3097 if peer:
3098 peer.close()
3098 peer.close()
3099
3099
3100 if proc:
3100 if proc:
3101 proc.kill()
3101 proc.kill()
@@ -1,817 +1,817
1 # httppeer.py - HTTP repository proxy classes for mercurial
1 # httppeer.py - HTTP repository proxy classes for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import errno
11 import errno
12 import io
12 import io
13 import os
13 import os
14 import socket
14 import socket
15 import struct
15 import struct
16 import tempfile
16 import tempfile
17
17
18 from .i18n import _
18 from .i18n import _
19 from .thirdparty import (
19 from .thirdparty import (
20 cbor,
20 cbor,
21 )
21 )
22 from .thirdparty.zope import (
22 from .thirdparty.zope import (
23 interface as zi,
23 interface as zi,
24 )
24 )
25 from . import (
25 from . import (
26 bundle2,
26 bundle2,
27 error,
27 error,
28 httpconnection,
28 httpconnection,
29 pycompat,
29 pycompat,
30 repository,
30 repository,
31 statichttprepo,
31 statichttprepo,
32 url as urlmod,
32 url as urlmod,
33 util,
33 util,
34 wireproto,
34 wireproto,
35 wireprotoframing,
35 wireprotoframing,
36 wireprototypes,
36 wireprototypes,
37 wireprotov1peer,
37 wireprotov1peer,
38 wireprotov2server,
38 wireprotov2server,
39 )
39 )
40
40
41 httplib = util.httplib
41 httplib = util.httplib
42 urlerr = util.urlerr
42 urlerr = util.urlerr
43 urlreq = util.urlreq
43 urlreq = util.urlreq
44
44
45 def encodevalueinheaders(value, header, limit):
45 def encodevalueinheaders(value, header, limit):
46 """Encode a string value into multiple HTTP headers.
46 """Encode a string value into multiple HTTP headers.
47
47
48 ``value`` will be encoded into 1 or more HTTP headers with the names
48 ``value`` will be encoded into 1 or more HTTP headers with the names
49 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
49 ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
50 name + value will be at most ``limit`` bytes long.
50 name + value will be at most ``limit`` bytes long.
51
51
52 Returns an iterable of 2-tuples consisting of header names and
52 Returns an iterable of 2-tuples consisting of header names and
53 values as native strings.
53 values as native strings.
54 """
54 """
55 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
55 # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
56 # not bytes. This function always takes bytes in as arguments.
56 # not bytes. This function always takes bytes in as arguments.
57 fmt = pycompat.strurl(header) + r'-%s'
57 fmt = pycompat.strurl(header) + r'-%s'
58 # Note: it is *NOT* a bug that the last bit here is a bytestring
58 # Note: it is *NOT* a bug that the last bit here is a bytestring
59 # and not a unicode: we're just getting the encoded length anyway,
59 # and not a unicode: we're just getting the encoded length anyway,
60 # and using an r-string to make it portable between Python 2 and 3
60 # and using an r-string to make it portable between Python 2 and 3
61 # doesn't work because then the \r is a literal backslash-r
61 # doesn't work because then the \r is a literal backslash-r
62 # instead of a carriage return.
62 # instead of a carriage return.
63 valuelen = limit - len(fmt % r'000') - len(': \r\n')
63 valuelen = limit - len(fmt % r'000') - len(': \r\n')
64 result = []
64 result = []
65
65
66 n = 0
66 n = 0
67 for i in xrange(0, len(value), valuelen):
67 for i in xrange(0, len(value), valuelen):
68 n += 1
68 n += 1
69 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
69 result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
70
70
71 return result
71 return result
72
72
73 def _wraphttpresponse(resp):
73 def _wraphttpresponse(resp):
74 """Wrap an HTTPResponse with common error handlers.
74 """Wrap an HTTPResponse with common error handlers.
75
75
76 This ensures that any I/O from any consumer raises the appropriate
76 This ensures that any I/O from any consumer raises the appropriate
77 error and messaging.
77 error and messaging.
78 """
78 """
79 origread = resp.read
79 origread = resp.read
80
80
81 class readerproxy(resp.__class__):
81 class readerproxy(resp.__class__):
82 def read(self, size=None):
82 def read(self, size=None):
83 try:
83 try:
84 return origread(size)
84 return origread(size)
85 except httplib.IncompleteRead as e:
85 except httplib.IncompleteRead as e:
86 # e.expected is an integer if length known or None otherwise.
86 # e.expected is an integer if length known or None otherwise.
87 if e.expected:
87 if e.expected:
88 msg = _('HTTP request error (incomplete response; '
88 msg = _('HTTP request error (incomplete response; '
89 'expected %d bytes got %d)') % (e.expected,
89 'expected %d bytes got %d)') % (e.expected,
90 len(e.partial))
90 len(e.partial))
91 else:
91 else:
92 msg = _('HTTP request error (incomplete response)')
92 msg = _('HTTP request error (incomplete response)')
93
93
94 raise error.PeerTransportError(
94 raise error.PeerTransportError(
95 msg,
95 msg,
96 hint=_('this may be an intermittent network failure; '
96 hint=_('this may be an intermittent network failure; '
97 'if the error persists, consider contacting the '
97 'if the error persists, consider contacting the '
98 'network or server operator'))
98 'network or server operator'))
99 except httplib.HTTPException as e:
99 except httplib.HTTPException as e:
100 raise error.PeerTransportError(
100 raise error.PeerTransportError(
101 _('HTTP request error (%s)') % e,
101 _('HTTP request error (%s)') % e,
102 hint=_('this may be an intermittent network failure; '
102 hint=_('this may be an intermittent network failure; '
103 'if the error persists, consider contacting the '
103 'if the error persists, consider contacting the '
104 'network or server operator'))
104 'network or server operator'))
105
105
106 resp.__class__ = readerproxy
106 resp.__class__ = readerproxy
107
107
108 class _multifile(object):
108 class _multifile(object):
109 def __init__(self, *fileobjs):
109 def __init__(self, *fileobjs):
110 for f in fileobjs:
110 for f in fileobjs:
111 if not util.safehasattr(f, 'length'):
111 if not util.safehasattr(f, 'length'):
112 raise ValueError(
112 raise ValueError(
113 '_multifile only supports file objects that '
113 '_multifile only supports file objects that '
114 'have a length but this one does not:', type(f), f)
114 'have a length but this one does not:', type(f), f)
115 self._fileobjs = fileobjs
115 self._fileobjs = fileobjs
116 self._index = 0
116 self._index = 0
117
117
118 @property
118 @property
119 def length(self):
119 def length(self):
120 return sum(f.length for f in self._fileobjs)
120 return sum(f.length for f in self._fileobjs)
121
121
122 def read(self, amt=None):
122 def read(self, amt=None):
123 if amt <= 0:
123 if amt <= 0:
124 return ''.join(f.read() for f in self._fileobjs)
124 return ''.join(f.read() for f in self._fileobjs)
125 parts = []
125 parts = []
126 while amt and self._index < len(self._fileobjs):
126 while amt and self._index < len(self._fileobjs):
127 parts.append(self._fileobjs[self._index].read(amt))
127 parts.append(self._fileobjs[self._index].read(amt))
128 got = len(parts[-1])
128 got = len(parts[-1])
129 if got < amt:
129 if got < amt:
130 self._index += 1
130 self._index += 1
131 amt -= got
131 amt -= got
132 return ''.join(parts)
132 return ''.join(parts)
133
133
134 def seek(self, offset, whence=os.SEEK_SET):
134 def seek(self, offset, whence=os.SEEK_SET):
135 if whence != os.SEEK_SET:
135 if whence != os.SEEK_SET:
136 raise NotImplementedError(
136 raise NotImplementedError(
137 '_multifile does not support anything other'
137 '_multifile does not support anything other'
138 ' than os.SEEK_SET for whence on seek()')
138 ' than os.SEEK_SET for whence on seek()')
139 if offset != 0:
139 if offset != 0:
140 raise NotImplementedError(
140 raise NotImplementedError(
141 '_multifile only supports seeking to start, but that '
141 '_multifile only supports seeking to start, but that '
142 'could be fixed if you need it')
142 'could be fixed if you need it')
143 for f in self._fileobjs:
143 for f in self._fileobjs:
144 f.seek(0)
144 f.seek(0)
145 self._index = 0
145 self._index = 0
146
146
147 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
147 def makev1commandrequest(ui, requestbuilder, caps, capablefn,
148 repobaseurl, cmd, args):
148 repobaseurl, cmd, args):
149 """Make an HTTP request to run a command for a version 1 client.
149 """Make an HTTP request to run a command for a version 1 client.
150
150
151 ``caps`` is a set of known server capabilities. The value may be
151 ``caps`` is a set of known server capabilities. The value may be
152 None if capabilities are not yet known.
152 None if capabilities are not yet known.
153
153
154 ``capablefn`` is a function to evaluate a capability.
154 ``capablefn`` is a function to evaluate a capability.
155
155
156 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
156 ``cmd``, ``args``, and ``data`` define the command, its arguments, and
157 raw data to pass to it.
157 raw data to pass to it.
158 """
158 """
159 if cmd == 'pushkey':
159 if cmd == 'pushkey':
160 args['data'] = ''
160 args['data'] = ''
161 data = args.pop('data', None)
161 data = args.pop('data', None)
162 headers = args.pop('headers', {})
162 headers = args.pop('headers', {})
163
163
164 ui.debug("sending %s command\n" % cmd)
164 ui.debug("sending %s command\n" % cmd)
165 q = [('cmd', cmd)]
165 q = [('cmd', cmd)]
166 headersize = 0
166 headersize = 0
167 # Important: don't use self.capable() here or else you end up
167 # Important: don't use self.capable() here or else you end up
168 # with infinite recursion when trying to look up capabilities
168 # with infinite recursion when trying to look up capabilities
169 # for the first time.
169 # for the first time.
170 postargsok = caps is not None and 'httppostargs' in caps
170 postargsok = caps is not None and 'httppostargs' in caps
171
171
172 # Send arguments via POST.
172 # Send arguments via POST.
173 if postargsok and args:
173 if postargsok and args:
174 strargs = urlreq.urlencode(sorted(args.items()))
174 strargs = urlreq.urlencode(sorted(args.items()))
175 if not data:
175 if not data:
176 data = strargs
176 data = strargs
177 else:
177 else:
178 if isinstance(data, bytes):
178 if isinstance(data, bytes):
179 i = io.BytesIO(data)
179 i = io.BytesIO(data)
180 i.length = len(data)
180 i.length = len(data)
181 data = i
181 data = i
182 argsio = io.BytesIO(strargs)
182 argsio = io.BytesIO(strargs)
183 argsio.length = len(strargs)
183 argsio.length = len(strargs)
184 data = _multifile(argsio, data)
184 data = _multifile(argsio, data)
185 headers[r'X-HgArgs-Post'] = len(strargs)
185 headers[r'X-HgArgs-Post'] = len(strargs)
186 elif args:
186 elif args:
187 # Calling self.capable() can infinite loop if we are calling
187 # Calling self.capable() can infinite loop if we are calling
188 # "capabilities". But that command should never accept wire
188 # "capabilities". But that command should never accept wire
189 # protocol arguments. So this should never happen.
189 # protocol arguments. So this should never happen.
190 assert cmd != 'capabilities'
190 assert cmd != 'capabilities'
191 httpheader = capablefn('httpheader')
191 httpheader = capablefn('httpheader')
192 if httpheader:
192 if httpheader:
193 headersize = int(httpheader.split(',', 1)[0])
193 headersize = int(httpheader.split(',', 1)[0])
194
194
195 # Send arguments via HTTP headers.
195 # Send arguments via HTTP headers.
196 if headersize > 0:
196 if headersize > 0:
197 # The headers can typically carry more data than the URL.
197 # The headers can typically carry more data than the URL.
198 encargs = urlreq.urlencode(sorted(args.items()))
198 encargs = urlreq.urlencode(sorted(args.items()))
199 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
199 for header, value in encodevalueinheaders(encargs, 'X-HgArg',
200 headersize):
200 headersize):
201 headers[header] = value
201 headers[header] = value
202 # Send arguments via query string (Mercurial <1.9).
202 # Send arguments via query string (Mercurial <1.9).
203 else:
203 else:
204 q += sorted(args.items())
204 q += sorted(args.items())
205
205
206 qs = '?%s' % urlreq.urlencode(q)
206 qs = '?%s' % urlreq.urlencode(q)
207 cu = "%s%s" % (repobaseurl, qs)
207 cu = "%s%s" % (repobaseurl, qs)
208 size = 0
208 size = 0
209 if util.safehasattr(data, 'length'):
209 if util.safehasattr(data, 'length'):
210 size = data.length
210 size = data.length
211 elif data is not None:
211 elif data is not None:
212 size = len(data)
212 size = len(data)
213 if data is not None and r'Content-Type' not in headers:
213 if data is not None and r'Content-Type' not in headers:
214 headers[r'Content-Type'] = r'application/mercurial-0.1'
214 headers[r'Content-Type'] = r'application/mercurial-0.1'
215
215
216 # Tell the server we accept application/mercurial-0.2 and multiple
216 # Tell the server we accept application/mercurial-0.2 and multiple
217 # compression formats if the server is capable of emitting those
217 # compression formats if the server is capable of emitting those
218 # payloads.
218 # payloads.
219 # Note: Keep this set empty by default, as client advertisement of
219 # Note: Keep this set empty by default, as client advertisement of
220 # protocol parameters should only occur after the handshake.
220 # protocol parameters should only occur after the handshake.
221 protoparams = set()
221 protoparams = set()
222
222
223 mediatypes = set()
223 mediatypes = set()
224 if caps is not None:
224 if caps is not None:
225 mt = capablefn('httpmediatype')
225 mt = capablefn('httpmediatype')
226 if mt:
226 if mt:
227 protoparams.add('0.1')
227 protoparams.add('0.1')
228 mediatypes = set(mt.split(','))
228 mediatypes = set(mt.split(','))
229
229
230 protoparams.add('partial-pull')
230 protoparams.add('partial-pull')
231
231
232 if '0.2tx' in mediatypes:
232 if '0.2tx' in mediatypes:
233 protoparams.add('0.2')
233 protoparams.add('0.2')
234
234
235 if '0.2tx' in mediatypes and capablefn('compression'):
235 if '0.2tx' in mediatypes and capablefn('compression'):
236 # We /could/ compare supported compression formats and prune
236 # We /could/ compare supported compression formats and prune
237 # non-mutually supported or error if nothing is mutually supported.
237 # non-mutually supported or error if nothing is mutually supported.
238 # For now, send the full list to the server and have it error.
238 # For now, send the full list to the server and have it error.
239 comps = [e.wireprotosupport().name for e in
239 comps = [e.wireprotosupport().name for e in
240 util.compengines.supportedwireengines(util.CLIENTROLE)]
240 util.compengines.supportedwireengines(util.CLIENTROLE)]
241 protoparams.add('comp=%s' % ','.join(comps))
241 protoparams.add('comp=%s' % ','.join(comps))
242
242
243 if protoparams:
243 if protoparams:
244 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
244 protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),
245 'X-HgProto',
245 'X-HgProto',
246 headersize or 1024)
246 headersize or 1024)
247 for header, value in protoheaders:
247 for header, value in protoheaders:
248 headers[header] = value
248 headers[header] = value
249
249
250 varyheaders = []
250 varyheaders = []
251 for header in headers:
251 for header in headers:
252 if header.lower().startswith(r'x-hg'):
252 if header.lower().startswith(r'x-hg'):
253 varyheaders.append(header)
253 varyheaders.append(header)
254
254
255 if varyheaders:
255 if varyheaders:
256 headers[r'Vary'] = r','.join(sorted(varyheaders))
256 headers[r'Vary'] = r','.join(sorted(varyheaders))
257
257
258 req = requestbuilder(pycompat.strurl(cu), data, headers)
258 req = requestbuilder(pycompat.strurl(cu), data, headers)
259
259
260 if data is not None:
260 if data is not None:
261 ui.debug("sending %d bytes\n" % size)
261 ui.debug("sending %d bytes\n" % size)
262 req.add_unredirected_header(r'Content-Length', r'%d' % size)
262 req.add_unredirected_header(r'Content-Length', r'%d' % size)
263
263
264 return req, cu, qs
264 return req, cu, qs
265
265
266 def sendrequest(ui, opener, req):
266 def sendrequest(ui, opener, req):
267 """Send a prepared HTTP request.
267 """Send a prepared HTTP request.
268
268
269 Returns the response object.
269 Returns the response object.
270 """
270 """
271 if (ui.debugflag
271 if (ui.debugflag
272 and ui.configbool('devel', 'debug.peer-request')):
272 and ui.configbool('devel', 'debug.peer-request')):
273 dbg = ui.debug
273 dbg = ui.debug
274 line = 'devel-peer-request: %s\n'
274 line = 'devel-peer-request: %s\n'
275 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
275 dbg(line % '%s %s' % (req.get_method(), req.get_full_url()))
276 hgargssize = None
276 hgargssize = None
277
277
278 for header, value in sorted(req.header_items()):
278 for header, value in sorted(req.header_items()):
279 if header.startswith('X-hgarg-'):
279 if header.startswith('X-hgarg-'):
280 if hgargssize is None:
280 if hgargssize is None:
281 hgargssize = 0
281 hgargssize = 0
282 hgargssize += len(value)
282 hgargssize += len(value)
283 else:
283 else:
284 dbg(line % ' %s %s' % (header, value))
284 dbg(line % ' %s %s' % (header, value))
285
285
286 if hgargssize is not None:
286 if hgargssize is not None:
287 dbg(line % ' %d bytes of commands arguments in headers'
287 dbg(line % ' %d bytes of commands arguments in headers'
288 % hgargssize)
288 % hgargssize)
289
289
290 if req.has_data():
290 if req.has_data():
291 data = req.get_data()
291 data = req.get_data()
292 length = getattr(data, 'length', None)
292 length = getattr(data, 'length', None)
293 if length is None:
293 if length is None:
294 length = len(data)
294 length = len(data)
295 dbg(line % ' %d bytes of data' % length)
295 dbg(line % ' %d bytes of data' % length)
296
296
297 start = util.timer()
297 start = util.timer()
298
298
299 try:
299 try:
300 res = opener.open(req)
300 res = opener.open(req)
301 except urlerr.httperror as inst:
301 except urlerr.httperror as inst:
302 if inst.code == 401:
302 if inst.code == 401:
303 raise error.Abort(_('authorization failed'))
303 raise error.Abort(_('authorization failed'))
304 raise
304 raise
305 except httplib.HTTPException as inst:
305 except httplib.HTTPException as inst:
306 ui.debug('http error requesting %s\n' %
306 ui.debug('http error requesting %s\n' %
307 util.hidepassword(req.get_full_url()))
307 util.hidepassword(req.get_full_url()))
308 ui.traceback()
308 ui.traceback()
309 raise IOError(None, inst)
309 raise IOError(None, inst)
310 finally:
310 finally:
311 if ui.configbool('devel', 'debug.peer-request'):
311 if ui.configbool('devel', 'debug.peer-request'):
312 dbg(line % ' finished in %.4f seconds (%s)'
312 dbg(line % ' finished in %.4f seconds (%s)'
313 % (util.timer() - start, res.code))
313 % (util.timer() - start, res.code))
314
314
315 # Insert error handlers for common I/O failures.
315 # Insert error handlers for common I/O failures.
316 _wraphttpresponse(res)
316 _wraphttpresponse(res)
317
317
318 return res
318 return res
319
319
320 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
320 def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,
321 allowcbor=False):
321 allowcbor=False):
322 # record the url we got redirected to
322 # record the url we got redirected to
323 respurl = pycompat.bytesurl(resp.geturl())
323 respurl = pycompat.bytesurl(resp.geturl())
324 if respurl.endswith(qs):
324 if respurl.endswith(qs):
325 respurl = respurl[:-len(qs)]
325 respurl = respurl[:-len(qs)]
326 if baseurl.rstrip('/') != respurl.rstrip('/'):
326 if baseurl.rstrip('/') != respurl.rstrip('/'):
327 if not ui.quiet:
327 if not ui.quiet:
328 ui.warn(_('real URL is %s\n') % respurl)
328 ui.warn(_('real URL is %s\n') % respurl)
329
329
330 try:
330 try:
331 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
331 proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
332 except AttributeError:
332 except AttributeError:
333 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
333 proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
334
334
335 safeurl = util.hidepassword(baseurl)
335 safeurl = util.hidepassword(baseurl)
336 if proto.startswith('application/hg-error'):
336 if proto.startswith('application/hg-error'):
337 raise error.OutOfBandError(resp.read())
337 raise error.OutOfBandError(resp.read())
338
338
339 # Pre 1.0 versions of Mercurial used text/plain and
339 # Pre 1.0 versions of Mercurial used text/plain and
340 # application/hg-changegroup. We don't support such old servers.
340 # application/hg-changegroup. We don't support such old servers.
341 if not proto.startswith('application/mercurial-'):
341 if not proto.startswith('application/mercurial-'):
342 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
342 ui.debug("requested URL: '%s'\n" % util.hidepassword(requrl))
343 raise error.RepoError(
343 raise error.RepoError(
344 _("'%s' does not appear to be an hg repository:\n"
344 _("'%s' does not appear to be an hg repository:\n"
345 "---%%<--- (%s)\n%s\n---%%<---\n")
345 "---%%<--- (%s)\n%s\n---%%<---\n")
346 % (safeurl, proto or 'no content-type', resp.read(1024)))
346 % (safeurl, proto or 'no content-type', resp.read(1024)))
347
347
348 try:
348 try:
349 subtype = proto.split('-', 1)[1]
349 subtype = proto.split('-', 1)[1]
350
350
351 # Unless we end up supporting CBOR in the legacy wire protocol,
351 # Unless we end up supporting CBOR in the legacy wire protocol,
352 # this should ONLY be encountered for the initial capabilities
352 # this should ONLY be encountered for the initial capabilities
353 # request during handshake.
353 # request during handshake.
354 if subtype == 'cbor':
354 if subtype == 'cbor':
355 if allowcbor:
355 if allowcbor:
356 return respurl, proto, resp
356 return respurl, proto, resp
357 else:
357 else:
358 raise error.RepoError(_('unexpected CBOR response from '
358 raise error.RepoError(_('unexpected CBOR response from '
359 'server'))
359 'server'))
360
360
361 version_info = tuple([int(n) for n in subtype.split('.')])
361 version_info = tuple([int(n) for n in subtype.split('.')])
362 except ValueError:
362 except ValueError:
363 raise error.RepoError(_("'%s' sent a broken Content-Type "
363 raise error.RepoError(_("'%s' sent a broken Content-Type "
364 "header (%s)") % (safeurl, proto))
364 "header (%s)") % (safeurl, proto))
365
365
366 # TODO consider switching to a decompression reader that uses
366 # TODO consider switching to a decompression reader that uses
367 # generators.
367 # generators.
368 if version_info == (0, 1):
368 if version_info == (0, 1):
369 if compressible:
369 if compressible:
370 resp = util.compengines['zlib'].decompressorreader(resp)
370 resp = util.compengines['zlib'].decompressorreader(resp)
371
371
372 elif version_info == (0, 2):
372 elif version_info == (0, 2):
373 # application/mercurial-0.2 always identifies the compression
373 # application/mercurial-0.2 always identifies the compression
374 # engine in the payload header.
374 # engine in the payload header.
375 elen = struct.unpack('B', resp.read(1))[0]
375 elen = struct.unpack('B', resp.read(1))[0]
376 ename = resp.read(elen)
376 ename = resp.read(elen)
377 engine = util.compengines.forwiretype(ename)
377 engine = util.compengines.forwiretype(ename)
378
378
379 resp = engine.decompressorreader(resp)
379 resp = engine.decompressorreader(resp)
380 else:
380 else:
381 raise error.RepoError(_("'%s' uses newer protocol %s") %
381 raise error.RepoError(_("'%s' uses newer protocol %s") %
382 (safeurl, subtype))
382 (safeurl, subtype))
383
383
384 return respurl, proto, resp
384 return respurl, proto, resp
385
385
386 class httppeer(wireprotov1peer.wirepeer):
386 class httppeer(wireprotov1peer.wirepeer):
387 def __init__(self, ui, path, url, opener, requestbuilder, caps):
387 def __init__(self, ui, path, url, opener, requestbuilder, caps):
388 self.ui = ui
388 self.ui = ui
389 self._path = path
389 self._path = path
390 self._url = url
390 self._url = url
391 self._caps = caps
391 self._caps = caps
392 self._urlopener = opener
392 self._urlopener = opener
393 self._requestbuilder = requestbuilder
393 self._requestbuilder = requestbuilder
394
394
395 def __del__(self):
395 def __del__(self):
396 for h in self._urlopener.handlers:
396 for h in self._urlopener.handlers:
397 h.close()
397 h.close()
398 getattr(h, "close_all", lambda: None)()
398 getattr(h, "close_all", lambda: None)()
399
399
400 # Begin of ipeerconnection interface.
400 # Begin of ipeerconnection interface.
401
401
402 def url(self):
402 def url(self):
403 return self._path
403 return self._path
404
404
405 def local(self):
405 def local(self):
406 return None
406 return None
407
407
408 def peer(self):
408 def peer(self):
409 return self
409 return self
410
410
411 def canpush(self):
411 def canpush(self):
412 return True
412 return True
413
413
414 def close(self):
414 def close(self):
415 pass
415 pass
416
416
417 # End of ipeerconnection interface.
417 # End of ipeerconnection interface.
418
418
419 # Begin of ipeercommands interface.
419 # Begin of ipeercommands interface.
420
420
421 def capabilities(self):
421 def capabilities(self):
422 return self._caps
422 return self._caps
423
423
424 # End of ipeercommands interface.
424 # End of ipeercommands interface.
425
425
426 # look up capabilities only when needed
426 # look up capabilities only when needed
427
427
428 def _callstream(self, cmd, _compressible=False, **args):
428 def _callstream(self, cmd, _compressible=False, **args):
429 args = pycompat.byteskwargs(args)
429 args = pycompat.byteskwargs(args)
430
430
431 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
431 req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,
432 self._caps, self.capable,
432 self._caps, self.capable,
433 self._url, cmd, args)
433 self._url, cmd, args)
434
434
435 resp = sendrequest(self.ui, self._urlopener, req)
435 resp = sendrequest(self.ui, self._urlopener, req)
436
436
437 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
437 self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,
438 resp, _compressible)
438 resp, _compressible)
439
439
440 return resp
440 return resp
441
441
442 def _call(self, cmd, **args):
442 def _call(self, cmd, **args):
443 fp = self._callstream(cmd, **args)
443 fp = self._callstream(cmd, **args)
444 try:
444 try:
445 return fp.read()
445 return fp.read()
446 finally:
446 finally:
447 # if using keepalive, allow connection to be reused
447 # if using keepalive, allow connection to be reused
448 fp.close()
448 fp.close()
449
449
450 def _callpush(self, cmd, cg, **args):
450 def _callpush(self, cmd, cg, **args):
451 # have to stream bundle to a temp file because we do not have
451 # have to stream bundle to a temp file because we do not have
452 # http 1.1 chunked transfer.
452 # http 1.1 chunked transfer.
453
453
454 types = self.capable('unbundle')
454 types = self.capable('unbundle')
455 try:
455 try:
456 types = types.split(',')
456 types = types.split(',')
457 except AttributeError:
457 except AttributeError:
458 # servers older than d1b16a746db6 will send 'unbundle' as a
458 # servers older than d1b16a746db6 will send 'unbundle' as a
459 # boolean capability. They only support headerless/uncompressed
459 # boolean capability. They only support headerless/uncompressed
460 # bundles.
460 # bundles.
461 types = [""]
461 types = [""]
462 for x in types:
462 for x in types:
463 if x in bundle2.bundletypes:
463 if x in bundle2.bundletypes:
464 type = x
464 type = x
465 break
465 break
466
466
467 tempname = bundle2.writebundle(self.ui, cg, None, type)
467 tempname = bundle2.writebundle(self.ui, cg, None, type)
468 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
468 fp = httpconnection.httpsendfile(self.ui, tempname, "rb")
469 headers = {r'Content-Type': r'application/mercurial-0.1'}
469 headers = {r'Content-Type': r'application/mercurial-0.1'}
470
470
471 try:
471 try:
472 r = self._call(cmd, data=fp, headers=headers, **args)
472 r = self._call(cmd, data=fp, headers=headers, **args)
473 vals = r.split('\n', 1)
473 vals = r.split('\n', 1)
474 if len(vals) < 2:
474 if len(vals) < 2:
475 raise error.ResponseError(_("unexpected response:"), r)
475 raise error.ResponseError(_("unexpected response:"), r)
476 return vals
476 return vals
477 except urlerr.httperror:
477 except urlerr.httperror:
478 # Catch and re-raise these so we don't try and treat them
478 # Catch and re-raise these so we don't try and treat them
479 # like generic socket errors. They lack any values in
479 # like generic socket errors. They lack any values in
480 # .args on Python 3 which breaks our socket.error block.
480 # .args on Python 3 which breaks our socket.error block.
481 raise
481 raise
482 except socket.error as err:
482 except socket.error as err:
483 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
483 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
484 raise error.Abort(_('push failed: %s') % err.args[1])
484 raise error.Abort(_('push failed: %s') % err.args[1])
485 raise error.Abort(err.args[1])
485 raise error.Abort(err.args[1])
486 finally:
486 finally:
487 fp.close()
487 fp.close()
488 os.unlink(tempname)
488 os.unlink(tempname)
489
489
490 def _calltwowaystream(self, cmd, fp, **args):
490 def _calltwowaystream(self, cmd, fp, **args):
491 fh = None
491 fh = None
492 fp_ = None
492 fp_ = None
493 filename = None
493 filename = None
494 try:
494 try:
495 # dump bundle to disk
495 # dump bundle to disk
496 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
496 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
497 fh = os.fdopen(fd, r"wb")
497 fh = os.fdopen(fd, r"wb")
498 d = fp.read(4096)
498 d = fp.read(4096)
499 while d:
499 while d:
500 fh.write(d)
500 fh.write(d)
501 d = fp.read(4096)
501 d = fp.read(4096)
502 fh.close()
502 fh.close()
503 # start http push
503 # start http push
504 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
504 fp_ = httpconnection.httpsendfile(self.ui, filename, "rb")
505 headers = {r'Content-Type': r'application/mercurial-0.1'}
505 headers = {r'Content-Type': r'application/mercurial-0.1'}
506 return self._callstream(cmd, data=fp_, headers=headers, **args)
506 return self._callstream(cmd, data=fp_, headers=headers, **args)
507 finally:
507 finally:
508 if fp_ is not None:
508 if fp_ is not None:
509 fp_.close()
509 fp_.close()
510 if fh is not None:
510 if fh is not None:
511 fh.close()
511 fh.close()
512 os.unlink(filename)
512 os.unlink(filename)
513
513
514 def _callcompressable(self, cmd, **args):
514 def _callcompressable(self, cmd, **args):
515 return self._callstream(cmd, _compressible=True, **args)
515 return self._callstream(cmd, _compressible=True, **args)
516
516
517 def _abort(self, exception):
517 def _abort(self, exception):
518 raise exception
518 raise exception
519
519
520 # TODO implement interface for version 2 peers
520 # TODO implement interface for version 2 peers
521 @zi.implementer(repository.ipeerconnection, repository.ipeercapabilities)
521 @zi.implementer(repository.ipeerconnection, repository.ipeercapabilities)
522 class httpv2peer(object):
522 class httpv2peer(object):
523 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
523 def __init__(self, ui, repourl, apipath, opener, requestbuilder,
524 apidescriptor):
524 apidescriptor):
525 self.ui = ui
525 self.ui = ui
526
526
527 if repourl.endswith('/'):
527 if repourl.endswith('/'):
528 repourl = repourl[:-1]
528 repourl = repourl[:-1]
529
529
530 self._url = repourl
530 self._url = repourl
531 self._apipath = apipath
531 self._apipath = apipath
532 self._opener = opener
532 self._opener = opener
533 self._requestbuilder = requestbuilder
533 self._requestbuilder = requestbuilder
534 self._descriptor = apidescriptor
534 self._descriptor = apidescriptor
535
535
536 # Start of ipeerconnection.
536 # Start of ipeerconnection.
537
537
538 def url(self):
538 def url(self):
539 return self._url
539 return self._url
540
540
541 def local(self):
541 def local(self):
542 return None
542 return None
543
543
544 def peer(self):
544 def peer(self):
545 return self
545 return self
546
546
547 def canpush(self):
547 def canpush(self):
548 # TODO change once implemented.
548 # TODO change once implemented.
549 return False
549 return False
550
550
551 def close(self):
551 def close(self):
552 pass
552 pass
553
553
554 # End of ipeerconnection.
554 # End of ipeerconnection.
555
555
556 # Start of ipeercapabilities.
556 # Start of ipeercapabilities.
557
557
558 def capable(self, name):
558 def capable(self, name):
559 # The capabilities used internally historically map to capabilities
559 # The capabilities used internally historically map to capabilities
560 # advertised from the "capabilities" wire protocol command. However,
560 # advertised from the "capabilities" wire protocol command. However,
561 # version 2 of that command works differently.
561 # version 2 of that command works differently.
562
562
563 # Maps to commands that are available.
563 # Maps to commands that are available.
564 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
564 if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):
565 return True
565 return True
566
566
567 # Other concepts.
567 # Other concepts.
568 if name in ('bundle2',):
568 if name in ('bundle2',):
569 return True
569 return True
570
570
571 return False
571 return False
572
572
573 def requirecap(self, name, purpose):
573 def requirecap(self, name, purpose):
574 if self.capable(name):
574 if self.capable(name):
575 return
575 return
576
576
577 raise error.CapabilityError(
577 raise error.CapabilityError(
578 _('cannot %s; client or remote repository does not support the %r '
578 _('cannot %s; client or remote repository does not support the %r '
579 'capability') % (purpose, name))
579 'capability') % (purpose, name))
580
580
581 # End of ipeercapabilities.
581 # End of ipeercapabilities.
582
582
583 # TODO require to be part of a batched primitive, use futures.
583 # TODO require to be part of a batched primitive, use futures.
584 def _call(self, name, **args):
584 def _call(self, name, **args):
585 """Call a wire protocol command with arguments."""
585 """Call a wire protocol command with arguments."""
586
586
587 # Having this early has a side-effect of importing wireprotov2server,
587 # Having this early has a side-effect of importing wireprotov2server,
588 # which has the side-effect of ensuring commands are registered.
588 # which has the side-effect of ensuring commands are registered.
589
589
590 # TODO modify user-agent to reflect v2.
590 # TODO modify user-agent to reflect v2.
591 headers = {
591 headers = {
592 r'Accept': wireprotov2server.FRAMINGTYPE,
592 r'Accept': wireprotov2server.FRAMINGTYPE,
593 r'Content-Type': wireprotov2server.FRAMINGTYPE,
593 r'Content-Type': wireprotov2server.FRAMINGTYPE,
594 }
594 }
595
595
596 # TODO permissions should come from capabilities results.
596 # TODO permissions should come from capabilities results.
597 permission = wireproto.commandsv2[name].permission
597 permission = wireproto.commandsv2[name].permission
598 if permission not in ('push', 'pull'):
598 if permission not in ('push', 'pull'):
599 raise error.ProgrammingError('unknown permission type: %s' %
599 raise error.ProgrammingError('unknown permission type: %s' %
600 permission)
600 permission)
601
601
602 permission = {
602 permission = {
603 'push': 'rw',
603 'push': 'rw',
604 'pull': 'ro',
604 'pull': 'ro',
605 }[permission]
605 }[permission]
606
606
607 url = '%s/%s/%s/%s' % (self._url, self._apipath, permission, name)
607 url = '%s/%s/%s/%s' % (self._url, self._apipath, permission, name)
608
608
609 # TODO this should be part of a generic peer for the frame-based
609 # TODO this should be part of a generic peer for the frame-based
610 # protocol.
610 # protocol.
611 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
611 reactor = wireprotoframing.clientreactor(hasmultiplesend=False,
612 buffersends=True)
612 buffersends=True)
613
613
614 request, action, meta = reactor.callcommand(name, args)
614 request, action, meta = reactor.callcommand(name, args)
615 assert action == 'noop'
615 assert action == 'noop'
616
616
617 action, meta = reactor.flushcommands()
617 action, meta = reactor.flushcommands()
618 assert action == 'sendframes'
618 assert action == 'sendframes'
619
619
620 body = b''.join(map(bytes, meta['framegen']))
620 body = b''.join(map(bytes, meta['framegen']))
621 req = self._requestbuilder(pycompat.strurl(url), body, headers)
621 req = self._requestbuilder(pycompat.strurl(url), body, headers)
622 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
622 req.add_unredirected_header(r'Content-Length', r'%d' % len(body))
623
623
624 # TODO unify this code with httppeer.
624 # TODO unify this code with httppeer.
625 try:
625 try:
626 res = self._opener.open(req)
626 res = self._opener.open(req)
627 except urlerr.httperror as e:
627 except urlerr.httperror as e:
628 if e.code == 401:
628 if e.code == 401:
629 raise error.Abort(_('authorization failed'))
629 raise error.Abort(_('authorization failed'))
630
630
631 raise
631 raise
632 except httplib.HTTPException as e:
632 except httplib.HTTPException as e:
633 self.ui.traceback()
633 self.ui.traceback()
634 raise IOError(None, e)
634 raise IOError(None, e)
635
635
636 # TODO validate response type, wrap response to handle I/O errors.
636 # TODO validate response type, wrap response to handle I/O errors.
637 # TODO more robust frame receiver.
637 # TODO more robust frame receiver.
638 results = []
638 results = []
639
639
640 while True:
640 while True:
641 frame = wireprotoframing.readframe(res)
641 frame = wireprotoframing.readframe(res)
642 if frame is None:
642 if frame is None:
643 break
643 break
644
644
645 self.ui.note(_('received %r\n') % frame)
645 self.ui.note(_('received %r\n') % frame)
646
646
647 action, meta = reactor.onframerecv(frame)
647 action, meta = reactor.onframerecv(frame)
648
648
649 if action == 'responsedata':
649 if action == 'responsedata':
650 if meta['cbor']:
650 if meta['cbor']:
651 payload = util.bytesio(meta['data'])
651 payload = util.bytesio(meta['data'])
652
652
653 decoder = cbor.CBORDecoder(payload)
653 decoder = cbor.CBORDecoder(payload)
654 while payload.tell() + 1 < len(meta['data']):
654 while payload.tell() + 1 < len(meta['data']):
655 results.append(decoder.decode())
655 results.append(decoder.decode())
656 else:
656 else:
657 results.append(meta['data'])
657 results.append(meta['data'])
658 else:
658 else:
659 error.ProgrammingError('unhandled action: %s' % action)
659 error.ProgrammingError('unhandled action: %s' % action)
660
660
661 return results
661 return results
662
662
663 # Registry of API service names to metadata about peers that handle it.
663 # Registry of API service names to metadata about peers that handle it.
664 #
664 #
665 # The following keys are meaningful:
665 # The following keys are meaningful:
666 #
666 #
667 # init
667 # init
668 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
668 # Callable receiving (ui, repourl, servicepath, opener, requestbuilder,
669 # apidescriptor) to create a peer.
669 # apidescriptor) to create a peer.
670 #
670 #
671 # priority
671 # priority
672 # Integer priority for the service. If we could choose from multiple
672 # Integer priority for the service. If we could choose from multiple
673 # services, we choose the one with the highest priority.
673 # services, we choose the one with the highest priority.
674 API_PEERS = {
674 API_PEERS = {
675 wireprototypes.HTTPV2: {
675 wireprototypes.HTTP_WIREPROTO_V2: {
676 'init': httpv2peer,
676 'init': httpv2peer,
677 'priority': 50,
677 'priority': 50,
678 },
678 },
679 }
679 }
680
680
681 def performhandshake(ui, url, opener, requestbuilder):
681 def performhandshake(ui, url, opener, requestbuilder):
682 # The handshake is a request to the capabilities command.
682 # The handshake is a request to the capabilities command.
683
683
684 caps = None
684 caps = None
685 def capable(x):
685 def capable(x):
686 raise error.ProgrammingError('should not be called')
686 raise error.ProgrammingError('should not be called')
687
687
688 args = {}
688 args = {}
689
689
690 # The client advertises support for newer protocols by adding an
690 # The client advertises support for newer protocols by adding an
691 # X-HgUpgrade-* header with a list of supported APIs and an
691 # X-HgUpgrade-* header with a list of supported APIs and an
692 # X-HgProto-* header advertising which serializing formats it supports.
692 # X-HgProto-* header advertising which serializing formats it supports.
693 # We only support the HTTP version 2 transport and CBOR responses for
693 # We only support the HTTP version 2 transport and CBOR responses for
694 # now.
694 # now.
695 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
695 advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')
696
696
697 if advertisev2:
697 if advertisev2:
698 args['headers'] = {
698 args['headers'] = {
699 r'X-HgProto-1': r'cbor',
699 r'X-HgProto-1': r'cbor',
700 }
700 }
701
701
702 args['headers'].update(
702 args['headers'].update(
703 encodevalueinheaders(' '.join(sorted(API_PEERS)),
703 encodevalueinheaders(' '.join(sorted(API_PEERS)),
704 'X-HgUpgrade',
704 'X-HgUpgrade',
705 # We don't know the header limit this early.
705 # We don't know the header limit this early.
706 # So make it small.
706 # So make it small.
707 1024))
707 1024))
708
708
709 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
709 req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,
710 capable, url, 'capabilities',
710 capable, url, 'capabilities',
711 args)
711 args)
712
712
713 resp = sendrequest(ui, opener, req)
713 resp = sendrequest(ui, opener, req)
714
714
715 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
715 respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,
716 compressible=False,
716 compressible=False,
717 allowcbor=advertisev2)
717 allowcbor=advertisev2)
718
718
719 try:
719 try:
720 rawdata = resp.read()
720 rawdata = resp.read()
721 finally:
721 finally:
722 resp.close()
722 resp.close()
723
723
724 if not ct.startswith('application/mercurial-'):
724 if not ct.startswith('application/mercurial-'):
725 raise error.ProgrammingError('unexpected content-type: %s' % ct)
725 raise error.ProgrammingError('unexpected content-type: %s' % ct)
726
726
727 if advertisev2:
727 if advertisev2:
728 if ct == 'application/mercurial-cbor':
728 if ct == 'application/mercurial-cbor':
729 try:
729 try:
730 info = cbor.loads(rawdata)
730 info = cbor.loads(rawdata)
731 except cbor.CBORDecodeError:
731 except cbor.CBORDecodeError:
732 raise error.Abort(_('error decoding CBOR from remote server'),
732 raise error.Abort(_('error decoding CBOR from remote server'),
733 hint=_('try again and consider contacting '
733 hint=_('try again and consider contacting '
734 'the server operator'))
734 'the server operator'))
735
735
736 # We got a legacy response. That's fine.
736 # We got a legacy response. That's fine.
737 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
737 elif ct in ('application/mercurial-0.1', 'application/mercurial-0.2'):
738 info = {
738 info = {
739 'v1capabilities': set(rawdata.split())
739 'v1capabilities': set(rawdata.split())
740 }
740 }
741
741
742 else:
742 else:
743 raise error.RepoError(
743 raise error.RepoError(
744 _('unexpected response type from server: %s') % ct)
744 _('unexpected response type from server: %s') % ct)
745 else:
745 else:
746 info = {
746 info = {
747 'v1capabilities': set(rawdata.split())
747 'v1capabilities': set(rawdata.split())
748 }
748 }
749
749
750 return respurl, info
750 return respurl, info
751
751
752 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
752 def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):
753 """Construct an appropriate HTTP peer instance.
753 """Construct an appropriate HTTP peer instance.
754
754
755 ``opener`` is an ``url.opener`` that should be used to establish
755 ``opener`` is an ``url.opener`` that should be used to establish
756 connections, perform HTTP requests.
756 connections, perform HTTP requests.
757
757
758 ``requestbuilder`` is the type used for constructing HTTP requests.
758 ``requestbuilder`` is the type used for constructing HTTP requests.
759 It exists as an argument so extensions can override the default.
759 It exists as an argument so extensions can override the default.
760 """
760 """
761 u = util.url(path)
761 u = util.url(path)
762 if u.query or u.fragment:
762 if u.query or u.fragment:
763 raise error.Abort(_('unsupported URL component: "%s"') %
763 raise error.Abort(_('unsupported URL component: "%s"') %
764 (u.query or u.fragment))
764 (u.query or u.fragment))
765
765
766 # urllib cannot handle URLs with embedded user or passwd.
766 # urllib cannot handle URLs with embedded user or passwd.
767 url, authinfo = u.authinfo()
767 url, authinfo = u.authinfo()
768 ui.debug('using %s\n' % url)
768 ui.debug('using %s\n' % url)
769
769
770 opener = opener or urlmod.opener(ui, authinfo)
770 opener = opener or urlmod.opener(ui, authinfo)
771
771
772 respurl, info = performhandshake(ui, url, opener, requestbuilder)
772 respurl, info = performhandshake(ui, url, opener, requestbuilder)
773
773
774 # Given the intersection of APIs that both we and the server support,
774 # Given the intersection of APIs that both we and the server support,
775 # sort by their advertised priority and pick the first one.
775 # sort by their advertised priority and pick the first one.
776 #
776 #
777 # TODO consider making this request-based and interface driven. For
777 # TODO consider making this request-based and interface driven. For
778 # example, the caller could say "I want a peer that does X." It's quite
778 # example, the caller could say "I want a peer that does X." It's quite
779 # possible that not all peers would do that. Since we know the service
779 # possible that not all peers would do that. Since we know the service
780 # capabilities, we could filter out services not meeting the
780 # capabilities, we could filter out services not meeting the
781 # requirements. Possibly by consulting the interfaces defined by the
781 # requirements. Possibly by consulting the interfaces defined by the
782 # peer type.
782 # peer type.
783 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
783 apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())
784
784
785 preferredchoices = sorted(apipeerchoices,
785 preferredchoices = sorted(apipeerchoices,
786 key=lambda x: API_PEERS[x]['priority'],
786 key=lambda x: API_PEERS[x]['priority'],
787 reverse=True)
787 reverse=True)
788
788
789 for service in preferredchoices:
789 for service in preferredchoices:
790 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
790 apipath = '%s/%s' % (info['apibase'].rstrip('/'), service)
791
791
792 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
792 return API_PEERS[service]['init'](ui, respurl, apipath, opener,
793 requestbuilder,
793 requestbuilder,
794 info['apis'][service])
794 info['apis'][service])
795
795
796 # Failed to construct an API peer. Fall back to legacy.
796 # Failed to construct an API peer. Fall back to legacy.
797 return httppeer(ui, path, respurl, opener, requestbuilder,
797 return httppeer(ui, path, respurl, opener, requestbuilder,
798 info['v1capabilities'])
798 info['v1capabilities'])
799
799
800 def instance(ui, path, create):
800 def instance(ui, path, create):
801 if create:
801 if create:
802 raise error.Abort(_('cannot create new http repository'))
802 raise error.Abort(_('cannot create new http repository'))
803 try:
803 try:
804 if path.startswith('https:') and not urlmod.has_https:
804 if path.startswith('https:') and not urlmod.has_https:
805 raise error.Abort(_('Python support for SSL and HTTPS '
805 raise error.Abort(_('Python support for SSL and HTTPS '
806 'is not installed'))
806 'is not installed'))
807
807
808 inst = makepeer(ui, path)
808 inst = makepeer(ui, path)
809
809
810 return inst
810 return inst
811 except error.RepoError as httpexception:
811 except error.RepoError as httpexception:
812 try:
812 try:
813 r = statichttprepo.instance(ui, "static-" + path, create)
813 r = statichttprepo.instance(ui, "static-" + path, create)
814 ui.note(_('(falling back to static-http)\n'))
814 ui.note(_('(falling back to static-http)\n'))
815 return r
815 return r
816 except error.RepoError:
816 except error.RepoError:
817 raise httpexception # use the original http RepoError instead
817 raise httpexception # use the original http RepoError instead
@@ -1,811 +1,811
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10 import struct
10 import struct
11 import sys
11 import sys
12 import threading
12 import threading
13
13
14 from .i18n import _
14 from .i18n import _
15 from .thirdparty import (
15 from .thirdparty import (
16 cbor,
16 cbor,
17 )
17 )
18 from .thirdparty.zope import (
18 from .thirdparty.zope import (
19 interface as zi,
19 interface as zi,
20 )
20 )
21 from . import (
21 from . import (
22 encoding,
22 encoding,
23 error,
23 error,
24 hook,
24 hook,
25 pycompat,
25 pycompat,
26 util,
26 util,
27 wireproto,
27 wireproto,
28 wireprototypes,
28 wireprototypes,
29 wireprotov2server,
29 wireprotov2server,
30 )
30 )
31 from .utils import (
31 from .utils import (
32 procutil,
32 procutil,
33 )
33 )
34
34
35 stringio = util.stringio
35 stringio = util.stringio
36
36
37 urlerr = util.urlerr
37 urlerr = util.urlerr
38 urlreq = util.urlreq
38 urlreq = util.urlreq
39
39
40 HTTP_OK = 200
40 HTTP_OK = 200
41
41
42 HGTYPE = 'application/mercurial-0.1'
42 HGTYPE = 'application/mercurial-0.1'
43 HGTYPE2 = 'application/mercurial-0.2'
43 HGTYPE2 = 'application/mercurial-0.2'
44 HGERRTYPE = 'application/hg-error'
44 HGERRTYPE = 'application/hg-error'
45
45
46 SSHV1 = wireprototypes.SSHV1
46 SSHV1 = wireprototypes.SSHV1
47 SSHV2 = wireprototypes.SSHV2
47 SSHV2 = wireprototypes.SSHV2
48
48
49 def decodevaluefromheaders(req, headerprefix):
49 def decodevaluefromheaders(req, headerprefix):
50 """Decode a long value from multiple HTTP request headers.
50 """Decode a long value from multiple HTTP request headers.
51
51
52 Returns the value as a bytes, not a str.
52 Returns the value as a bytes, not a str.
53 """
53 """
54 chunks = []
54 chunks = []
55 i = 1
55 i = 1
56 while True:
56 while True:
57 v = req.headers.get(b'%s-%d' % (headerprefix, i))
57 v = req.headers.get(b'%s-%d' % (headerprefix, i))
58 if v is None:
58 if v is None:
59 break
59 break
60 chunks.append(pycompat.bytesurl(v))
60 chunks.append(pycompat.bytesurl(v))
61 i += 1
61 i += 1
62
62
63 return ''.join(chunks)
63 return ''.join(chunks)
64
64
65 @zi.implementer(wireprototypes.baseprotocolhandler)
65 @zi.implementer(wireprototypes.baseprotocolhandler)
66 class httpv1protocolhandler(object):
66 class httpv1protocolhandler(object):
67 def __init__(self, req, ui, checkperm):
67 def __init__(self, req, ui, checkperm):
68 self._req = req
68 self._req = req
69 self._ui = ui
69 self._ui = ui
70 self._checkperm = checkperm
70 self._checkperm = checkperm
71 self._protocaps = None
71 self._protocaps = None
72
72
73 @property
73 @property
74 def name(self):
74 def name(self):
75 return 'http-v1'
75 return 'http-v1'
76
76
77 def getargs(self, args):
77 def getargs(self, args):
78 knownargs = self._args()
78 knownargs = self._args()
79 data = {}
79 data = {}
80 keys = args.split()
80 keys = args.split()
81 for k in keys:
81 for k in keys:
82 if k == '*':
82 if k == '*':
83 star = {}
83 star = {}
84 for key in knownargs.keys():
84 for key in knownargs.keys():
85 if key != 'cmd' and key not in keys:
85 if key != 'cmd' and key not in keys:
86 star[key] = knownargs[key][0]
86 star[key] = knownargs[key][0]
87 data['*'] = star
87 data['*'] = star
88 else:
88 else:
89 data[k] = knownargs[k][0]
89 data[k] = knownargs[k][0]
90 return [data[k] for k in keys]
90 return [data[k] for k in keys]
91
91
92 def _args(self):
92 def _args(self):
93 args = self._req.qsparams.asdictoflists()
93 args = self._req.qsparams.asdictoflists()
94 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
94 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
95 if postlen:
95 if postlen:
96 args.update(urlreq.parseqs(
96 args.update(urlreq.parseqs(
97 self._req.bodyfh.read(postlen), keep_blank_values=True))
97 self._req.bodyfh.read(postlen), keep_blank_values=True))
98 return args
98 return args
99
99
100 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
100 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
101 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
101 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
102 return args
102 return args
103
103
104 def getprotocaps(self):
104 def getprotocaps(self):
105 if self._protocaps is None:
105 if self._protocaps is None:
106 value = decodevaluefromheaders(self._req, b'X-HgProto')
106 value = decodevaluefromheaders(self._req, b'X-HgProto')
107 self._protocaps = set(value.split(' '))
107 self._protocaps = set(value.split(' '))
108 return self._protocaps
108 return self._protocaps
109
109
110 def getpayload(self):
110 def getpayload(self):
111 # Existing clients *always* send Content-Length.
111 # Existing clients *always* send Content-Length.
112 length = int(self._req.headers[b'Content-Length'])
112 length = int(self._req.headers[b'Content-Length'])
113
113
114 # If httppostargs is used, we need to read Content-Length
114 # If httppostargs is used, we need to read Content-Length
115 # minus the amount that was consumed by args.
115 # minus the amount that was consumed by args.
116 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
116 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
117 return util.filechunkiter(self._req.bodyfh, limit=length)
117 return util.filechunkiter(self._req.bodyfh, limit=length)
118
118
119 @contextlib.contextmanager
119 @contextlib.contextmanager
120 def mayberedirectstdio(self):
120 def mayberedirectstdio(self):
121 oldout = self._ui.fout
121 oldout = self._ui.fout
122 olderr = self._ui.ferr
122 olderr = self._ui.ferr
123
123
124 out = util.stringio()
124 out = util.stringio()
125
125
126 try:
126 try:
127 self._ui.fout = out
127 self._ui.fout = out
128 self._ui.ferr = out
128 self._ui.ferr = out
129 yield out
129 yield out
130 finally:
130 finally:
131 self._ui.fout = oldout
131 self._ui.fout = oldout
132 self._ui.ferr = olderr
132 self._ui.ferr = olderr
133
133
134 def client(self):
134 def client(self):
135 return 'remote:%s:%s:%s' % (
135 return 'remote:%s:%s:%s' % (
136 self._req.urlscheme,
136 self._req.urlscheme,
137 urlreq.quote(self._req.remotehost or ''),
137 urlreq.quote(self._req.remotehost or ''),
138 urlreq.quote(self._req.remoteuser or ''))
138 urlreq.quote(self._req.remoteuser or ''))
139
139
140 def addcapabilities(self, repo, caps):
140 def addcapabilities(self, repo, caps):
141 caps.append(b'batch')
141 caps.append(b'batch')
142
142
143 caps.append('httpheader=%d' %
143 caps.append('httpheader=%d' %
144 repo.ui.configint('server', 'maxhttpheaderlen'))
144 repo.ui.configint('server', 'maxhttpheaderlen'))
145 if repo.ui.configbool('experimental', 'httppostargs'):
145 if repo.ui.configbool('experimental', 'httppostargs'):
146 caps.append('httppostargs')
146 caps.append('httppostargs')
147
147
148 # FUTURE advertise 0.2rx once support is implemented
148 # FUTURE advertise 0.2rx once support is implemented
149 # FUTURE advertise minrx and mintx after consulting config option
149 # FUTURE advertise minrx and mintx after consulting config option
150 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
150 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
151
151
152 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
152 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
153 if compengines:
153 if compengines:
154 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
154 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
155 for e in compengines)
155 for e in compengines)
156 caps.append('compression=%s' % comptypes)
156 caps.append('compression=%s' % comptypes)
157
157
158 return caps
158 return caps
159
159
160 def checkperm(self, perm):
160 def checkperm(self, perm):
161 return self._checkperm(perm)
161 return self._checkperm(perm)
162
162
163 # This method exists mostly so that extensions like remotefilelog can
163 # This method exists mostly so that extensions like remotefilelog can
164 # disable a kludgey legacy method only over http. As of early 2018,
164 # disable a kludgey legacy method only over http. As of early 2018,
165 # there are no other known users, so with any luck we can discard this
165 # there are no other known users, so with any luck we can discard this
166 # hook if remotefilelog becomes a first-party extension.
166 # hook if remotefilelog becomes a first-party extension.
167 def iscmd(cmd):
167 def iscmd(cmd):
168 return cmd in wireproto.commands
168 return cmd in wireproto.commands
169
169
170 def handlewsgirequest(rctx, req, res, checkperm):
170 def handlewsgirequest(rctx, req, res, checkperm):
171 """Possibly process a wire protocol request.
171 """Possibly process a wire protocol request.
172
172
173 If the current request is a wire protocol request, the request is
173 If the current request is a wire protocol request, the request is
174 processed by this function.
174 processed by this function.
175
175
176 ``req`` is a ``parsedrequest`` instance.
176 ``req`` is a ``parsedrequest`` instance.
177 ``res`` is a ``wsgiresponse`` instance.
177 ``res`` is a ``wsgiresponse`` instance.
178
178
179 Returns a bool indicating if the request was serviced. If set, the caller
179 Returns a bool indicating if the request was serviced. If set, the caller
180 should stop processing the request, as a response has already been issued.
180 should stop processing the request, as a response has already been issued.
181 """
181 """
182 # Avoid cycle involving hg module.
182 # Avoid cycle involving hg module.
183 from .hgweb import common as hgwebcommon
183 from .hgweb import common as hgwebcommon
184
184
185 repo = rctx.repo
185 repo = rctx.repo
186
186
187 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
187 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
188 # string parameter. If it isn't present, this isn't a wire protocol
188 # string parameter. If it isn't present, this isn't a wire protocol
189 # request.
189 # request.
190 if 'cmd' not in req.qsparams:
190 if 'cmd' not in req.qsparams:
191 return False
191 return False
192
192
193 cmd = req.qsparams['cmd']
193 cmd = req.qsparams['cmd']
194
194
195 # The "cmd" request parameter is used by both the wire protocol and hgweb.
195 # The "cmd" request parameter is used by both the wire protocol and hgweb.
196 # While not all wire protocol commands are available for all transports,
196 # While not all wire protocol commands are available for all transports,
197 # if we see a "cmd" value that resembles a known wire protocol command, we
197 # if we see a "cmd" value that resembles a known wire protocol command, we
198 # route it to a protocol handler. This is better than routing possible
198 # route it to a protocol handler. This is better than routing possible
199 # wire protocol requests to hgweb because it prevents hgweb from using
199 # wire protocol requests to hgweb because it prevents hgweb from using
200 # known wire protocol commands and it is less confusing for machine
200 # known wire protocol commands and it is less confusing for machine
201 # clients.
201 # clients.
202 if not iscmd(cmd):
202 if not iscmd(cmd):
203 return False
203 return False
204
204
205 # The "cmd" query string argument is only valid on the root path of the
205 # The "cmd" query string argument is only valid on the root path of the
206 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
206 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
207 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
207 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
208 # in this case. We send an HTTP 404 for backwards compatibility reasons.
208 # in this case. We send an HTTP 404 for backwards compatibility reasons.
209 if req.dispatchpath:
209 if req.dispatchpath:
210 res.status = hgwebcommon.statusmessage(404)
210 res.status = hgwebcommon.statusmessage(404)
211 res.headers['Content-Type'] = HGTYPE
211 res.headers['Content-Type'] = HGTYPE
212 # TODO This is not a good response to issue for this request. This
212 # TODO This is not a good response to issue for this request. This
213 # is mostly for BC for now.
213 # is mostly for BC for now.
214 res.setbodybytes('0\n%s\n' % b'Not Found')
214 res.setbodybytes('0\n%s\n' % b'Not Found')
215 return True
215 return True
216
216
217 proto = httpv1protocolhandler(req, repo.ui,
217 proto = httpv1protocolhandler(req, repo.ui,
218 lambda perm: checkperm(rctx, req, perm))
218 lambda perm: checkperm(rctx, req, perm))
219
219
220 # The permissions checker should be the only thing that can raise an
220 # The permissions checker should be the only thing that can raise an
221 # ErrorResponse. It is kind of a layer violation to catch an hgweb
221 # ErrorResponse. It is kind of a layer violation to catch an hgweb
222 # exception here. So consider refactoring into a exception type that
222 # exception here. So consider refactoring into a exception type that
223 # is associated with the wire protocol.
223 # is associated with the wire protocol.
224 try:
224 try:
225 _callhttp(repo, req, res, proto, cmd)
225 _callhttp(repo, req, res, proto, cmd)
226 except hgwebcommon.ErrorResponse as e:
226 except hgwebcommon.ErrorResponse as e:
227 for k, v in e.headers:
227 for k, v in e.headers:
228 res.headers[k] = v
228 res.headers[k] = v
229 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
229 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
230 # TODO This response body assumes the failed command was
230 # TODO This response body assumes the failed command was
231 # "unbundle." That assumption is not always valid.
231 # "unbundle." That assumption is not always valid.
232 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
232 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
233
233
234 return True
234 return True
235
235
236 def _availableapis(repo):
236 def _availableapis(repo):
237 apis = set()
237 apis = set()
238
238
239 # Registered APIs are made available via config options of the name of
239 # Registered APIs are made available via config options of the name of
240 # the protocol.
240 # the protocol.
241 for k, v in API_HANDLERS.items():
241 for k, v in API_HANDLERS.items():
242 section, option = v['config']
242 section, option = v['config']
243 if repo.ui.configbool(section, option):
243 if repo.ui.configbool(section, option):
244 apis.add(k)
244 apis.add(k)
245
245
246 return apis
246 return apis
247
247
248 def handlewsgiapirequest(rctx, req, res, checkperm):
248 def handlewsgiapirequest(rctx, req, res, checkperm):
249 """Handle requests to /api/*."""
249 """Handle requests to /api/*."""
250 assert req.dispatchparts[0] == b'api'
250 assert req.dispatchparts[0] == b'api'
251
251
252 repo = rctx.repo
252 repo = rctx.repo
253
253
254 # This whole URL space is experimental for now. But we want to
254 # This whole URL space is experimental for now. But we want to
255 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
255 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
256 if not repo.ui.configbool('experimental', 'web.apiserver'):
256 if not repo.ui.configbool('experimental', 'web.apiserver'):
257 res.status = b'404 Not Found'
257 res.status = b'404 Not Found'
258 res.headers[b'Content-Type'] = b'text/plain'
258 res.headers[b'Content-Type'] = b'text/plain'
259 res.setbodybytes(_('Experimental API server endpoint not enabled'))
259 res.setbodybytes(_('Experimental API server endpoint not enabled'))
260 return
260 return
261
261
262 # The URL space is /api/<protocol>/*. The structure of URLs under varies
262 # The URL space is /api/<protocol>/*. The structure of URLs under varies
263 # by <protocol>.
263 # by <protocol>.
264
264
265 availableapis = _availableapis(repo)
265 availableapis = _availableapis(repo)
266
266
267 # Requests to /api/ list available APIs.
267 # Requests to /api/ list available APIs.
268 if req.dispatchparts == [b'api']:
268 if req.dispatchparts == [b'api']:
269 res.status = b'200 OK'
269 res.status = b'200 OK'
270 res.headers[b'Content-Type'] = b'text/plain'
270 res.headers[b'Content-Type'] = b'text/plain'
271 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
271 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
272 'one of the following:\n')]
272 'one of the following:\n')]
273 if availableapis:
273 if availableapis:
274 lines.extend(sorted(availableapis))
274 lines.extend(sorted(availableapis))
275 else:
275 else:
276 lines.append(_('(no available APIs)\n'))
276 lines.append(_('(no available APIs)\n'))
277 res.setbodybytes(b'\n'.join(lines))
277 res.setbodybytes(b'\n'.join(lines))
278 return
278 return
279
279
280 proto = req.dispatchparts[1]
280 proto = req.dispatchparts[1]
281
281
282 if proto not in API_HANDLERS:
282 if proto not in API_HANDLERS:
283 res.status = b'404 Not Found'
283 res.status = b'404 Not Found'
284 res.headers[b'Content-Type'] = b'text/plain'
284 res.headers[b'Content-Type'] = b'text/plain'
285 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
285 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
286 proto, b', '.join(sorted(availableapis))))
286 proto, b', '.join(sorted(availableapis))))
287 return
287 return
288
288
289 if proto not in availableapis:
289 if proto not in availableapis:
290 res.status = b'404 Not Found'
290 res.status = b'404 Not Found'
291 res.headers[b'Content-Type'] = b'text/plain'
291 res.headers[b'Content-Type'] = b'text/plain'
292 res.setbodybytes(_('API %s not enabled\n') % proto)
292 res.setbodybytes(_('API %s not enabled\n') % proto)
293 return
293 return
294
294
295 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
295 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
296 req.dispatchparts[2:])
296 req.dispatchparts[2:])
297
297
298 # Maps API name to metadata so custom API can be registered.
298 # Maps API name to metadata so custom API can be registered.
299 # Keys are:
299 # Keys are:
300 #
300 #
301 # config
301 # config
302 # Config option that controls whether service is enabled.
302 # Config option that controls whether service is enabled.
303 # handler
303 # handler
304 # Callable receiving (rctx, req, res, checkperm, urlparts) that is called
304 # Callable receiving (rctx, req, res, checkperm, urlparts) that is called
305 # when a request to this API is received.
305 # when a request to this API is received.
306 # apidescriptor
306 # apidescriptor
307 # Callable receiving (req, repo) that is called to obtain an API
307 # Callable receiving (req, repo) that is called to obtain an API
308 # descriptor for this service. The response must be serializable to CBOR.
308 # descriptor for this service. The response must be serializable to CBOR.
309 API_HANDLERS = {
309 API_HANDLERS = {
310 wireprotov2server.HTTPV2: {
310 wireprotov2server.HTTP_WIREPROTO_V2: {
311 'config': ('experimental', 'web.api.http-v2'),
311 'config': ('experimental', 'web.api.http-v2'),
312 'handler': wireprotov2server.handlehttpv2request,
312 'handler': wireprotov2server.handlehttpv2request,
313 'apidescriptor': wireprotov2server.httpv2apidescriptor,
313 'apidescriptor': wireprotov2server.httpv2apidescriptor,
314 },
314 },
315 }
315 }
316
316
317 def _httpresponsetype(ui, proto, prefer_uncompressed):
317 def _httpresponsetype(ui, proto, prefer_uncompressed):
318 """Determine the appropriate response type and compression settings.
318 """Determine the appropriate response type and compression settings.
319
319
320 Returns a tuple of (mediatype, compengine, engineopts).
320 Returns a tuple of (mediatype, compengine, engineopts).
321 """
321 """
322 # Determine the response media type and compression engine based
322 # Determine the response media type and compression engine based
323 # on the request parameters.
323 # on the request parameters.
324
324
325 if '0.2' in proto.getprotocaps():
325 if '0.2' in proto.getprotocaps():
326 # All clients are expected to support uncompressed data.
326 # All clients are expected to support uncompressed data.
327 if prefer_uncompressed:
327 if prefer_uncompressed:
328 return HGTYPE2, util._noopengine(), {}
328 return HGTYPE2, util._noopengine(), {}
329
329
330 # Now find an agreed upon compression format.
330 # Now find an agreed upon compression format.
331 compformats = wireproto.clientcompressionsupport(proto)
331 compformats = wireproto.clientcompressionsupport(proto)
332 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
332 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
333 if engine.wireprotosupport().name in compformats:
333 if engine.wireprotosupport().name in compformats:
334 opts = {}
334 opts = {}
335 level = ui.configint('server', '%slevel' % engine.name())
335 level = ui.configint('server', '%slevel' % engine.name())
336 if level is not None:
336 if level is not None:
337 opts['level'] = level
337 opts['level'] = level
338
338
339 return HGTYPE2, engine, opts
339 return HGTYPE2, engine, opts
340
340
341 # No mutually supported compression format. Fall back to the
341 # No mutually supported compression format. Fall back to the
342 # legacy protocol.
342 # legacy protocol.
343
343
344 # Don't allow untrusted settings because disabling compression or
344 # Don't allow untrusted settings because disabling compression or
345 # setting a very high compression level could lead to flooding
345 # setting a very high compression level could lead to flooding
346 # the server's network or CPU.
346 # the server's network or CPU.
347 opts = {'level': ui.configint('server', 'zliblevel')}
347 opts = {'level': ui.configint('server', 'zliblevel')}
348 return HGTYPE, util.compengines['zlib'], opts
348 return HGTYPE, util.compengines['zlib'], opts
349
349
350 def processcapabilitieshandshake(repo, req, res, proto):
350 def processcapabilitieshandshake(repo, req, res, proto):
351 """Called during a ?cmd=capabilities request.
351 """Called during a ?cmd=capabilities request.
352
352
353 If the client is advertising support for a newer protocol, we send
353 If the client is advertising support for a newer protocol, we send
354 a CBOR response with information about available services. If no
354 a CBOR response with information about available services. If no
355 advertised services are available, we don't handle the request.
355 advertised services are available, we don't handle the request.
356 """
356 """
357 # Fall back to old behavior unless the API server is enabled.
357 # Fall back to old behavior unless the API server is enabled.
358 if not repo.ui.configbool('experimental', 'web.apiserver'):
358 if not repo.ui.configbool('experimental', 'web.apiserver'):
359 return False
359 return False
360
360
361 clientapis = decodevaluefromheaders(req, b'X-HgUpgrade')
361 clientapis = decodevaluefromheaders(req, b'X-HgUpgrade')
362 protocaps = decodevaluefromheaders(req, b'X-HgProto')
362 protocaps = decodevaluefromheaders(req, b'X-HgProto')
363 if not clientapis or not protocaps:
363 if not clientapis or not protocaps:
364 return False
364 return False
365
365
366 # We currently only support CBOR responses.
366 # We currently only support CBOR responses.
367 protocaps = set(protocaps.split(' '))
367 protocaps = set(protocaps.split(' '))
368 if b'cbor' not in protocaps:
368 if b'cbor' not in protocaps:
369 return False
369 return False
370
370
371 descriptors = {}
371 descriptors = {}
372
372
373 for api in sorted(set(clientapis.split()) & _availableapis(repo)):
373 for api in sorted(set(clientapis.split()) & _availableapis(repo)):
374 handler = API_HANDLERS[api]
374 handler = API_HANDLERS[api]
375
375
376 descriptorfn = handler.get('apidescriptor')
376 descriptorfn = handler.get('apidescriptor')
377 if not descriptorfn:
377 if not descriptorfn:
378 continue
378 continue
379
379
380 descriptors[api] = descriptorfn(req, repo)
380 descriptors[api] = descriptorfn(req, repo)
381
381
382 v1caps = wireproto.dispatch(repo, proto, 'capabilities')
382 v1caps = wireproto.dispatch(repo, proto, 'capabilities')
383 assert isinstance(v1caps, wireprototypes.bytesresponse)
383 assert isinstance(v1caps, wireprototypes.bytesresponse)
384
384
385 m = {
385 m = {
386 # TODO allow this to be configurable.
386 # TODO allow this to be configurable.
387 'apibase': 'api/',
387 'apibase': 'api/',
388 'apis': descriptors,
388 'apis': descriptors,
389 'v1capabilities': v1caps.data,
389 'v1capabilities': v1caps.data,
390 }
390 }
391
391
392 res.status = b'200 OK'
392 res.status = b'200 OK'
393 res.headers[b'Content-Type'] = b'application/mercurial-cbor'
393 res.headers[b'Content-Type'] = b'application/mercurial-cbor'
394 res.setbodybytes(cbor.dumps(m, canonical=True))
394 res.setbodybytes(cbor.dumps(m, canonical=True))
395
395
396 return True
396 return True
397
397
398 def _callhttp(repo, req, res, proto, cmd):
398 def _callhttp(repo, req, res, proto, cmd):
399 # Avoid cycle involving hg module.
399 # Avoid cycle involving hg module.
400 from .hgweb import common as hgwebcommon
400 from .hgweb import common as hgwebcommon
401
401
402 def genversion2(gen, engine, engineopts):
402 def genversion2(gen, engine, engineopts):
403 # application/mercurial-0.2 always sends a payload header
403 # application/mercurial-0.2 always sends a payload header
404 # identifying the compression engine.
404 # identifying the compression engine.
405 name = engine.wireprotosupport().name
405 name = engine.wireprotosupport().name
406 assert 0 < len(name) < 256
406 assert 0 < len(name) < 256
407 yield struct.pack('B', len(name))
407 yield struct.pack('B', len(name))
408 yield name
408 yield name
409
409
410 for chunk in gen:
410 for chunk in gen:
411 yield chunk
411 yield chunk
412
412
413 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
413 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
414 if code == HTTP_OK:
414 if code == HTTP_OK:
415 res.status = '200 Script output follows'
415 res.status = '200 Script output follows'
416 else:
416 else:
417 res.status = hgwebcommon.statusmessage(code)
417 res.status = hgwebcommon.statusmessage(code)
418
418
419 res.headers['Content-Type'] = contenttype
419 res.headers['Content-Type'] = contenttype
420
420
421 if bodybytes is not None:
421 if bodybytes is not None:
422 res.setbodybytes(bodybytes)
422 res.setbodybytes(bodybytes)
423 if bodygen is not None:
423 if bodygen is not None:
424 res.setbodygen(bodygen)
424 res.setbodygen(bodygen)
425
425
426 if not wireproto.commands.commandavailable(cmd, proto):
426 if not wireproto.commands.commandavailable(cmd, proto):
427 setresponse(HTTP_OK, HGERRTYPE,
427 setresponse(HTTP_OK, HGERRTYPE,
428 _('requested wire protocol command is not available over '
428 _('requested wire protocol command is not available over '
429 'HTTP'))
429 'HTTP'))
430 return
430 return
431
431
432 proto.checkperm(wireproto.commands[cmd].permission)
432 proto.checkperm(wireproto.commands[cmd].permission)
433
433
434 # Possibly handle a modern client wanting to switch protocols.
434 # Possibly handle a modern client wanting to switch protocols.
435 if (cmd == 'capabilities' and
435 if (cmd == 'capabilities' and
436 processcapabilitieshandshake(repo, req, res, proto)):
436 processcapabilitieshandshake(repo, req, res, proto)):
437
437
438 return
438 return
439
439
440 rsp = wireproto.dispatch(repo, proto, cmd)
440 rsp = wireproto.dispatch(repo, proto, cmd)
441
441
442 if isinstance(rsp, bytes):
442 if isinstance(rsp, bytes):
443 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
443 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
444 elif isinstance(rsp, wireprototypes.bytesresponse):
444 elif isinstance(rsp, wireprototypes.bytesresponse):
445 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
445 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
446 elif isinstance(rsp, wireprototypes.streamreslegacy):
446 elif isinstance(rsp, wireprototypes.streamreslegacy):
447 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
447 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
448 elif isinstance(rsp, wireprototypes.streamres):
448 elif isinstance(rsp, wireprototypes.streamres):
449 gen = rsp.gen
449 gen = rsp.gen
450
450
451 # This code for compression should not be streamres specific. It
451 # This code for compression should not be streamres specific. It
452 # is here because we only compress streamres at the moment.
452 # is here because we only compress streamres at the moment.
453 mediatype, engine, engineopts = _httpresponsetype(
453 mediatype, engine, engineopts = _httpresponsetype(
454 repo.ui, proto, rsp.prefer_uncompressed)
454 repo.ui, proto, rsp.prefer_uncompressed)
455 gen = engine.compressstream(gen, engineopts)
455 gen = engine.compressstream(gen, engineopts)
456
456
457 if mediatype == HGTYPE2:
457 if mediatype == HGTYPE2:
458 gen = genversion2(gen, engine, engineopts)
458 gen = genversion2(gen, engine, engineopts)
459
459
460 setresponse(HTTP_OK, mediatype, bodygen=gen)
460 setresponse(HTTP_OK, mediatype, bodygen=gen)
461 elif isinstance(rsp, wireprototypes.pushres):
461 elif isinstance(rsp, wireprototypes.pushres):
462 rsp = '%d\n%s' % (rsp.res, rsp.output)
462 rsp = '%d\n%s' % (rsp.res, rsp.output)
463 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
463 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
464 elif isinstance(rsp, wireprototypes.pusherr):
464 elif isinstance(rsp, wireprototypes.pusherr):
465 rsp = '0\n%s\n' % rsp.res
465 rsp = '0\n%s\n' % rsp.res
466 res.drain = True
466 res.drain = True
467 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
467 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
468 elif isinstance(rsp, wireprototypes.ooberror):
468 elif isinstance(rsp, wireprototypes.ooberror):
469 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
469 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
470 else:
470 else:
471 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
471 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
472
472
473 def _sshv1respondbytes(fout, value):
473 def _sshv1respondbytes(fout, value):
474 """Send a bytes response for protocol version 1."""
474 """Send a bytes response for protocol version 1."""
475 fout.write('%d\n' % len(value))
475 fout.write('%d\n' % len(value))
476 fout.write(value)
476 fout.write(value)
477 fout.flush()
477 fout.flush()
478
478
479 def _sshv1respondstream(fout, source):
479 def _sshv1respondstream(fout, source):
480 write = fout.write
480 write = fout.write
481 for chunk in source.gen:
481 for chunk in source.gen:
482 write(chunk)
482 write(chunk)
483 fout.flush()
483 fout.flush()
484
484
485 def _sshv1respondooberror(fout, ferr, rsp):
485 def _sshv1respondooberror(fout, ferr, rsp):
486 ferr.write(b'%s\n-\n' % rsp)
486 ferr.write(b'%s\n-\n' % rsp)
487 ferr.flush()
487 ferr.flush()
488 fout.write(b'\n')
488 fout.write(b'\n')
489 fout.flush()
489 fout.flush()
490
490
491 @zi.implementer(wireprototypes.baseprotocolhandler)
491 @zi.implementer(wireprototypes.baseprotocolhandler)
492 class sshv1protocolhandler(object):
492 class sshv1protocolhandler(object):
493 """Handler for requests services via version 1 of SSH protocol."""
493 """Handler for requests services via version 1 of SSH protocol."""
494 def __init__(self, ui, fin, fout):
494 def __init__(self, ui, fin, fout):
495 self._ui = ui
495 self._ui = ui
496 self._fin = fin
496 self._fin = fin
497 self._fout = fout
497 self._fout = fout
498 self._protocaps = set()
498 self._protocaps = set()
499
499
500 @property
500 @property
501 def name(self):
501 def name(self):
502 return wireprototypes.SSHV1
502 return wireprototypes.SSHV1
503
503
504 def getargs(self, args):
504 def getargs(self, args):
505 data = {}
505 data = {}
506 keys = args.split()
506 keys = args.split()
507 for n in xrange(len(keys)):
507 for n in xrange(len(keys)):
508 argline = self._fin.readline()[:-1]
508 argline = self._fin.readline()[:-1]
509 arg, l = argline.split()
509 arg, l = argline.split()
510 if arg not in keys:
510 if arg not in keys:
511 raise error.Abort(_("unexpected parameter %r") % arg)
511 raise error.Abort(_("unexpected parameter %r") % arg)
512 if arg == '*':
512 if arg == '*':
513 star = {}
513 star = {}
514 for k in xrange(int(l)):
514 for k in xrange(int(l)):
515 argline = self._fin.readline()[:-1]
515 argline = self._fin.readline()[:-1]
516 arg, l = argline.split()
516 arg, l = argline.split()
517 val = self._fin.read(int(l))
517 val = self._fin.read(int(l))
518 star[arg] = val
518 star[arg] = val
519 data['*'] = star
519 data['*'] = star
520 else:
520 else:
521 val = self._fin.read(int(l))
521 val = self._fin.read(int(l))
522 data[arg] = val
522 data[arg] = val
523 return [data[k] for k in keys]
523 return [data[k] for k in keys]
524
524
525 def getprotocaps(self):
525 def getprotocaps(self):
526 return self._protocaps
526 return self._protocaps
527
527
528 def getpayload(self):
528 def getpayload(self):
529 # We initially send an empty response. This tells the client it is
529 # We initially send an empty response. This tells the client it is
530 # OK to start sending data. If a client sees any other response, it
530 # OK to start sending data. If a client sees any other response, it
531 # interprets it as an error.
531 # interprets it as an error.
532 _sshv1respondbytes(self._fout, b'')
532 _sshv1respondbytes(self._fout, b'')
533
533
534 # The file is in the form:
534 # The file is in the form:
535 #
535 #
536 # <chunk size>\n<chunk>
536 # <chunk size>\n<chunk>
537 # ...
537 # ...
538 # 0\n
538 # 0\n
539 count = int(self._fin.readline())
539 count = int(self._fin.readline())
540 while count:
540 while count:
541 yield self._fin.read(count)
541 yield self._fin.read(count)
542 count = int(self._fin.readline())
542 count = int(self._fin.readline())
543
543
544 @contextlib.contextmanager
544 @contextlib.contextmanager
545 def mayberedirectstdio(self):
545 def mayberedirectstdio(self):
546 yield None
546 yield None
547
547
548 def client(self):
548 def client(self):
549 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
549 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
550 return 'remote:ssh:' + client
550 return 'remote:ssh:' + client
551
551
552 def addcapabilities(self, repo, caps):
552 def addcapabilities(self, repo, caps):
553 if self.name == wireprototypes.SSHV1:
553 if self.name == wireprototypes.SSHV1:
554 caps.append(b'protocaps')
554 caps.append(b'protocaps')
555 caps.append(b'batch')
555 caps.append(b'batch')
556 return caps
556 return caps
557
557
558 def checkperm(self, perm):
558 def checkperm(self, perm):
559 pass
559 pass
560
560
561 class sshv2protocolhandler(sshv1protocolhandler):
561 class sshv2protocolhandler(sshv1protocolhandler):
562 """Protocol handler for version 2 of the SSH protocol."""
562 """Protocol handler for version 2 of the SSH protocol."""
563
563
564 @property
564 @property
565 def name(self):
565 def name(self):
566 return wireprototypes.SSHV2
566 return wireprototypes.SSHV2
567
567
568 def addcapabilities(self, repo, caps):
568 def addcapabilities(self, repo, caps):
569 return caps
569 return caps
570
570
571 def _runsshserver(ui, repo, fin, fout, ev):
571 def _runsshserver(ui, repo, fin, fout, ev):
572 # This function operates like a state machine of sorts. The following
572 # This function operates like a state machine of sorts. The following
573 # states are defined:
573 # states are defined:
574 #
574 #
575 # protov1-serving
575 # protov1-serving
576 # Server is in protocol version 1 serving mode. Commands arrive on
576 # Server is in protocol version 1 serving mode. Commands arrive on
577 # new lines. These commands are processed in this state, one command
577 # new lines. These commands are processed in this state, one command
578 # after the other.
578 # after the other.
579 #
579 #
580 # protov2-serving
580 # protov2-serving
581 # Server is in protocol version 2 serving mode.
581 # Server is in protocol version 2 serving mode.
582 #
582 #
583 # upgrade-initial
583 # upgrade-initial
584 # The server is going to process an upgrade request.
584 # The server is going to process an upgrade request.
585 #
585 #
586 # upgrade-v2-filter-legacy-handshake
586 # upgrade-v2-filter-legacy-handshake
587 # The protocol is being upgraded to version 2. The server is expecting
587 # The protocol is being upgraded to version 2. The server is expecting
588 # the legacy handshake from version 1.
588 # the legacy handshake from version 1.
589 #
589 #
590 # upgrade-v2-finish
590 # upgrade-v2-finish
591 # The upgrade to version 2 of the protocol is imminent.
591 # The upgrade to version 2 of the protocol is imminent.
592 #
592 #
593 # shutdown
593 # shutdown
594 # The server is shutting down, possibly in reaction to a client event.
594 # The server is shutting down, possibly in reaction to a client event.
595 #
595 #
596 # And here are their transitions:
596 # And here are their transitions:
597 #
597 #
598 # protov1-serving -> shutdown
598 # protov1-serving -> shutdown
599 # When server receives an empty request or encounters another
599 # When server receives an empty request or encounters another
600 # error.
600 # error.
601 #
601 #
602 # protov1-serving -> upgrade-initial
602 # protov1-serving -> upgrade-initial
603 # An upgrade request line was seen.
603 # An upgrade request line was seen.
604 #
604 #
605 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
605 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
606 # Upgrade to version 2 in progress. Server is expecting to
606 # Upgrade to version 2 in progress. Server is expecting to
607 # process a legacy handshake.
607 # process a legacy handshake.
608 #
608 #
609 # upgrade-v2-filter-legacy-handshake -> shutdown
609 # upgrade-v2-filter-legacy-handshake -> shutdown
610 # Client did not fulfill upgrade handshake requirements.
610 # Client did not fulfill upgrade handshake requirements.
611 #
611 #
612 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
612 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
613 # Client fulfilled version 2 upgrade requirements. Finishing that
613 # Client fulfilled version 2 upgrade requirements. Finishing that
614 # upgrade.
614 # upgrade.
615 #
615 #
616 # upgrade-v2-finish -> protov2-serving
616 # upgrade-v2-finish -> protov2-serving
617 # Protocol upgrade to version 2 complete. Server can now speak protocol
617 # Protocol upgrade to version 2 complete. Server can now speak protocol
618 # version 2.
618 # version 2.
619 #
619 #
620 # protov2-serving -> protov1-serving
620 # protov2-serving -> protov1-serving
621 # Ths happens by default since protocol version 2 is the same as
621 # Ths happens by default since protocol version 2 is the same as
622 # version 1 except for the handshake.
622 # version 1 except for the handshake.
623
623
624 state = 'protov1-serving'
624 state = 'protov1-serving'
625 proto = sshv1protocolhandler(ui, fin, fout)
625 proto = sshv1protocolhandler(ui, fin, fout)
626 protoswitched = False
626 protoswitched = False
627
627
628 while not ev.is_set():
628 while not ev.is_set():
629 if state == 'protov1-serving':
629 if state == 'protov1-serving':
630 # Commands are issued on new lines.
630 # Commands are issued on new lines.
631 request = fin.readline()[:-1]
631 request = fin.readline()[:-1]
632
632
633 # Empty lines signal to terminate the connection.
633 # Empty lines signal to terminate the connection.
634 if not request:
634 if not request:
635 state = 'shutdown'
635 state = 'shutdown'
636 continue
636 continue
637
637
638 # It looks like a protocol upgrade request. Transition state to
638 # It looks like a protocol upgrade request. Transition state to
639 # handle it.
639 # handle it.
640 if request.startswith(b'upgrade '):
640 if request.startswith(b'upgrade '):
641 if protoswitched:
641 if protoswitched:
642 _sshv1respondooberror(fout, ui.ferr,
642 _sshv1respondooberror(fout, ui.ferr,
643 b'cannot upgrade protocols multiple '
643 b'cannot upgrade protocols multiple '
644 b'times')
644 b'times')
645 state = 'shutdown'
645 state = 'shutdown'
646 continue
646 continue
647
647
648 state = 'upgrade-initial'
648 state = 'upgrade-initial'
649 continue
649 continue
650
650
651 available = wireproto.commands.commandavailable(request, proto)
651 available = wireproto.commands.commandavailable(request, proto)
652
652
653 # This command isn't available. Send an empty response and go
653 # This command isn't available. Send an empty response and go
654 # back to waiting for a new command.
654 # back to waiting for a new command.
655 if not available:
655 if not available:
656 _sshv1respondbytes(fout, b'')
656 _sshv1respondbytes(fout, b'')
657 continue
657 continue
658
658
659 rsp = wireproto.dispatch(repo, proto, request)
659 rsp = wireproto.dispatch(repo, proto, request)
660
660
661 if isinstance(rsp, bytes):
661 if isinstance(rsp, bytes):
662 _sshv1respondbytes(fout, rsp)
662 _sshv1respondbytes(fout, rsp)
663 elif isinstance(rsp, wireprototypes.bytesresponse):
663 elif isinstance(rsp, wireprototypes.bytesresponse):
664 _sshv1respondbytes(fout, rsp.data)
664 _sshv1respondbytes(fout, rsp.data)
665 elif isinstance(rsp, wireprototypes.streamres):
665 elif isinstance(rsp, wireprototypes.streamres):
666 _sshv1respondstream(fout, rsp)
666 _sshv1respondstream(fout, rsp)
667 elif isinstance(rsp, wireprototypes.streamreslegacy):
667 elif isinstance(rsp, wireprototypes.streamreslegacy):
668 _sshv1respondstream(fout, rsp)
668 _sshv1respondstream(fout, rsp)
669 elif isinstance(rsp, wireprototypes.pushres):
669 elif isinstance(rsp, wireprototypes.pushres):
670 _sshv1respondbytes(fout, b'')
670 _sshv1respondbytes(fout, b'')
671 _sshv1respondbytes(fout, b'%d' % rsp.res)
671 _sshv1respondbytes(fout, b'%d' % rsp.res)
672 elif isinstance(rsp, wireprototypes.pusherr):
672 elif isinstance(rsp, wireprototypes.pusherr):
673 _sshv1respondbytes(fout, rsp.res)
673 _sshv1respondbytes(fout, rsp.res)
674 elif isinstance(rsp, wireprototypes.ooberror):
674 elif isinstance(rsp, wireprototypes.ooberror):
675 _sshv1respondooberror(fout, ui.ferr, rsp.message)
675 _sshv1respondooberror(fout, ui.ferr, rsp.message)
676 else:
676 else:
677 raise error.ProgrammingError('unhandled response type from '
677 raise error.ProgrammingError('unhandled response type from '
678 'wire protocol command: %s' % rsp)
678 'wire protocol command: %s' % rsp)
679
679
680 # For now, protocol version 2 serving just goes back to version 1.
680 # For now, protocol version 2 serving just goes back to version 1.
681 elif state == 'protov2-serving':
681 elif state == 'protov2-serving':
682 state = 'protov1-serving'
682 state = 'protov1-serving'
683 continue
683 continue
684
684
685 elif state == 'upgrade-initial':
685 elif state == 'upgrade-initial':
686 # We should never transition into this state if we've switched
686 # We should never transition into this state if we've switched
687 # protocols.
687 # protocols.
688 assert not protoswitched
688 assert not protoswitched
689 assert proto.name == wireprototypes.SSHV1
689 assert proto.name == wireprototypes.SSHV1
690
690
691 # Expected: upgrade <token> <capabilities>
691 # Expected: upgrade <token> <capabilities>
692 # If we get something else, the request is malformed. It could be
692 # If we get something else, the request is malformed. It could be
693 # from a future client that has altered the upgrade line content.
693 # from a future client that has altered the upgrade line content.
694 # We treat this as an unknown command.
694 # We treat this as an unknown command.
695 try:
695 try:
696 token, caps = request.split(b' ')[1:]
696 token, caps = request.split(b' ')[1:]
697 except ValueError:
697 except ValueError:
698 _sshv1respondbytes(fout, b'')
698 _sshv1respondbytes(fout, b'')
699 state = 'protov1-serving'
699 state = 'protov1-serving'
700 continue
700 continue
701
701
702 # Send empty response if we don't support upgrading protocols.
702 # Send empty response if we don't support upgrading protocols.
703 if not ui.configbool('experimental', 'sshserver.support-v2'):
703 if not ui.configbool('experimental', 'sshserver.support-v2'):
704 _sshv1respondbytes(fout, b'')
704 _sshv1respondbytes(fout, b'')
705 state = 'protov1-serving'
705 state = 'protov1-serving'
706 continue
706 continue
707
707
708 try:
708 try:
709 caps = urlreq.parseqs(caps)
709 caps = urlreq.parseqs(caps)
710 except ValueError:
710 except ValueError:
711 _sshv1respondbytes(fout, b'')
711 _sshv1respondbytes(fout, b'')
712 state = 'protov1-serving'
712 state = 'protov1-serving'
713 continue
713 continue
714
714
715 # We don't see an upgrade request to protocol version 2. Ignore
715 # We don't see an upgrade request to protocol version 2. Ignore
716 # the upgrade request.
716 # the upgrade request.
717 wantedprotos = caps.get(b'proto', [b''])[0]
717 wantedprotos = caps.get(b'proto', [b''])[0]
718 if SSHV2 not in wantedprotos:
718 if SSHV2 not in wantedprotos:
719 _sshv1respondbytes(fout, b'')
719 _sshv1respondbytes(fout, b'')
720 state = 'protov1-serving'
720 state = 'protov1-serving'
721 continue
721 continue
722
722
723 # It looks like we can honor this upgrade request to protocol 2.
723 # It looks like we can honor this upgrade request to protocol 2.
724 # Filter the rest of the handshake protocol request lines.
724 # Filter the rest of the handshake protocol request lines.
725 state = 'upgrade-v2-filter-legacy-handshake'
725 state = 'upgrade-v2-filter-legacy-handshake'
726 continue
726 continue
727
727
728 elif state == 'upgrade-v2-filter-legacy-handshake':
728 elif state == 'upgrade-v2-filter-legacy-handshake':
729 # Client should have sent legacy handshake after an ``upgrade``
729 # Client should have sent legacy handshake after an ``upgrade``
730 # request. Expected lines:
730 # request. Expected lines:
731 #
731 #
732 # hello
732 # hello
733 # between
733 # between
734 # pairs 81
734 # pairs 81
735 # 0000...-0000...
735 # 0000...-0000...
736
736
737 ok = True
737 ok = True
738 for line in (b'hello', b'between', b'pairs 81'):
738 for line in (b'hello', b'between', b'pairs 81'):
739 request = fin.readline()[:-1]
739 request = fin.readline()[:-1]
740
740
741 if request != line:
741 if request != line:
742 _sshv1respondooberror(fout, ui.ferr,
742 _sshv1respondooberror(fout, ui.ferr,
743 b'malformed handshake protocol: '
743 b'malformed handshake protocol: '
744 b'missing %s' % line)
744 b'missing %s' % line)
745 ok = False
745 ok = False
746 state = 'shutdown'
746 state = 'shutdown'
747 break
747 break
748
748
749 if not ok:
749 if not ok:
750 continue
750 continue
751
751
752 request = fin.read(81)
752 request = fin.read(81)
753 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
753 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
754 _sshv1respondooberror(fout, ui.ferr,
754 _sshv1respondooberror(fout, ui.ferr,
755 b'malformed handshake protocol: '
755 b'malformed handshake protocol: '
756 b'missing between argument value')
756 b'missing between argument value')
757 state = 'shutdown'
757 state = 'shutdown'
758 continue
758 continue
759
759
760 state = 'upgrade-v2-finish'
760 state = 'upgrade-v2-finish'
761 continue
761 continue
762
762
763 elif state == 'upgrade-v2-finish':
763 elif state == 'upgrade-v2-finish':
764 # Send the upgrade response.
764 # Send the upgrade response.
765 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
765 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
766 servercaps = wireproto.capabilities(repo, proto)
766 servercaps = wireproto.capabilities(repo, proto)
767 rsp = b'capabilities: %s' % servercaps.data
767 rsp = b'capabilities: %s' % servercaps.data
768 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
768 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
769 fout.flush()
769 fout.flush()
770
770
771 proto = sshv2protocolhandler(ui, fin, fout)
771 proto = sshv2protocolhandler(ui, fin, fout)
772 protoswitched = True
772 protoswitched = True
773
773
774 state = 'protov2-serving'
774 state = 'protov2-serving'
775 continue
775 continue
776
776
777 elif state == 'shutdown':
777 elif state == 'shutdown':
778 break
778 break
779
779
780 else:
780 else:
781 raise error.ProgrammingError('unhandled ssh server state: %s' %
781 raise error.ProgrammingError('unhandled ssh server state: %s' %
782 state)
782 state)
783
783
784 class sshserver(object):
784 class sshserver(object):
785 def __init__(self, ui, repo, logfh=None):
785 def __init__(self, ui, repo, logfh=None):
786 self._ui = ui
786 self._ui = ui
787 self._repo = repo
787 self._repo = repo
788 self._fin = ui.fin
788 self._fin = ui.fin
789 self._fout = ui.fout
789 self._fout = ui.fout
790
790
791 # Log write I/O to stdout and stderr if configured.
791 # Log write I/O to stdout and stderr if configured.
792 if logfh:
792 if logfh:
793 self._fout = util.makeloggingfileobject(
793 self._fout = util.makeloggingfileobject(
794 logfh, self._fout, 'o', logdata=True)
794 logfh, self._fout, 'o', logdata=True)
795 ui.ferr = util.makeloggingfileobject(
795 ui.ferr = util.makeloggingfileobject(
796 logfh, ui.ferr, 'e', logdata=True)
796 logfh, ui.ferr, 'e', logdata=True)
797
797
798 hook.redirect(True)
798 hook.redirect(True)
799 ui.fout = repo.ui.fout = ui.ferr
799 ui.fout = repo.ui.fout = ui.ferr
800
800
801 # Prevent insertion/deletion of CRs
801 # Prevent insertion/deletion of CRs
802 procutil.setbinary(self._fin)
802 procutil.setbinary(self._fin)
803 procutil.setbinary(self._fout)
803 procutil.setbinary(self._fout)
804
804
805 def serve_forever(self):
805 def serve_forever(self):
806 self.serveuntil(threading.Event())
806 self.serveuntil(threading.Event())
807 sys.exit(0)
807 sys.exit(0)
808
808
809 def serveuntil(self, ev):
809 def serveuntil(self, ev):
810 """Serve until a threading.Event is set."""
810 """Serve until a threading.Event is set."""
811 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
811 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,227 +1,227
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
2 #
2 #
3 # This software may be used and distributed according to the terms of the
3 # This software may be used and distributed according to the terms of the
4 # GNU General Public License version 2 or any later version.
4 # GNU General Public License version 2 or any later version.
5
5
6 from __future__ import absolute_import
6 from __future__ import absolute_import
7
7
8 from .node import (
8 from .node import (
9 bin,
9 bin,
10 hex,
10 hex,
11 )
11 )
12 from .thirdparty.zope import (
12 from .thirdparty.zope import (
13 interface as zi,
13 interface as zi,
14 )
14 )
15
15
16 # Names of the SSH protocol implementations.
16 # Names of the SSH protocol implementations.
17 SSHV1 = 'ssh-v1'
17 SSHV1 = 'ssh-v1'
18 # These are advertised over the wire. Increment the counters at the end
18 # These are advertised over the wire. Increment the counters at the end
19 # to reflect BC breakages.
19 # to reflect BC breakages.
20 SSHV2 = 'exp-ssh-v2-0001'
20 SSHV2 = 'exp-ssh-v2-0001'
21 HTTPV2 = 'exp-http-v2-0001'
21 HTTP_WIREPROTO_V2 = 'exp-http-v2-0001'
22
22
23 # All available wire protocol transports.
23 # All available wire protocol transports.
24 TRANSPORTS = {
24 TRANSPORTS = {
25 SSHV1: {
25 SSHV1: {
26 'transport': 'ssh',
26 'transport': 'ssh',
27 'version': 1,
27 'version': 1,
28 },
28 },
29 SSHV2: {
29 SSHV2: {
30 'transport': 'ssh',
30 'transport': 'ssh',
31 # TODO mark as version 2 once all commands are implemented.
31 # TODO mark as version 2 once all commands are implemented.
32 'version': 1,
32 'version': 1,
33 },
33 },
34 'http-v1': {
34 'http-v1': {
35 'transport': 'http',
35 'transport': 'http',
36 'version': 1,
36 'version': 1,
37 },
37 },
38 HTTPV2: {
38 HTTP_WIREPROTO_V2: {
39 'transport': 'http',
39 'transport': 'http',
40 'version': 2,
40 'version': 2,
41 }
41 }
42 }
42 }
43
43
44 class bytesresponse(object):
44 class bytesresponse(object):
45 """A wire protocol response consisting of raw bytes."""
45 """A wire protocol response consisting of raw bytes."""
46 def __init__(self, data):
46 def __init__(self, data):
47 self.data = data
47 self.data = data
48
48
49 class ooberror(object):
49 class ooberror(object):
50 """wireproto reply: failure of a batch of operation
50 """wireproto reply: failure of a batch of operation
51
51
52 Something failed during a batch call. The error message is stored in
52 Something failed during a batch call. The error message is stored in
53 `self.message`.
53 `self.message`.
54 """
54 """
55 def __init__(self, message):
55 def __init__(self, message):
56 self.message = message
56 self.message = message
57
57
58 class pushres(object):
58 class pushres(object):
59 """wireproto reply: success with simple integer return
59 """wireproto reply: success with simple integer return
60
60
61 The call was successful and returned an integer contained in `self.res`.
61 The call was successful and returned an integer contained in `self.res`.
62 """
62 """
63 def __init__(self, res, output):
63 def __init__(self, res, output):
64 self.res = res
64 self.res = res
65 self.output = output
65 self.output = output
66
66
67 class pusherr(object):
67 class pusherr(object):
68 """wireproto reply: failure
68 """wireproto reply: failure
69
69
70 The call failed. The `self.res` attribute contains the error message.
70 The call failed. The `self.res` attribute contains the error message.
71 """
71 """
72 def __init__(self, res, output):
72 def __init__(self, res, output):
73 self.res = res
73 self.res = res
74 self.output = output
74 self.output = output
75
75
76 class streamres(object):
76 class streamres(object):
77 """wireproto reply: binary stream
77 """wireproto reply: binary stream
78
78
79 The call was successful and the result is a stream.
79 The call was successful and the result is a stream.
80
80
81 Accepts a generator containing chunks of data to be sent to the client.
81 Accepts a generator containing chunks of data to be sent to the client.
82
82
83 ``prefer_uncompressed`` indicates that the data is expected to be
83 ``prefer_uncompressed`` indicates that the data is expected to be
84 uncompressable and that the stream should therefore use the ``none``
84 uncompressable and that the stream should therefore use the ``none``
85 engine.
85 engine.
86 """
86 """
87 def __init__(self, gen=None, prefer_uncompressed=False):
87 def __init__(self, gen=None, prefer_uncompressed=False):
88 self.gen = gen
88 self.gen = gen
89 self.prefer_uncompressed = prefer_uncompressed
89 self.prefer_uncompressed = prefer_uncompressed
90
90
91 class streamreslegacy(object):
91 class streamreslegacy(object):
92 """wireproto reply: uncompressed binary stream
92 """wireproto reply: uncompressed binary stream
93
93
94 The call was successful and the result is a stream.
94 The call was successful and the result is a stream.
95
95
96 Accepts a generator containing chunks of data to be sent to the client.
96 Accepts a generator containing chunks of data to be sent to the client.
97
97
98 Like ``streamres``, but sends an uncompressed data for "version 1" clients
98 Like ``streamres``, but sends an uncompressed data for "version 1" clients
99 using the application/mercurial-0.1 media type.
99 using the application/mercurial-0.1 media type.
100 """
100 """
101 def __init__(self, gen=None):
101 def __init__(self, gen=None):
102 self.gen = gen
102 self.gen = gen
103
103
104 class cborresponse(object):
104 class cborresponse(object):
105 """Encode the response value as CBOR."""
105 """Encode the response value as CBOR."""
106 def __init__(self, v):
106 def __init__(self, v):
107 self.value = v
107 self.value = v
108
108
109 # list of nodes encoding / decoding
109 # list of nodes encoding / decoding
110 def decodelist(l, sep=' '):
110 def decodelist(l, sep=' '):
111 if l:
111 if l:
112 return [bin(v) for v in l.split(sep)]
112 return [bin(v) for v in l.split(sep)]
113 return []
113 return []
114
114
115 def encodelist(l, sep=' '):
115 def encodelist(l, sep=' '):
116 try:
116 try:
117 return sep.join(map(hex, l))
117 return sep.join(map(hex, l))
118 except TypeError:
118 except TypeError:
119 raise
119 raise
120
120
121 # batched call argument encoding
121 # batched call argument encoding
122
122
123 def escapebatcharg(plain):
123 def escapebatcharg(plain):
124 return (plain
124 return (plain
125 .replace(':', ':c')
125 .replace(':', ':c')
126 .replace(',', ':o')
126 .replace(',', ':o')
127 .replace(';', ':s')
127 .replace(';', ':s')
128 .replace('=', ':e'))
128 .replace('=', ':e'))
129
129
130 def unescapebatcharg(escaped):
130 def unescapebatcharg(escaped):
131 return (escaped
131 return (escaped
132 .replace(':e', '=')
132 .replace(':e', '=')
133 .replace(':s', ';')
133 .replace(':s', ';')
134 .replace(':o', ',')
134 .replace(':o', ',')
135 .replace(':c', ':'))
135 .replace(':c', ':'))
136
136
137 # mapping of options accepted by getbundle and their types
137 # mapping of options accepted by getbundle and their types
138 #
138 #
139 # Meant to be extended by extensions. It is extensions responsibility to ensure
139 # Meant to be extended by extensions. It is extensions responsibility to ensure
140 # such options are properly processed in exchange.getbundle.
140 # such options are properly processed in exchange.getbundle.
141 #
141 #
142 # supported types are:
142 # supported types are:
143 #
143 #
144 # :nodes: list of binary nodes
144 # :nodes: list of binary nodes
145 # :csv: list of comma-separated values
145 # :csv: list of comma-separated values
146 # :scsv: list of comma-separated values return as set
146 # :scsv: list of comma-separated values return as set
147 # :plain: string with no transformation needed.
147 # :plain: string with no transformation needed.
148 GETBUNDLE_ARGUMENTS = {
148 GETBUNDLE_ARGUMENTS = {
149 'heads': 'nodes',
149 'heads': 'nodes',
150 'bookmarks': 'boolean',
150 'bookmarks': 'boolean',
151 'common': 'nodes',
151 'common': 'nodes',
152 'obsmarkers': 'boolean',
152 'obsmarkers': 'boolean',
153 'phases': 'boolean',
153 'phases': 'boolean',
154 'bundlecaps': 'scsv',
154 'bundlecaps': 'scsv',
155 'listkeys': 'csv',
155 'listkeys': 'csv',
156 'cg': 'boolean',
156 'cg': 'boolean',
157 'cbattempted': 'boolean',
157 'cbattempted': 'boolean',
158 'stream': 'boolean',
158 'stream': 'boolean',
159 }
159 }
160
160
161 class baseprotocolhandler(zi.Interface):
161 class baseprotocolhandler(zi.Interface):
162 """Abstract base class for wire protocol handlers.
162 """Abstract base class for wire protocol handlers.
163
163
164 A wire protocol handler serves as an interface between protocol command
164 A wire protocol handler serves as an interface between protocol command
165 handlers and the wire protocol transport layer. Protocol handlers provide
165 handlers and the wire protocol transport layer. Protocol handlers provide
166 methods to read command arguments, redirect stdio for the duration of
166 methods to read command arguments, redirect stdio for the duration of
167 the request, handle response types, etc.
167 the request, handle response types, etc.
168 """
168 """
169
169
170 name = zi.Attribute(
170 name = zi.Attribute(
171 """The name of the protocol implementation.
171 """The name of the protocol implementation.
172
172
173 Used for uniquely identifying the transport type.
173 Used for uniquely identifying the transport type.
174 """)
174 """)
175
175
176 def getargs(args):
176 def getargs(args):
177 """return the value for arguments in <args>
177 """return the value for arguments in <args>
178
178
179 For version 1 transports, returns a list of values in the same
179 For version 1 transports, returns a list of values in the same
180 order they appear in ``args``. For version 2 transports, returns
180 order they appear in ``args``. For version 2 transports, returns
181 a dict mapping argument name to value.
181 a dict mapping argument name to value.
182 """
182 """
183
183
184 def getprotocaps():
184 def getprotocaps():
185 """Returns the list of protocol-level capabilities of client
185 """Returns the list of protocol-level capabilities of client
186
186
187 Returns a list of capabilities as declared by the client for
187 Returns a list of capabilities as declared by the client for
188 the current request (or connection for stateful protocol handlers)."""
188 the current request (or connection for stateful protocol handlers)."""
189
189
190 def getpayload():
190 def getpayload():
191 """Provide a generator for the raw payload.
191 """Provide a generator for the raw payload.
192
192
193 The caller is responsible for ensuring that the full payload is
193 The caller is responsible for ensuring that the full payload is
194 processed.
194 processed.
195 """
195 """
196
196
197 def mayberedirectstdio():
197 def mayberedirectstdio():
198 """Context manager to possibly redirect stdio.
198 """Context manager to possibly redirect stdio.
199
199
200 The context manager yields a file-object like object that receives
200 The context manager yields a file-object like object that receives
201 stdout and stderr output when the context manager is active. Or it
201 stdout and stderr output when the context manager is active. Or it
202 yields ``None`` if no I/O redirection occurs.
202 yields ``None`` if no I/O redirection occurs.
203
203
204 The intent of this context manager is to capture stdio output
204 The intent of this context manager is to capture stdio output
205 so it may be sent in the response. Some transports support streaming
205 so it may be sent in the response. Some transports support streaming
206 stdio to the client in real time. For these transports, stdio output
206 stdio to the client in real time. For these transports, stdio output
207 won't be captured.
207 won't be captured.
208 """
208 """
209
209
210 def client():
210 def client():
211 """Returns a string representation of this client (as bytes)."""
211 """Returns a string representation of this client (as bytes)."""
212
212
213 def addcapabilities(repo, caps):
213 def addcapabilities(repo, caps):
214 """Adds advertised capabilities specific to this protocol.
214 """Adds advertised capabilities specific to this protocol.
215
215
216 Receives the list of capabilities collected so far.
216 Receives the list of capabilities collected so far.
217
217
218 Returns a list of capabilities. The passed in argument can be returned.
218 Returns a list of capabilities. The passed in argument can be returned.
219 """
219 """
220
220
221 def checkperm(perm):
221 def checkperm(perm):
222 """Validate that the client has permissions to perform a request.
222 """Validate that the client has permissions to perform a request.
223
223
224 The argument is the permission required to proceed. If the client
224 The argument is the permission required to proceed. If the client
225 doesn't have that permission, the exception should raise or abort
225 doesn't have that permission, the exception should raise or abort
226 in a protocol specific manner.
226 in a protocol specific manner.
227 """
227 """
@@ -1,479 +1,479
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import contextlib
9 import contextlib
10
10
11 from .i18n import _
11 from .i18n import _
12 from .thirdparty import (
12 from .thirdparty import (
13 cbor,
13 cbor,
14 )
14 )
15 from .thirdparty.zope import (
15 from .thirdparty.zope import (
16 interface as zi,
16 interface as zi,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 wireproto,
23 wireproto,
24 wireprotoframing,
24 wireprotoframing,
25 wireprototypes,
25 wireprototypes,
26 )
26 )
27
27
28 FRAMINGTYPE = b'application/mercurial-exp-framing-0003'
28 FRAMINGTYPE = b'application/mercurial-exp-framing-0003'
29
29
30 HTTPV2 = wireprototypes.HTTPV2
30 HTTP_WIREPROTO_V2 = wireprototypes.HTTP_WIREPROTO_V2
31
31
32 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
32 def handlehttpv2request(rctx, req, res, checkperm, urlparts):
33 from .hgweb import common as hgwebcommon
33 from .hgweb import common as hgwebcommon
34
34
35 # URL space looks like: <permissions>/<command>, where <permission> can
35 # URL space looks like: <permissions>/<command>, where <permission> can
36 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
36 # be ``ro`` or ``rw`` to signal read-only or read-write, respectively.
37
37
38 # Root URL does nothing meaningful... yet.
38 # Root URL does nothing meaningful... yet.
39 if not urlparts:
39 if not urlparts:
40 res.status = b'200 OK'
40 res.status = b'200 OK'
41 res.headers[b'Content-Type'] = b'text/plain'
41 res.headers[b'Content-Type'] = b'text/plain'
42 res.setbodybytes(_('HTTP version 2 API handler'))
42 res.setbodybytes(_('HTTP version 2 API handler'))
43 return
43 return
44
44
45 if len(urlparts) == 1:
45 if len(urlparts) == 1:
46 res.status = b'404 Not Found'
46 res.status = b'404 Not Found'
47 res.headers[b'Content-Type'] = b'text/plain'
47 res.headers[b'Content-Type'] = b'text/plain'
48 res.setbodybytes(_('do not know how to process %s\n') %
48 res.setbodybytes(_('do not know how to process %s\n') %
49 req.dispatchpath)
49 req.dispatchpath)
50 return
50 return
51
51
52 permission, command = urlparts[0:2]
52 permission, command = urlparts[0:2]
53
53
54 if permission not in (b'ro', b'rw'):
54 if permission not in (b'ro', b'rw'):
55 res.status = b'404 Not Found'
55 res.status = b'404 Not Found'
56 res.headers[b'Content-Type'] = b'text/plain'
56 res.headers[b'Content-Type'] = b'text/plain'
57 res.setbodybytes(_('unknown permission: %s') % permission)
57 res.setbodybytes(_('unknown permission: %s') % permission)
58 return
58 return
59
59
60 if req.method != 'POST':
60 if req.method != 'POST':
61 res.status = b'405 Method Not Allowed'
61 res.status = b'405 Method Not Allowed'
62 res.headers[b'Allow'] = b'POST'
62 res.headers[b'Allow'] = b'POST'
63 res.setbodybytes(_('commands require POST requests'))
63 res.setbodybytes(_('commands require POST requests'))
64 return
64 return
65
65
66 # At some point we'll want to use our own API instead of recycling the
66 # At some point we'll want to use our own API instead of recycling the
67 # behavior of version 1 of the wire protocol...
67 # behavior of version 1 of the wire protocol...
68 # TODO return reasonable responses - not responses that overload the
68 # TODO return reasonable responses - not responses that overload the
69 # HTTP status line message for error reporting.
69 # HTTP status line message for error reporting.
70 try:
70 try:
71 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
71 checkperm(rctx, req, 'pull' if permission == b'ro' else 'push')
72 except hgwebcommon.ErrorResponse as e:
72 except hgwebcommon.ErrorResponse as e:
73 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
73 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
74 for k, v in e.headers:
74 for k, v in e.headers:
75 res.headers[k] = v
75 res.headers[k] = v
76 res.setbodybytes('permission denied')
76 res.setbodybytes('permission denied')
77 return
77 return
78
78
79 # We have a special endpoint to reflect the request back at the client.
79 # We have a special endpoint to reflect the request back at the client.
80 if command == b'debugreflect':
80 if command == b'debugreflect':
81 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
81 _processhttpv2reflectrequest(rctx.repo.ui, rctx.repo, req, res)
82 return
82 return
83
83
84 # Extra commands that we handle that aren't really wire protocol
84 # Extra commands that we handle that aren't really wire protocol
85 # commands. Think extra hard before making this hackery available to
85 # commands. Think extra hard before making this hackery available to
86 # extension.
86 # extension.
87 extracommands = {'multirequest'}
87 extracommands = {'multirequest'}
88
88
89 if command not in wireproto.commandsv2 and command not in extracommands:
89 if command not in wireproto.commandsv2 and command not in extracommands:
90 res.status = b'404 Not Found'
90 res.status = b'404 Not Found'
91 res.headers[b'Content-Type'] = b'text/plain'
91 res.headers[b'Content-Type'] = b'text/plain'
92 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
92 res.setbodybytes(_('unknown wire protocol command: %s\n') % command)
93 return
93 return
94
94
95 repo = rctx.repo
95 repo = rctx.repo
96 ui = repo.ui
96 ui = repo.ui
97
97
98 proto = httpv2protocolhandler(req, ui)
98 proto = httpv2protocolhandler(req, ui)
99
99
100 if (not wireproto.commandsv2.commandavailable(command, proto)
100 if (not wireproto.commandsv2.commandavailable(command, proto)
101 and command not in extracommands):
101 and command not in extracommands):
102 res.status = b'404 Not Found'
102 res.status = b'404 Not Found'
103 res.headers[b'Content-Type'] = b'text/plain'
103 res.headers[b'Content-Type'] = b'text/plain'
104 res.setbodybytes(_('invalid wire protocol command: %s') % command)
104 res.setbodybytes(_('invalid wire protocol command: %s') % command)
105 return
105 return
106
106
107 # TODO consider cases where proxies may add additional Accept headers.
107 # TODO consider cases where proxies may add additional Accept headers.
108 if req.headers.get(b'Accept') != FRAMINGTYPE:
108 if req.headers.get(b'Accept') != FRAMINGTYPE:
109 res.status = b'406 Not Acceptable'
109 res.status = b'406 Not Acceptable'
110 res.headers[b'Content-Type'] = b'text/plain'
110 res.headers[b'Content-Type'] = b'text/plain'
111 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
111 res.setbodybytes(_('client MUST specify Accept header with value: %s\n')
112 % FRAMINGTYPE)
112 % FRAMINGTYPE)
113 return
113 return
114
114
115 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
115 if req.headers.get(b'Content-Type') != FRAMINGTYPE:
116 res.status = b'415 Unsupported Media Type'
116 res.status = b'415 Unsupported Media Type'
117 # TODO we should send a response with appropriate media type,
117 # TODO we should send a response with appropriate media type,
118 # since client does Accept it.
118 # since client does Accept it.
119 res.headers[b'Content-Type'] = b'text/plain'
119 res.headers[b'Content-Type'] = b'text/plain'
120 res.setbodybytes(_('client MUST send Content-Type header with '
120 res.setbodybytes(_('client MUST send Content-Type header with '
121 'value: %s\n') % FRAMINGTYPE)
121 'value: %s\n') % FRAMINGTYPE)
122 return
122 return
123
123
124 _processhttpv2request(ui, repo, req, res, permission, command, proto)
124 _processhttpv2request(ui, repo, req, res, permission, command, proto)
125
125
126 def _processhttpv2reflectrequest(ui, repo, req, res):
126 def _processhttpv2reflectrequest(ui, repo, req, res):
127 """Reads unified frame protocol request and dumps out state to client.
127 """Reads unified frame protocol request and dumps out state to client.
128
128
129 This special endpoint can be used to help debug the wire protocol.
129 This special endpoint can be used to help debug the wire protocol.
130
130
131 Instead of routing the request through the normal dispatch mechanism,
131 Instead of routing the request through the normal dispatch mechanism,
132 we instead read all frames, decode them, and feed them into our state
132 we instead read all frames, decode them, and feed them into our state
133 tracker. We then dump the log of all that activity back out to the
133 tracker. We then dump the log of all that activity back out to the
134 client.
134 client.
135 """
135 """
136 import json
136 import json
137
137
138 # Reflection APIs have a history of being abused, accidentally disclosing
138 # Reflection APIs have a history of being abused, accidentally disclosing
139 # sensitive data, etc. So we have a config knob.
139 # sensitive data, etc. So we have a config knob.
140 if not ui.configbool('experimental', 'web.api.debugreflect'):
140 if not ui.configbool('experimental', 'web.api.debugreflect'):
141 res.status = b'404 Not Found'
141 res.status = b'404 Not Found'
142 res.headers[b'Content-Type'] = b'text/plain'
142 res.headers[b'Content-Type'] = b'text/plain'
143 res.setbodybytes(_('debugreflect service not available'))
143 res.setbodybytes(_('debugreflect service not available'))
144 return
144 return
145
145
146 # We assume we have a unified framing protocol request body.
146 # We assume we have a unified framing protocol request body.
147
147
148 reactor = wireprotoframing.serverreactor()
148 reactor = wireprotoframing.serverreactor()
149 states = []
149 states = []
150
150
151 while True:
151 while True:
152 frame = wireprotoframing.readframe(req.bodyfh)
152 frame = wireprotoframing.readframe(req.bodyfh)
153
153
154 if not frame:
154 if not frame:
155 states.append(b'received: <no frame>')
155 states.append(b'received: <no frame>')
156 break
156 break
157
157
158 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
158 states.append(b'received: %d %d %d %s' % (frame.typeid, frame.flags,
159 frame.requestid,
159 frame.requestid,
160 frame.payload))
160 frame.payload))
161
161
162 action, meta = reactor.onframerecv(frame)
162 action, meta = reactor.onframerecv(frame)
163 states.append(json.dumps((action, meta), sort_keys=True,
163 states.append(json.dumps((action, meta), sort_keys=True,
164 separators=(', ', ': ')))
164 separators=(', ', ': ')))
165
165
166 action, meta = reactor.oninputeof()
166 action, meta = reactor.oninputeof()
167 meta['action'] = action
167 meta['action'] = action
168 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
168 states.append(json.dumps(meta, sort_keys=True, separators=(', ',': ')))
169
169
170 res.status = b'200 OK'
170 res.status = b'200 OK'
171 res.headers[b'Content-Type'] = b'text/plain'
171 res.headers[b'Content-Type'] = b'text/plain'
172 res.setbodybytes(b'\n'.join(states))
172 res.setbodybytes(b'\n'.join(states))
173
173
174 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
174 def _processhttpv2request(ui, repo, req, res, authedperm, reqcommand, proto):
175 """Post-validation handler for HTTPv2 requests.
175 """Post-validation handler for HTTPv2 requests.
176
176
177 Called when the HTTP request contains unified frame-based protocol
177 Called when the HTTP request contains unified frame-based protocol
178 frames for evaluation.
178 frames for evaluation.
179 """
179 """
180 # TODO Some HTTP clients are full duplex and can receive data before
180 # TODO Some HTTP clients are full duplex and can receive data before
181 # the entire request is transmitted. Figure out a way to indicate support
181 # the entire request is transmitted. Figure out a way to indicate support
182 # for that so we can opt into full duplex mode.
182 # for that so we can opt into full duplex mode.
183 reactor = wireprotoframing.serverreactor(deferoutput=True)
183 reactor = wireprotoframing.serverreactor(deferoutput=True)
184 seencommand = False
184 seencommand = False
185
185
186 outstream = reactor.makeoutputstream()
186 outstream = reactor.makeoutputstream()
187
187
188 while True:
188 while True:
189 frame = wireprotoframing.readframe(req.bodyfh)
189 frame = wireprotoframing.readframe(req.bodyfh)
190 if not frame:
190 if not frame:
191 break
191 break
192
192
193 action, meta = reactor.onframerecv(frame)
193 action, meta = reactor.onframerecv(frame)
194
194
195 if action == 'wantframe':
195 if action == 'wantframe':
196 # Need more data before we can do anything.
196 # Need more data before we can do anything.
197 continue
197 continue
198 elif action == 'runcommand':
198 elif action == 'runcommand':
199 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
199 sentoutput = _httpv2runcommand(ui, repo, req, res, authedperm,
200 reqcommand, reactor, outstream,
200 reqcommand, reactor, outstream,
201 meta, issubsequent=seencommand)
201 meta, issubsequent=seencommand)
202
202
203 if sentoutput:
203 if sentoutput:
204 return
204 return
205
205
206 seencommand = True
206 seencommand = True
207
207
208 elif action == 'error':
208 elif action == 'error':
209 # TODO define proper error mechanism.
209 # TODO define proper error mechanism.
210 res.status = b'200 OK'
210 res.status = b'200 OK'
211 res.headers[b'Content-Type'] = b'text/plain'
211 res.headers[b'Content-Type'] = b'text/plain'
212 res.setbodybytes(meta['message'] + b'\n')
212 res.setbodybytes(meta['message'] + b'\n')
213 return
213 return
214 else:
214 else:
215 raise error.ProgrammingError(
215 raise error.ProgrammingError(
216 'unhandled action from frame processor: %s' % action)
216 'unhandled action from frame processor: %s' % action)
217
217
218 action, meta = reactor.oninputeof()
218 action, meta = reactor.oninputeof()
219 if action == 'sendframes':
219 if action == 'sendframes':
220 # We assume we haven't started sending the response yet. If we're
220 # We assume we haven't started sending the response yet. If we're
221 # wrong, the response type will raise an exception.
221 # wrong, the response type will raise an exception.
222 res.status = b'200 OK'
222 res.status = b'200 OK'
223 res.headers[b'Content-Type'] = FRAMINGTYPE
223 res.headers[b'Content-Type'] = FRAMINGTYPE
224 res.setbodygen(meta['framegen'])
224 res.setbodygen(meta['framegen'])
225 elif action == 'noop':
225 elif action == 'noop':
226 pass
226 pass
227 else:
227 else:
228 raise error.ProgrammingError('unhandled action from frame processor: %s'
228 raise error.ProgrammingError('unhandled action from frame processor: %s'
229 % action)
229 % action)
230
230
231 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
231 def _httpv2runcommand(ui, repo, req, res, authedperm, reqcommand, reactor,
232 outstream, command, issubsequent):
232 outstream, command, issubsequent):
233 """Dispatch a wire protocol command made from HTTPv2 requests.
233 """Dispatch a wire protocol command made from HTTPv2 requests.
234
234
235 The authenticated permission (``authedperm``) along with the original
235 The authenticated permission (``authedperm``) along with the original
236 command from the URL (``reqcommand``) are passed in.
236 command from the URL (``reqcommand``) are passed in.
237 """
237 """
238 # We already validated that the session has permissions to perform the
238 # We already validated that the session has permissions to perform the
239 # actions in ``authedperm``. In the unified frame protocol, the canonical
239 # actions in ``authedperm``. In the unified frame protocol, the canonical
240 # command to run is expressed in a frame. However, the URL also requested
240 # command to run is expressed in a frame. However, the URL also requested
241 # to run a specific command. We need to be careful that the command we
241 # to run a specific command. We need to be careful that the command we
242 # run doesn't have permissions requirements greater than what was granted
242 # run doesn't have permissions requirements greater than what was granted
243 # by ``authedperm``.
243 # by ``authedperm``.
244 #
244 #
245 # Our rule for this is we only allow one command per HTTP request and
245 # Our rule for this is we only allow one command per HTTP request and
246 # that command must match the command in the URL. However, we make
246 # that command must match the command in the URL. However, we make
247 # an exception for the ``multirequest`` URL. This URL is allowed to
247 # an exception for the ``multirequest`` URL. This URL is allowed to
248 # execute multiple commands. We double check permissions of each command
248 # execute multiple commands. We double check permissions of each command
249 # as it is invoked to ensure there is no privilege escalation.
249 # as it is invoked to ensure there is no privilege escalation.
250 # TODO consider allowing multiple commands to regular command URLs
250 # TODO consider allowing multiple commands to regular command URLs
251 # iff each command is the same.
251 # iff each command is the same.
252
252
253 proto = httpv2protocolhandler(req, ui, args=command['args'])
253 proto = httpv2protocolhandler(req, ui, args=command['args'])
254
254
255 if reqcommand == b'multirequest':
255 if reqcommand == b'multirequest':
256 if not wireproto.commandsv2.commandavailable(command['command'], proto):
256 if not wireproto.commandsv2.commandavailable(command['command'], proto):
257 # TODO proper error mechanism
257 # TODO proper error mechanism
258 res.status = b'200 OK'
258 res.status = b'200 OK'
259 res.headers[b'Content-Type'] = b'text/plain'
259 res.headers[b'Content-Type'] = b'text/plain'
260 res.setbodybytes(_('wire protocol command not available: %s') %
260 res.setbodybytes(_('wire protocol command not available: %s') %
261 command['command'])
261 command['command'])
262 return True
262 return True
263
263
264 # TODO don't use assert here, since it may be elided by -O.
264 # TODO don't use assert here, since it may be elided by -O.
265 assert authedperm in (b'ro', b'rw')
265 assert authedperm in (b'ro', b'rw')
266 wirecommand = wireproto.commandsv2[command['command']]
266 wirecommand = wireproto.commandsv2[command['command']]
267 assert wirecommand.permission in ('push', 'pull')
267 assert wirecommand.permission in ('push', 'pull')
268
268
269 if authedperm == b'ro' and wirecommand.permission != 'pull':
269 if authedperm == b'ro' and wirecommand.permission != 'pull':
270 # TODO proper error mechanism
270 # TODO proper error mechanism
271 res.status = b'403 Forbidden'
271 res.status = b'403 Forbidden'
272 res.headers[b'Content-Type'] = b'text/plain'
272 res.headers[b'Content-Type'] = b'text/plain'
273 res.setbodybytes(_('insufficient permissions to execute '
273 res.setbodybytes(_('insufficient permissions to execute '
274 'command: %s') % command['command'])
274 'command: %s') % command['command'])
275 return True
275 return True
276
276
277 # TODO should we also call checkperm() here? Maybe not if we're going
277 # TODO should we also call checkperm() here? Maybe not if we're going
278 # to overhaul that API. The granted scope from the URL check should
278 # to overhaul that API. The granted scope from the URL check should
279 # be good enough.
279 # be good enough.
280
280
281 else:
281 else:
282 # Don't allow multiple commands outside of ``multirequest`` URL.
282 # Don't allow multiple commands outside of ``multirequest`` URL.
283 if issubsequent:
283 if issubsequent:
284 # TODO proper error mechanism
284 # TODO proper error mechanism
285 res.status = b'200 OK'
285 res.status = b'200 OK'
286 res.headers[b'Content-Type'] = b'text/plain'
286 res.headers[b'Content-Type'] = b'text/plain'
287 res.setbodybytes(_('multiple commands cannot be issued to this '
287 res.setbodybytes(_('multiple commands cannot be issued to this '
288 'URL'))
288 'URL'))
289 return True
289 return True
290
290
291 if reqcommand != command['command']:
291 if reqcommand != command['command']:
292 # TODO define proper error mechanism
292 # TODO define proper error mechanism
293 res.status = b'200 OK'
293 res.status = b'200 OK'
294 res.headers[b'Content-Type'] = b'text/plain'
294 res.headers[b'Content-Type'] = b'text/plain'
295 res.setbodybytes(_('command in frame must match command in URL'))
295 res.setbodybytes(_('command in frame must match command in URL'))
296 return True
296 return True
297
297
298 rsp = wireproto.dispatch(repo, proto, command['command'])
298 rsp = wireproto.dispatch(repo, proto, command['command'])
299
299
300 res.status = b'200 OK'
300 res.status = b'200 OK'
301 res.headers[b'Content-Type'] = FRAMINGTYPE
301 res.headers[b'Content-Type'] = FRAMINGTYPE
302
302
303 if isinstance(rsp, wireprototypes.bytesresponse):
303 if isinstance(rsp, wireprototypes.bytesresponse):
304 action, meta = reactor.onbytesresponseready(outstream,
304 action, meta = reactor.onbytesresponseready(outstream,
305 command['requestid'],
305 command['requestid'],
306 rsp.data)
306 rsp.data)
307 elif isinstance(rsp, wireprototypes.cborresponse):
307 elif isinstance(rsp, wireprototypes.cborresponse):
308 encoded = cbor.dumps(rsp.value, canonical=True)
308 encoded = cbor.dumps(rsp.value, canonical=True)
309 action, meta = reactor.onbytesresponseready(outstream,
309 action, meta = reactor.onbytesresponseready(outstream,
310 command['requestid'],
310 command['requestid'],
311 encoded,
311 encoded,
312 iscbor=True)
312 iscbor=True)
313 else:
313 else:
314 action, meta = reactor.onapplicationerror(
314 action, meta = reactor.onapplicationerror(
315 _('unhandled response type from wire proto command'))
315 _('unhandled response type from wire proto command'))
316
316
317 if action == 'sendframes':
317 if action == 'sendframes':
318 res.setbodygen(meta['framegen'])
318 res.setbodygen(meta['framegen'])
319 return True
319 return True
320 elif action == 'noop':
320 elif action == 'noop':
321 return False
321 return False
322 else:
322 else:
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
323 raise error.ProgrammingError('unhandled event from reactor: %s' %
324 action)
324 action)
325
325
326 @zi.implementer(wireprototypes.baseprotocolhandler)
326 @zi.implementer(wireprototypes.baseprotocolhandler)
327 class httpv2protocolhandler(object):
327 class httpv2protocolhandler(object):
328 def __init__(self, req, ui, args=None):
328 def __init__(self, req, ui, args=None):
329 self._req = req
329 self._req = req
330 self._ui = ui
330 self._ui = ui
331 self._args = args
331 self._args = args
332
332
333 @property
333 @property
334 def name(self):
334 def name(self):
335 return HTTPV2
335 return HTTP_WIREPROTO_V2
336
336
337 def getargs(self, args):
337 def getargs(self, args):
338 data = {}
338 data = {}
339 for k, typ in args.items():
339 for k, typ in args.items():
340 if k == '*':
340 if k == '*':
341 raise NotImplementedError('do not support * args')
341 raise NotImplementedError('do not support * args')
342 elif k in self._args:
342 elif k in self._args:
343 # TODO consider validating value types.
343 # TODO consider validating value types.
344 data[k] = self._args[k]
344 data[k] = self._args[k]
345
345
346 return data
346 return data
347
347
348 def getprotocaps(self):
348 def getprotocaps(self):
349 # Protocol capabilities are currently not implemented for HTTP V2.
349 # Protocol capabilities are currently not implemented for HTTP V2.
350 return set()
350 return set()
351
351
352 def getpayload(self):
352 def getpayload(self):
353 raise NotImplementedError
353 raise NotImplementedError
354
354
355 @contextlib.contextmanager
355 @contextlib.contextmanager
356 def mayberedirectstdio(self):
356 def mayberedirectstdio(self):
357 raise NotImplementedError
357 raise NotImplementedError
358
358
359 def client(self):
359 def client(self):
360 raise NotImplementedError
360 raise NotImplementedError
361
361
362 def addcapabilities(self, repo, caps):
362 def addcapabilities(self, repo, caps):
363 return caps
363 return caps
364
364
365 def checkperm(self, perm):
365 def checkperm(self, perm):
366 raise NotImplementedError
366 raise NotImplementedError
367
367
368 def httpv2apidescriptor(req, repo):
368 def httpv2apidescriptor(req, repo):
369 proto = httpv2protocolhandler(req, repo.ui)
369 proto = httpv2protocolhandler(req, repo.ui)
370
370
371 return _capabilitiesv2(repo, proto)
371 return _capabilitiesv2(repo, proto)
372
372
373 def _capabilitiesv2(repo, proto):
373 def _capabilitiesv2(repo, proto):
374 """Obtain the set of capabilities for version 2 transports.
374 """Obtain the set of capabilities for version 2 transports.
375
375
376 These capabilities are distinct from the capabilities for version 1
376 These capabilities are distinct from the capabilities for version 1
377 transports.
377 transports.
378 """
378 """
379 compression = []
379 compression = []
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
380 for engine in wireproto.supportedcompengines(repo.ui, util.SERVERROLE):
381 compression.append({
381 compression.append({
382 b'name': engine.wireprotosupport().name,
382 b'name': engine.wireprotosupport().name,
383 })
383 })
384
384
385 caps = {
385 caps = {
386 'commands': {},
386 'commands': {},
387 'compression': compression,
387 'compression': compression,
388 }
388 }
389
389
390 for command, entry in wireproto.commandsv2.items():
390 for command, entry in wireproto.commandsv2.items():
391 caps['commands'][command] = {
391 caps['commands'][command] = {
392 'args': entry.args,
392 'args': entry.args,
393 'permissions': [entry.permission],
393 'permissions': [entry.permission],
394 }
394 }
395
395
396 return proto.addcapabilities(repo, caps)
396 return proto.addcapabilities(repo, caps)
397
397
398 def wireprotocommand(*args, **kwargs):
398 def wireprotocommand(*args, **kwargs):
399 def register(func):
399 def register(func):
400 return wireproto.wireprotocommand(
400 return wireproto.wireprotocommand(
401 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
401 *args, transportpolicy=wireproto.POLICY_V2_ONLY, **kwargs)(func)
402
402
403 return register
403 return register
404
404
405 @wireprotocommand('branchmap', permission='pull')
405 @wireprotocommand('branchmap', permission='pull')
406 def branchmapv2(repo, proto):
406 def branchmapv2(repo, proto):
407 branchmap = {encoding.fromlocal(k): v
407 branchmap = {encoding.fromlocal(k): v
408 for k, v in repo.branchmap().iteritems()}
408 for k, v in repo.branchmap().iteritems()}
409
409
410 return wireprototypes.cborresponse(branchmap)
410 return wireprototypes.cborresponse(branchmap)
411
411
412 @wireprotocommand('capabilities', permission='pull')
412 @wireprotocommand('capabilities', permission='pull')
413 def capabilitiesv2(repo, proto):
413 def capabilitiesv2(repo, proto):
414 caps = _capabilitiesv2(repo, proto)
414 caps = _capabilitiesv2(repo, proto)
415
415
416 return wireprototypes.cborresponse(caps)
416 return wireprototypes.cborresponse(caps)
417
417
418 @wireprotocommand('heads',
418 @wireprotocommand('heads',
419 args={
419 args={
420 'publiconly': False,
420 'publiconly': False,
421 },
421 },
422 permission='pull')
422 permission='pull')
423 def headsv2(repo, proto, publiconly=False):
423 def headsv2(repo, proto, publiconly=False):
424 if publiconly:
424 if publiconly:
425 repo = repo.filtered('immutable')
425 repo = repo.filtered('immutable')
426
426
427 return wireprototypes.cborresponse(repo.heads())
427 return wireprototypes.cborresponse(repo.heads())
428
428
429 @wireprotocommand('known',
429 @wireprotocommand('known',
430 args={
430 args={
431 'nodes': [b'deadbeef'],
431 'nodes': [b'deadbeef'],
432 },
432 },
433 permission='pull')
433 permission='pull')
434 def knownv2(repo, proto, nodes=None):
434 def knownv2(repo, proto, nodes=None):
435 nodes = nodes or []
435 nodes = nodes or []
436 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
436 result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
437 return wireprototypes.cborresponse(result)
437 return wireprototypes.cborresponse(result)
438
438
439 @wireprotocommand('listkeys',
439 @wireprotocommand('listkeys',
440 args={
440 args={
441 'namespace': b'ns',
441 'namespace': b'ns',
442 },
442 },
443 permission='pull')
443 permission='pull')
444 def listkeysv2(repo, proto, namespace=None):
444 def listkeysv2(repo, proto, namespace=None):
445 keys = repo.listkeys(encoding.tolocal(namespace))
445 keys = repo.listkeys(encoding.tolocal(namespace))
446 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
446 keys = {encoding.fromlocal(k): encoding.fromlocal(v)
447 for k, v in keys.iteritems()}
447 for k, v in keys.iteritems()}
448
448
449 return wireprototypes.cborresponse(keys)
449 return wireprototypes.cborresponse(keys)
450
450
451 @wireprotocommand('lookup',
451 @wireprotocommand('lookup',
452 args={
452 args={
453 'key': b'foo',
453 'key': b'foo',
454 },
454 },
455 permission='pull')
455 permission='pull')
456 def lookupv2(repo, proto, key):
456 def lookupv2(repo, proto, key):
457 key = encoding.tolocal(key)
457 key = encoding.tolocal(key)
458
458
459 # TODO handle exception.
459 # TODO handle exception.
460 node = repo.lookup(key)
460 node = repo.lookup(key)
461
461
462 return wireprototypes.cborresponse(node)
462 return wireprototypes.cborresponse(node)
463
463
464 @wireprotocommand('pushkey',
464 @wireprotocommand('pushkey',
465 args={
465 args={
466 'namespace': b'ns',
466 'namespace': b'ns',
467 'key': b'key',
467 'key': b'key',
468 'old': b'old',
468 'old': b'old',
469 'new': b'new',
469 'new': b'new',
470 },
470 },
471 permission='push')
471 permission='push')
472 def pushkeyv2(repo, proto, namespace, key, old, new):
472 def pushkeyv2(repo, proto, namespace, key, old, new):
473 # TODO handle ui output redirection
473 # TODO handle ui output redirection
474 r = repo.pushkey(encoding.tolocal(namespace),
474 r = repo.pushkey(encoding.tolocal(namespace),
475 encoding.tolocal(key),
475 encoding.tolocal(key),
476 encoding.tolocal(old),
476 encoding.tolocal(old),
477 encoding.tolocal(new))
477 encoding.tolocal(new))
478
478
479 return wireprototypes.cborresponse(r)
479 return wireprototypes.cborresponse(r)
General Comments 0
You need to be logged in to leave comments. Login now