##// END OF EJS Templates
wireproto: support /api/* URL space for exposing APIs...
Gregory Szorc -
r37064:1cfef569 default
parent child Browse files
Show More
@@ -0,0 +1,65 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ cat > server/.hg/hgrc << EOF
7 > [experimental]
8 > web.apiserver = true
9 > EOF
10 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ cat hg.pid > $DAEMON_PIDS
12
13 HTTP v2 protocol not enabled by default
14
15 $ send << EOF
16 > httprequest GET api/exp-http-v2-0001
17 > user-agent: test
18 > EOF
19 using raw connection to peer
20 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> Accept-Encoding: identity\r\n
22 s> user-agent: test\r\n
23 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> \r\n
25 s> makefile('rb', None)
26 s> HTTP/1.1 404 Not Found\r\n
27 s> Server: testing stub value\r\n
28 s> Date: $HTTP_DATE$\r\n
29 s> Content-Type: text/plain\r\n
30 s> Content-Length: 33\r\n
31 s> \r\n
32 s> API exp-http-v2-0001 not enabled\n
33
34 Restart server with support for HTTP v2 API
35
36 $ killdaemons.py
37 $ cat > server/.hg/hgrc << EOF
38 > [experimental]
39 > web.apiserver = true
40 > web.api.http-v2 = true
41 > EOF
42
43 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
44 $ cat hg.pid > $DAEMON_PIDS
45
46 Requests simply echo their path (for now)
47
48 $ send << EOF
49 > httprequest GET api/exp-http-v2-0001/path1/path2
50 > user-agent: test
51 > EOF
52 using raw connection to peer
53 s> GET /api/exp-http-v2-0001/path1/path2 HTTP/1.1\r\n
54 s> Accept-Encoding: identity\r\n
55 s> user-agent: test\r\n
56 s> host: $LOCALIP:$HGPORT\r\n (glob)
57 s> \r\n
58 s> makefile('rb', None)
59 s> HTTP/1.1 200 OK\r\n
60 s> Server: testing stub value\r\n
61 s> Date: $HTTP_DATE$\r\n
62 s> Content-Type: text/plain\r\n
63 s> Content-Length: 12\r\n
64 s> \r\n
65 s> path1/path2\n
@@ -0,0 +1,201 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
7 $ cat hg.pid > $DAEMON_PIDS
8
9 Request to /api fails unless web.apiserver is enabled
10
11 $ send << EOF
12 > httprequest GET api
13 > user-agent: test
14 > EOF
15 using raw connection to peer
16 s> GET /api HTTP/1.1\r\n
17 s> Accept-Encoding: identity\r\n
18 s> user-agent: test\r\n
19 s> host: $LOCALIP:$HGPORT\r\n (glob)
20 s> \r\n
21 s> makefile('rb', None)
22 s> HTTP/1.1 404 Not Found\r\n
23 s> Server: testing stub value\r\n
24 s> Date: $HTTP_DATE$\r\n
25 s> Content-Type: text/plain\r\n
26 s> Content-Length: 44\r\n
27 s> \r\n
28 s> Experimental API server endpoint not enabled
29
30 $ send << EOF
31 > httprequest GET api/
32 > user-agent: test
33 > EOF
34 using raw connection to peer
35 s> GET /api/ HTTP/1.1\r\n
36 s> Accept-Encoding: identity\r\n
37 s> user-agent: test\r\n
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> \r\n
40 s> makefile('rb', None)
41 s> HTTP/1.1 404 Not Found\r\n
42 s> Server: testing stub value\r\n
43 s> Date: $HTTP_DATE$\r\n
44 s> Content-Type: text/plain\r\n
45 s> Content-Length: 44\r\n
46 s> \r\n
47 s> Experimental API server endpoint not enabled
48
49 Restart server with support for API server
50
51 $ killdaemons.py
52 $ cat > server/.hg/hgrc << EOF
53 > [experimental]
54 > web.apiserver = true
55 > EOF
56
57 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
58 $ cat hg.pid > $DAEMON_PIDS
59
60 /api lists available APIs (empty since none are available by default)
61
62 $ send << EOF
63 > httprequest GET api
64 > user-agent: test
65 > EOF
66 using raw connection to peer
67 s> GET /api HTTP/1.1\r\n
68 s> Accept-Encoding: identity\r\n
69 s> user-agent: test\r\n
70 s> host: $LOCALIP:$HGPORT\r\n (glob)
71 s> \r\n
72 s> makefile('rb', None)
73 s> HTTP/1.1 200 OK\r\n
74 s> Server: testing stub value\r\n
75 s> Date: $HTTP_DATE$\r\n
76 s> Content-Type: text/plain\r\n
77 s> Content-Length: 100\r\n
78 s> \r\n
79 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
80 s> \n
81 s> (no available APIs)\n
82
83 $ send << EOF
84 > httprequest GET api/
85 > user-agent: test
86 > EOF
87 using raw connection to peer
88 s> GET /api/ HTTP/1.1\r\n
89 s> Accept-Encoding: identity\r\n
90 s> user-agent: test\r\n
91 s> host: $LOCALIP:$HGPORT\r\n (glob)
92 s> \r\n
93 s> makefile('rb', None)
94 s> HTTP/1.1 200 OK\r\n
95 s> Server: testing stub value\r\n
96 s> Date: $HTTP_DATE$\r\n
97 s> Content-Type: text/plain\r\n
98 s> Content-Length: 100\r\n
99 s> \r\n
100 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
101 s> \n
102 s> (no available APIs)\n
103
104 Accessing an unknown API yields a 404
105
106 $ send << EOF
107 > httprequest GET api/unknown
108 > user-agent: test
109 > EOF
110 using raw connection to peer
111 s> GET /api/unknown HTTP/1.1\r\n
112 s> Accept-Encoding: identity\r\n
113 s> user-agent: test\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
116 s> makefile('rb', None)
117 s> HTTP/1.1 404 Not Found\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: text/plain\r\n
121 s> Content-Length: 33\r\n
122 s> \r\n
123 s> Unknown API: unknown\n
124 s> Known APIs:
125
126 Accessing a known but not enabled API yields a different error
127
128 $ send << EOF
129 > httprequest GET api/exp-http-v2-0001
130 > user-agent: test
131 > EOF
132 using raw connection to peer
133 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
134 s> Accept-Encoding: identity\r\n
135 s> user-agent: test\r\n
136 s> host: $LOCALIP:$HGPORT\r\n (glob)
137 s> \r\n
138 s> makefile('rb', None)
139 s> HTTP/1.1 404 Not Found\r\n
140 s> Server: testing stub value\r\n
141 s> Date: $HTTP_DATE$\r\n
142 s> Content-Type: text/plain\r\n
143 s> Content-Length: 33\r\n
144 s> \r\n
145 s> API exp-http-v2-0001 not enabled\n
146
147 Restart server with support for HTTP v2 API
148
149 $ killdaemons.py
150 $ cat > server/.hg/hgrc << EOF
151 > [experimental]
152 > web.apiserver = true
153 > web.api.http-v2 = true
154 > EOF
155
156 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
157 $ cat hg.pid > $DAEMON_PIDS
158
159 /api lists the HTTP v2 protocol as available
160
161 $ send << EOF
162 > httprequest GET api
163 > user-agent: test
164 > EOF
165 using raw connection to peer
166 s> GET /api HTTP/1.1\r\n
167 s> Accept-Encoding: identity\r\n
168 s> user-agent: test\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
171 s> makefile('rb', None)
172 s> HTTP/1.1 200 OK\r\n
173 s> Server: testing stub value\r\n
174 s> Date: $HTTP_DATE$\r\n
175 s> Content-Type: text/plain\r\n
176 s> Content-Length: 96\r\n
177 s> \r\n
178 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
179 s> \n
180 s> exp-http-v2-0001
181
182 $ send << EOF
183 > httprequest GET api/
184 > user-agent: test
185 > EOF
186 using raw connection to peer
187 s> GET /api/ HTTP/1.1\r\n
188 s> Accept-Encoding: identity\r\n
189 s> user-agent: test\r\n
190 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 s> \r\n
192 s> makefile('rb', None)
193 s> HTTP/1.1 200 OK\r\n
194 s> Server: testing stub value\r\n
195 s> Date: $HTTP_DATE$\r\n
196 s> Content-Type: text/plain\r\n
197 s> Content-Length: 96\r\n
198 s> \r\n
199 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
200 s> \n
201 s> exp-http-v2-0001
@@ -1,1311 +1,1317 b''
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
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 functools
10 import functools
11 import re
11 import re
12
12
13 from . import (
13 from . import (
14 encoding,
14 encoding,
15 error,
15 error,
16 )
16 )
17
17
18 def loadconfigtable(ui, extname, configtable):
18 def loadconfigtable(ui, extname, configtable):
19 """update config item known to the ui with the extension ones"""
19 """update config item known to the ui with the extension ones"""
20 for section, items in sorted(configtable.items()):
20 for section, items in sorted(configtable.items()):
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 knownkeys = set(knownitems)
22 knownkeys = set(knownitems)
23 newkeys = set(items)
23 newkeys = set(items)
24 for key in sorted(knownkeys & newkeys):
24 for key in sorted(knownkeys & newkeys):
25 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg = "extension '%s' overwrite config item '%s.%s'"
26 msg %= (extname, section, key)
26 msg %= (extname, section, key)
27 ui.develwarn(msg, config='warn-config')
27 ui.develwarn(msg, config='warn-config')
28
28
29 knownitems.update(items)
29 knownitems.update(items)
30
30
31 class configitem(object):
31 class configitem(object):
32 """represent a known config item
32 """represent a known config item
33
33
34 :section: the official config section where to find this item,
34 :section: the official config section where to find this item,
35 :name: the official name within the section,
35 :name: the official name within the section,
36 :default: default value for this item,
36 :default: default value for this item,
37 :alias: optional list of tuples as alternatives,
37 :alias: optional list of tuples as alternatives,
38 :generic: this is a generic definition, match name using regular expression.
38 :generic: this is a generic definition, match name using regular expression.
39 """
39 """
40
40
41 def __init__(self, section, name, default=None, alias=(),
41 def __init__(self, section, name, default=None, alias=(),
42 generic=False, priority=0):
42 generic=False, priority=0):
43 self.section = section
43 self.section = section
44 self.name = name
44 self.name = name
45 self.default = default
45 self.default = default
46 self.alias = list(alias)
46 self.alias = list(alias)
47 self.generic = generic
47 self.generic = generic
48 self.priority = priority
48 self.priority = priority
49 self._re = None
49 self._re = None
50 if generic:
50 if generic:
51 self._re = re.compile(self.name)
51 self._re = re.compile(self.name)
52
52
53 class itemregister(dict):
53 class itemregister(dict):
54 """A specialized dictionary that can handle wild-card selection"""
54 """A specialized dictionary that can handle wild-card selection"""
55
55
56 def __init__(self):
56 def __init__(self):
57 super(itemregister, self).__init__()
57 super(itemregister, self).__init__()
58 self._generics = set()
58 self._generics = set()
59
59
60 def update(self, other):
60 def update(self, other):
61 super(itemregister, self).update(other)
61 super(itemregister, self).update(other)
62 self._generics.update(other._generics)
62 self._generics.update(other._generics)
63
63
64 def __setitem__(self, key, item):
64 def __setitem__(self, key, item):
65 super(itemregister, self).__setitem__(key, item)
65 super(itemregister, self).__setitem__(key, item)
66 if item.generic:
66 if item.generic:
67 self._generics.add(item)
67 self._generics.add(item)
68
68
69 def get(self, key):
69 def get(self, key):
70 baseitem = super(itemregister, self).get(key)
70 baseitem = super(itemregister, self).get(key)
71 if baseitem is not None and not baseitem.generic:
71 if baseitem is not None and not baseitem.generic:
72 return baseitem
72 return baseitem
73
73
74 # search for a matching generic item
74 # search for a matching generic item
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 for item in generics:
76 for item in generics:
77 # we use 'match' instead of 'search' to make the matching simpler
77 # we use 'match' instead of 'search' to make the matching simpler
78 # for people unfamiliar with regular expression. Having the match
78 # for people unfamiliar with regular expression. Having the match
79 # rooted to the start of the string will produce less surprising
79 # rooted to the start of the string will produce less surprising
80 # result for user writing simple regex for sub-attribute.
80 # result for user writing simple regex for sub-attribute.
81 #
81 #
82 # For example using "color\..*" match produces an unsurprising
82 # For example using "color\..*" match produces an unsurprising
83 # result, while using search could suddenly match apparently
83 # result, while using search could suddenly match apparently
84 # unrelated configuration that happens to contains "color."
84 # unrelated configuration that happens to contains "color."
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 # some match to avoid the need to prefix most pattern with "^".
86 # some match to avoid the need to prefix most pattern with "^".
87 # The "^" seems more error prone.
87 # The "^" seems more error prone.
88 if item._re.match(key):
88 if item._re.match(key):
89 return item
89 return item
90
90
91 return None
91 return None
92
92
93 coreitems = {}
93 coreitems = {}
94
94
95 def _register(configtable, *args, **kwargs):
95 def _register(configtable, *args, **kwargs):
96 item = configitem(*args, **kwargs)
96 item = configitem(*args, **kwargs)
97 section = configtable.setdefault(item.section, itemregister())
97 section = configtable.setdefault(item.section, itemregister())
98 if item.name in section:
98 if item.name in section:
99 msg = "duplicated config item registration for '%s.%s'"
99 msg = "duplicated config item registration for '%s.%s'"
100 raise error.ProgrammingError(msg % (item.section, item.name))
100 raise error.ProgrammingError(msg % (item.section, item.name))
101 section[item.name] = item
101 section[item.name] = item
102
102
103 # special value for case where the default is derived from other values
103 # special value for case where the default is derived from other values
104 dynamicdefault = object()
104 dynamicdefault = object()
105
105
106 # Registering actual config items
106 # Registering actual config items
107
107
108 def getitemregister(configtable):
108 def getitemregister(configtable):
109 f = functools.partial(_register, configtable)
109 f = functools.partial(_register, configtable)
110 # export pseudo enum as configitem.*
110 # export pseudo enum as configitem.*
111 f.dynamicdefault = dynamicdefault
111 f.dynamicdefault = dynamicdefault
112 return f
112 return f
113
113
114 coreconfigitem = getitemregister(coreitems)
114 coreconfigitem = getitemregister(coreitems)
115
115
116 coreconfigitem('alias', '.*',
116 coreconfigitem('alias', '.*',
117 default=None,
117 default=None,
118 generic=True,
118 generic=True,
119 )
119 )
120 coreconfigitem('annotate', 'nodates',
120 coreconfigitem('annotate', 'nodates',
121 default=False,
121 default=False,
122 )
122 )
123 coreconfigitem('annotate', 'showfunc',
123 coreconfigitem('annotate', 'showfunc',
124 default=False,
124 default=False,
125 )
125 )
126 coreconfigitem('annotate', 'unified',
126 coreconfigitem('annotate', 'unified',
127 default=None,
127 default=None,
128 )
128 )
129 coreconfigitem('annotate', 'git',
129 coreconfigitem('annotate', 'git',
130 default=False,
130 default=False,
131 )
131 )
132 coreconfigitem('annotate', 'ignorews',
132 coreconfigitem('annotate', 'ignorews',
133 default=False,
133 default=False,
134 )
134 )
135 coreconfigitem('annotate', 'ignorewsamount',
135 coreconfigitem('annotate', 'ignorewsamount',
136 default=False,
136 default=False,
137 )
137 )
138 coreconfigitem('annotate', 'ignoreblanklines',
138 coreconfigitem('annotate', 'ignoreblanklines',
139 default=False,
139 default=False,
140 )
140 )
141 coreconfigitem('annotate', 'ignorewseol',
141 coreconfigitem('annotate', 'ignorewseol',
142 default=False,
142 default=False,
143 )
143 )
144 coreconfigitem('annotate', 'nobinary',
144 coreconfigitem('annotate', 'nobinary',
145 default=False,
145 default=False,
146 )
146 )
147 coreconfigitem('annotate', 'noprefix',
147 coreconfigitem('annotate', 'noprefix',
148 default=False,
148 default=False,
149 )
149 )
150 coreconfigitem('auth', 'cookiefile',
150 coreconfigitem('auth', 'cookiefile',
151 default=None,
151 default=None,
152 )
152 )
153 # bookmarks.pushing: internal hack for discovery
153 # bookmarks.pushing: internal hack for discovery
154 coreconfigitem('bookmarks', 'pushing',
154 coreconfigitem('bookmarks', 'pushing',
155 default=list,
155 default=list,
156 )
156 )
157 # bundle.mainreporoot: internal hack for bundlerepo
157 # bundle.mainreporoot: internal hack for bundlerepo
158 coreconfigitem('bundle', 'mainreporoot',
158 coreconfigitem('bundle', 'mainreporoot',
159 default='',
159 default='',
160 )
160 )
161 # bundle.reorder: experimental config
161 # bundle.reorder: experimental config
162 coreconfigitem('bundle', 'reorder',
162 coreconfigitem('bundle', 'reorder',
163 default='auto',
163 default='auto',
164 )
164 )
165 coreconfigitem('censor', 'policy',
165 coreconfigitem('censor', 'policy',
166 default='abort',
166 default='abort',
167 )
167 )
168 coreconfigitem('chgserver', 'idletimeout',
168 coreconfigitem('chgserver', 'idletimeout',
169 default=3600,
169 default=3600,
170 )
170 )
171 coreconfigitem('chgserver', 'skiphash',
171 coreconfigitem('chgserver', 'skiphash',
172 default=False,
172 default=False,
173 )
173 )
174 coreconfigitem('cmdserver', 'log',
174 coreconfigitem('cmdserver', 'log',
175 default=None,
175 default=None,
176 )
176 )
177 coreconfigitem('color', '.*',
177 coreconfigitem('color', '.*',
178 default=None,
178 default=None,
179 generic=True,
179 generic=True,
180 )
180 )
181 coreconfigitem('color', 'mode',
181 coreconfigitem('color', 'mode',
182 default='auto',
182 default='auto',
183 )
183 )
184 coreconfigitem('color', 'pagermode',
184 coreconfigitem('color', 'pagermode',
185 default=dynamicdefault,
185 default=dynamicdefault,
186 )
186 )
187 coreconfigitem('commands', 'show.aliasprefix',
187 coreconfigitem('commands', 'show.aliasprefix',
188 default=list,
188 default=list,
189 )
189 )
190 coreconfigitem('commands', 'status.relative',
190 coreconfigitem('commands', 'status.relative',
191 default=False,
191 default=False,
192 )
192 )
193 coreconfigitem('commands', 'status.skipstates',
193 coreconfigitem('commands', 'status.skipstates',
194 default=[],
194 default=[],
195 )
195 )
196 coreconfigitem('commands', 'status.verbose',
196 coreconfigitem('commands', 'status.verbose',
197 default=False,
197 default=False,
198 )
198 )
199 coreconfigitem('commands', 'update.check',
199 coreconfigitem('commands', 'update.check',
200 default=None,
200 default=None,
201 # Deprecated, remove after 4.4 release
201 # Deprecated, remove after 4.4 release
202 alias=[('experimental', 'updatecheck')]
202 alias=[('experimental', 'updatecheck')]
203 )
203 )
204 coreconfigitem('commands', 'update.requiredest',
204 coreconfigitem('commands', 'update.requiredest',
205 default=False,
205 default=False,
206 )
206 )
207 coreconfigitem('committemplate', '.*',
207 coreconfigitem('committemplate', '.*',
208 default=None,
208 default=None,
209 generic=True,
209 generic=True,
210 )
210 )
211 coreconfigitem('convert', 'cvsps.cache',
211 coreconfigitem('convert', 'cvsps.cache',
212 default=True,
212 default=True,
213 )
213 )
214 coreconfigitem('convert', 'cvsps.fuzz',
214 coreconfigitem('convert', 'cvsps.fuzz',
215 default=60,
215 default=60,
216 )
216 )
217 coreconfigitem('convert', 'cvsps.logencoding',
217 coreconfigitem('convert', 'cvsps.logencoding',
218 default=None,
218 default=None,
219 )
219 )
220 coreconfigitem('convert', 'cvsps.mergefrom',
220 coreconfigitem('convert', 'cvsps.mergefrom',
221 default=None,
221 default=None,
222 )
222 )
223 coreconfigitem('convert', 'cvsps.mergeto',
223 coreconfigitem('convert', 'cvsps.mergeto',
224 default=None,
224 default=None,
225 )
225 )
226 coreconfigitem('convert', 'git.committeractions',
226 coreconfigitem('convert', 'git.committeractions',
227 default=lambda: ['messagedifferent'],
227 default=lambda: ['messagedifferent'],
228 )
228 )
229 coreconfigitem('convert', 'git.extrakeys',
229 coreconfigitem('convert', 'git.extrakeys',
230 default=list,
230 default=list,
231 )
231 )
232 coreconfigitem('convert', 'git.findcopiesharder',
232 coreconfigitem('convert', 'git.findcopiesharder',
233 default=False,
233 default=False,
234 )
234 )
235 coreconfigitem('convert', 'git.remoteprefix',
235 coreconfigitem('convert', 'git.remoteprefix',
236 default='remote',
236 default='remote',
237 )
237 )
238 coreconfigitem('convert', 'git.renamelimit',
238 coreconfigitem('convert', 'git.renamelimit',
239 default=400,
239 default=400,
240 )
240 )
241 coreconfigitem('convert', 'git.saverev',
241 coreconfigitem('convert', 'git.saverev',
242 default=True,
242 default=True,
243 )
243 )
244 coreconfigitem('convert', 'git.similarity',
244 coreconfigitem('convert', 'git.similarity',
245 default=50,
245 default=50,
246 )
246 )
247 coreconfigitem('convert', 'git.skipsubmodules',
247 coreconfigitem('convert', 'git.skipsubmodules',
248 default=False,
248 default=False,
249 )
249 )
250 coreconfigitem('convert', 'hg.clonebranches',
250 coreconfigitem('convert', 'hg.clonebranches',
251 default=False,
251 default=False,
252 )
252 )
253 coreconfigitem('convert', 'hg.ignoreerrors',
253 coreconfigitem('convert', 'hg.ignoreerrors',
254 default=False,
254 default=False,
255 )
255 )
256 coreconfigitem('convert', 'hg.revs',
256 coreconfigitem('convert', 'hg.revs',
257 default=None,
257 default=None,
258 )
258 )
259 coreconfigitem('convert', 'hg.saverev',
259 coreconfigitem('convert', 'hg.saverev',
260 default=False,
260 default=False,
261 )
261 )
262 coreconfigitem('convert', 'hg.sourcename',
262 coreconfigitem('convert', 'hg.sourcename',
263 default=None,
263 default=None,
264 )
264 )
265 coreconfigitem('convert', 'hg.startrev',
265 coreconfigitem('convert', 'hg.startrev',
266 default=None,
266 default=None,
267 )
267 )
268 coreconfigitem('convert', 'hg.tagsbranch',
268 coreconfigitem('convert', 'hg.tagsbranch',
269 default='default',
269 default='default',
270 )
270 )
271 coreconfigitem('convert', 'hg.usebranchnames',
271 coreconfigitem('convert', 'hg.usebranchnames',
272 default=True,
272 default=True,
273 )
273 )
274 coreconfigitem('convert', 'ignoreancestorcheck',
274 coreconfigitem('convert', 'ignoreancestorcheck',
275 default=False,
275 default=False,
276 )
276 )
277 coreconfigitem('convert', 'localtimezone',
277 coreconfigitem('convert', 'localtimezone',
278 default=False,
278 default=False,
279 )
279 )
280 coreconfigitem('convert', 'p4.encoding',
280 coreconfigitem('convert', 'p4.encoding',
281 default=dynamicdefault,
281 default=dynamicdefault,
282 )
282 )
283 coreconfigitem('convert', 'p4.startrev',
283 coreconfigitem('convert', 'p4.startrev',
284 default=0,
284 default=0,
285 )
285 )
286 coreconfigitem('convert', 'skiptags',
286 coreconfigitem('convert', 'skiptags',
287 default=False,
287 default=False,
288 )
288 )
289 coreconfigitem('convert', 'svn.debugsvnlog',
289 coreconfigitem('convert', 'svn.debugsvnlog',
290 default=True,
290 default=True,
291 )
291 )
292 coreconfigitem('convert', 'svn.trunk',
292 coreconfigitem('convert', 'svn.trunk',
293 default=None,
293 default=None,
294 )
294 )
295 coreconfigitem('convert', 'svn.tags',
295 coreconfigitem('convert', 'svn.tags',
296 default=None,
296 default=None,
297 )
297 )
298 coreconfigitem('convert', 'svn.branches',
298 coreconfigitem('convert', 'svn.branches',
299 default=None,
299 default=None,
300 )
300 )
301 coreconfigitem('convert', 'svn.startrev',
301 coreconfigitem('convert', 'svn.startrev',
302 default=0,
302 default=0,
303 )
303 )
304 coreconfigitem('debug', 'dirstate.delaywrite',
304 coreconfigitem('debug', 'dirstate.delaywrite',
305 default=0,
305 default=0,
306 )
306 )
307 coreconfigitem('defaults', '.*',
307 coreconfigitem('defaults', '.*',
308 default=None,
308 default=None,
309 generic=True,
309 generic=True,
310 )
310 )
311 coreconfigitem('devel', 'all-warnings',
311 coreconfigitem('devel', 'all-warnings',
312 default=False,
312 default=False,
313 )
313 )
314 coreconfigitem('devel', 'bundle2.debug',
314 coreconfigitem('devel', 'bundle2.debug',
315 default=False,
315 default=False,
316 )
316 )
317 coreconfigitem('devel', 'cache-vfs',
317 coreconfigitem('devel', 'cache-vfs',
318 default=None,
318 default=None,
319 )
319 )
320 coreconfigitem('devel', 'check-locks',
320 coreconfigitem('devel', 'check-locks',
321 default=False,
321 default=False,
322 )
322 )
323 coreconfigitem('devel', 'check-relroot',
323 coreconfigitem('devel', 'check-relroot',
324 default=False,
324 default=False,
325 )
325 )
326 coreconfigitem('devel', 'default-date',
326 coreconfigitem('devel', 'default-date',
327 default=None,
327 default=None,
328 )
328 )
329 coreconfigitem('devel', 'deprec-warn',
329 coreconfigitem('devel', 'deprec-warn',
330 default=False,
330 default=False,
331 )
331 )
332 coreconfigitem('devel', 'disableloaddefaultcerts',
332 coreconfigitem('devel', 'disableloaddefaultcerts',
333 default=False,
333 default=False,
334 )
334 )
335 coreconfigitem('devel', 'warn-empty-changegroup',
335 coreconfigitem('devel', 'warn-empty-changegroup',
336 default=False,
336 default=False,
337 )
337 )
338 coreconfigitem('devel', 'legacy.exchange',
338 coreconfigitem('devel', 'legacy.exchange',
339 default=list,
339 default=list,
340 )
340 )
341 coreconfigitem('devel', 'servercafile',
341 coreconfigitem('devel', 'servercafile',
342 default='',
342 default='',
343 )
343 )
344 coreconfigitem('devel', 'serverexactprotocol',
344 coreconfigitem('devel', 'serverexactprotocol',
345 default='',
345 default='',
346 )
346 )
347 coreconfigitem('devel', 'serverrequirecert',
347 coreconfigitem('devel', 'serverrequirecert',
348 default=False,
348 default=False,
349 )
349 )
350 coreconfigitem('devel', 'strip-obsmarkers',
350 coreconfigitem('devel', 'strip-obsmarkers',
351 default=True,
351 default=True,
352 )
352 )
353 coreconfigitem('devel', 'warn-config',
353 coreconfigitem('devel', 'warn-config',
354 default=None,
354 default=None,
355 )
355 )
356 coreconfigitem('devel', 'warn-config-default',
356 coreconfigitem('devel', 'warn-config-default',
357 default=None,
357 default=None,
358 )
358 )
359 coreconfigitem('devel', 'user.obsmarker',
359 coreconfigitem('devel', 'user.obsmarker',
360 default=None,
360 default=None,
361 )
361 )
362 coreconfigitem('devel', 'warn-config-unknown',
362 coreconfigitem('devel', 'warn-config-unknown',
363 default=None,
363 default=None,
364 )
364 )
365 coreconfigitem('devel', 'debug.peer-request',
365 coreconfigitem('devel', 'debug.peer-request',
366 default=False,
366 default=False,
367 )
367 )
368 coreconfigitem('diff', 'nodates',
368 coreconfigitem('diff', 'nodates',
369 default=False,
369 default=False,
370 )
370 )
371 coreconfigitem('diff', 'showfunc',
371 coreconfigitem('diff', 'showfunc',
372 default=False,
372 default=False,
373 )
373 )
374 coreconfigitem('diff', 'unified',
374 coreconfigitem('diff', 'unified',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('diff', 'git',
377 coreconfigitem('diff', 'git',
378 default=False,
378 default=False,
379 )
379 )
380 coreconfigitem('diff', 'ignorews',
380 coreconfigitem('diff', 'ignorews',
381 default=False,
381 default=False,
382 )
382 )
383 coreconfigitem('diff', 'ignorewsamount',
383 coreconfigitem('diff', 'ignorewsamount',
384 default=False,
384 default=False,
385 )
385 )
386 coreconfigitem('diff', 'ignoreblanklines',
386 coreconfigitem('diff', 'ignoreblanklines',
387 default=False,
387 default=False,
388 )
388 )
389 coreconfigitem('diff', 'ignorewseol',
389 coreconfigitem('diff', 'ignorewseol',
390 default=False,
390 default=False,
391 )
391 )
392 coreconfigitem('diff', 'nobinary',
392 coreconfigitem('diff', 'nobinary',
393 default=False,
393 default=False,
394 )
394 )
395 coreconfigitem('diff', 'noprefix',
395 coreconfigitem('diff', 'noprefix',
396 default=False,
396 default=False,
397 )
397 )
398 coreconfigitem('email', 'bcc',
398 coreconfigitem('email', 'bcc',
399 default=None,
399 default=None,
400 )
400 )
401 coreconfigitem('email', 'cc',
401 coreconfigitem('email', 'cc',
402 default=None,
402 default=None,
403 )
403 )
404 coreconfigitem('email', 'charsets',
404 coreconfigitem('email', 'charsets',
405 default=list,
405 default=list,
406 )
406 )
407 coreconfigitem('email', 'from',
407 coreconfigitem('email', 'from',
408 default=None,
408 default=None,
409 )
409 )
410 coreconfigitem('email', 'method',
410 coreconfigitem('email', 'method',
411 default='smtp',
411 default='smtp',
412 )
412 )
413 coreconfigitem('email', 'reply-to',
413 coreconfigitem('email', 'reply-to',
414 default=None,
414 default=None,
415 )
415 )
416 coreconfigitem('email', 'to',
416 coreconfigitem('email', 'to',
417 default=None,
417 default=None,
418 )
418 )
419 coreconfigitem('experimental', 'archivemetatemplate',
419 coreconfigitem('experimental', 'archivemetatemplate',
420 default=dynamicdefault,
420 default=dynamicdefault,
421 )
421 )
422 coreconfigitem('experimental', 'bundle-phases',
422 coreconfigitem('experimental', 'bundle-phases',
423 default=False,
423 default=False,
424 )
424 )
425 coreconfigitem('experimental', 'bundle2-advertise',
425 coreconfigitem('experimental', 'bundle2-advertise',
426 default=True,
426 default=True,
427 )
427 )
428 coreconfigitem('experimental', 'bundle2-output-capture',
428 coreconfigitem('experimental', 'bundle2-output-capture',
429 default=False,
429 default=False,
430 )
430 )
431 coreconfigitem('experimental', 'bundle2.pushback',
431 coreconfigitem('experimental', 'bundle2.pushback',
432 default=False,
432 default=False,
433 )
433 )
434 coreconfigitem('experimental', 'bundle2.stream',
434 coreconfigitem('experimental', 'bundle2.stream',
435 default=False,
435 default=False,
436 )
436 )
437 coreconfigitem('experimental', 'bundle2lazylocking',
437 coreconfigitem('experimental', 'bundle2lazylocking',
438 default=False,
438 default=False,
439 )
439 )
440 coreconfigitem('experimental', 'bundlecomplevel',
440 coreconfigitem('experimental', 'bundlecomplevel',
441 default=None,
441 default=None,
442 )
442 )
443 coreconfigitem('experimental', 'changegroup3',
443 coreconfigitem('experimental', 'changegroup3',
444 default=False,
444 default=False,
445 )
445 )
446 coreconfigitem('experimental', 'clientcompressionengines',
446 coreconfigitem('experimental', 'clientcompressionengines',
447 default=list,
447 default=list,
448 )
448 )
449 coreconfigitem('experimental', 'copytrace',
449 coreconfigitem('experimental', 'copytrace',
450 default='on',
450 default='on',
451 )
451 )
452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
453 default=100,
453 default=100,
454 )
454 )
455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
456 default=100,
456 default=100,
457 )
457 )
458 coreconfigitem('experimental', 'crecordtest',
458 coreconfigitem('experimental', 'crecordtest',
459 default=None,
459 default=None,
460 )
460 )
461 coreconfigitem('experimental', 'directaccess',
461 coreconfigitem('experimental', 'directaccess',
462 default=False,
462 default=False,
463 )
463 )
464 coreconfigitem('experimental', 'directaccess.revnums',
464 coreconfigitem('experimental', 'directaccess.revnums',
465 default=False,
465 default=False,
466 )
466 )
467 coreconfigitem('experimental', 'editortmpinhg',
467 coreconfigitem('experimental', 'editortmpinhg',
468 default=False,
468 default=False,
469 )
469 )
470 coreconfigitem('experimental', 'evolution',
470 coreconfigitem('experimental', 'evolution',
471 default=list,
471 default=list,
472 )
472 )
473 coreconfigitem('experimental', 'evolution.allowdivergence',
473 coreconfigitem('experimental', 'evolution.allowdivergence',
474 default=False,
474 default=False,
475 alias=[('experimental', 'allowdivergence')]
475 alias=[('experimental', 'allowdivergence')]
476 )
476 )
477 coreconfigitem('experimental', 'evolution.allowunstable',
477 coreconfigitem('experimental', 'evolution.allowunstable',
478 default=None,
478 default=None,
479 )
479 )
480 coreconfigitem('experimental', 'evolution.createmarkers',
480 coreconfigitem('experimental', 'evolution.createmarkers',
481 default=None,
481 default=None,
482 )
482 )
483 coreconfigitem('experimental', 'evolution.effect-flags',
483 coreconfigitem('experimental', 'evolution.effect-flags',
484 default=True,
484 default=True,
485 alias=[('experimental', 'effect-flags')]
485 alias=[('experimental', 'effect-flags')]
486 )
486 )
487 coreconfigitem('experimental', 'evolution.exchange',
487 coreconfigitem('experimental', 'evolution.exchange',
488 default=None,
488 default=None,
489 )
489 )
490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
491 default=False,
491 default=False,
492 )
492 )
493 coreconfigitem('experimental', 'evolution.report-instabilities',
493 coreconfigitem('experimental', 'evolution.report-instabilities',
494 default=True,
494 default=True,
495 )
495 )
496 coreconfigitem('experimental', 'evolution.track-operation',
496 coreconfigitem('experimental', 'evolution.track-operation',
497 default=True,
497 default=True,
498 )
498 )
499 coreconfigitem('experimental', 'worddiff',
499 coreconfigitem('experimental', 'worddiff',
500 default=False,
500 default=False,
501 )
501 )
502 coreconfigitem('experimental', 'maxdeltachainspan',
502 coreconfigitem('experimental', 'maxdeltachainspan',
503 default=-1,
503 default=-1,
504 )
504 )
505 coreconfigitem('experimental', 'mergetempdirprefix',
505 coreconfigitem('experimental', 'mergetempdirprefix',
506 default=None,
506 default=None,
507 )
507 )
508 coreconfigitem('experimental', 'mmapindexthreshold',
508 coreconfigitem('experimental', 'mmapindexthreshold',
509 default=None,
509 default=None,
510 )
510 )
511 coreconfigitem('experimental', 'nonnormalparanoidcheck',
511 coreconfigitem('experimental', 'nonnormalparanoidcheck',
512 default=False,
512 default=False,
513 )
513 )
514 coreconfigitem('experimental', 'exportableenviron',
514 coreconfigitem('experimental', 'exportableenviron',
515 default=list,
515 default=list,
516 )
516 )
517 coreconfigitem('experimental', 'extendedheader.index',
517 coreconfigitem('experimental', 'extendedheader.index',
518 default=None,
518 default=None,
519 )
519 )
520 coreconfigitem('experimental', 'extendedheader.similarity',
520 coreconfigitem('experimental', 'extendedheader.similarity',
521 default=False,
521 default=False,
522 )
522 )
523 coreconfigitem('experimental', 'format.compression',
523 coreconfigitem('experimental', 'format.compression',
524 default='zlib',
524 default='zlib',
525 )
525 )
526 coreconfigitem('experimental', 'graphshorten',
526 coreconfigitem('experimental', 'graphshorten',
527 default=False,
527 default=False,
528 )
528 )
529 coreconfigitem('experimental', 'graphstyle.parent',
529 coreconfigitem('experimental', 'graphstyle.parent',
530 default=dynamicdefault,
530 default=dynamicdefault,
531 )
531 )
532 coreconfigitem('experimental', 'graphstyle.missing',
532 coreconfigitem('experimental', 'graphstyle.missing',
533 default=dynamicdefault,
533 default=dynamicdefault,
534 )
534 )
535 coreconfigitem('experimental', 'graphstyle.grandparent',
535 coreconfigitem('experimental', 'graphstyle.grandparent',
536 default=dynamicdefault,
536 default=dynamicdefault,
537 )
537 )
538 coreconfigitem('experimental', 'hook-track-tags',
538 coreconfigitem('experimental', 'hook-track-tags',
539 default=False,
539 default=False,
540 )
540 )
541 coreconfigitem('experimental', 'httppostargs',
541 coreconfigitem('experimental', 'httppostargs',
542 default=False,
542 default=False,
543 )
543 )
544 coreconfigitem('experimental', 'mergedriver',
544 coreconfigitem('experimental', 'mergedriver',
545 default=None,
545 default=None,
546 )
546 )
547 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
547 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
548 default=False,
548 default=False,
549 )
549 )
550 coreconfigitem('experimental', 'remotenames',
550 coreconfigitem('experimental', 'remotenames',
551 default=False,
551 default=False,
552 )
552 )
553 coreconfigitem('experimental', 'revlogv2',
553 coreconfigitem('experimental', 'revlogv2',
554 default=None,
554 default=None,
555 )
555 )
556 coreconfigitem('experimental', 'single-head-per-branch',
556 coreconfigitem('experimental', 'single-head-per-branch',
557 default=False,
557 default=False,
558 )
558 )
559 coreconfigitem('experimental', 'sshserver.support-v2',
559 coreconfigitem('experimental', 'sshserver.support-v2',
560 default=False,
560 default=False,
561 )
561 )
562 coreconfigitem('experimental', 'spacemovesdown',
562 coreconfigitem('experimental', 'spacemovesdown',
563 default=False,
563 default=False,
564 )
564 )
565 coreconfigitem('experimental', 'sparse-read',
565 coreconfigitem('experimental', 'sparse-read',
566 default=False,
566 default=False,
567 )
567 )
568 coreconfigitem('experimental', 'sparse-read.density-threshold',
568 coreconfigitem('experimental', 'sparse-read.density-threshold',
569 default=0.25,
569 default=0.25,
570 )
570 )
571 coreconfigitem('experimental', 'sparse-read.min-gap-size',
571 coreconfigitem('experimental', 'sparse-read.min-gap-size',
572 default='256K',
572 default='256K',
573 )
573 )
574 coreconfigitem('experimental', 'treemanifest',
574 coreconfigitem('experimental', 'treemanifest',
575 default=False,
575 default=False,
576 )
576 )
577 coreconfigitem('experimental', 'update.atomic-file',
577 coreconfigitem('experimental', 'update.atomic-file',
578 default=False,
578 default=False,
579 )
579 )
580 coreconfigitem('experimental', 'sshpeer.advertise-v2',
580 coreconfigitem('experimental', 'sshpeer.advertise-v2',
581 default=False,
581 default=False,
582 )
582 )
583 coreconfigitem('experimental', 'web.apiserver',
584 default=False,
585 )
586 coreconfigitem('experimental', 'web.api.http-v2',
587 default=False,
588 )
583 coreconfigitem('experimental', 'xdiff',
589 coreconfigitem('experimental', 'xdiff',
584 default=False,
590 default=False,
585 )
591 )
586 coreconfigitem('extensions', '.*',
592 coreconfigitem('extensions', '.*',
587 default=None,
593 default=None,
588 generic=True,
594 generic=True,
589 )
595 )
590 coreconfigitem('extdata', '.*',
596 coreconfigitem('extdata', '.*',
591 default=None,
597 default=None,
592 generic=True,
598 generic=True,
593 )
599 )
594 coreconfigitem('format', 'aggressivemergedeltas',
600 coreconfigitem('format', 'aggressivemergedeltas',
595 default=False,
601 default=False,
596 )
602 )
597 coreconfigitem('format', 'chunkcachesize',
603 coreconfigitem('format', 'chunkcachesize',
598 default=None,
604 default=None,
599 )
605 )
600 coreconfigitem('format', 'dotencode',
606 coreconfigitem('format', 'dotencode',
601 default=True,
607 default=True,
602 )
608 )
603 coreconfigitem('format', 'generaldelta',
609 coreconfigitem('format', 'generaldelta',
604 default=False,
610 default=False,
605 )
611 )
606 coreconfigitem('format', 'manifestcachesize',
612 coreconfigitem('format', 'manifestcachesize',
607 default=None,
613 default=None,
608 )
614 )
609 coreconfigitem('format', 'maxchainlen',
615 coreconfigitem('format', 'maxchainlen',
610 default=None,
616 default=None,
611 )
617 )
612 coreconfigitem('format', 'obsstore-version',
618 coreconfigitem('format', 'obsstore-version',
613 default=None,
619 default=None,
614 )
620 )
615 coreconfigitem('format', 'usefncache',
621 coreconfigitem('format', 'usefncache',
616 default=True,
622 default=True,
617 )
623 )
618 coreconfigitem('format', 'usegeneraldelta',
624 coreconfigitem('format', 'usegeneraldelta',
619 default=True,
625 default=True,
620 )
626 )
621 coreconfigitem('format', 'usestore',
627 coreconfigitem('format', 'usestore',
622 default=True,
628 default=True,
623 )
629 )
624 coreconfigitem('fsmonitor', 'warn_when_unused',
630 coreconfigitem('fsmonitor', 'warn_when_unused',
625 default=True,
631 default=True,
626 )
632 )
627 coreconfigitem('fsmonitor', 'warn_update_file_count',
633 coreconfigitem('fsmonitor', 'warn_update_file_count',
628 default=50000,
634 default=50000,
629 )
635 )
630 coreconfigitem('hooks', '.*',
636 coreconfigitem('hooks', '.*',
631 default=dynamicdefault,
637 default=dynamicdefault,
632 generic=True,
638 generic=True,
633 )
639 )
634 coreconfigitem('hgweb-paths', '.*',
640 coreconfigitem('hgweb-paths', '.*',
635 default=list,
641 default=list,
636 generic=True,
642 generic=True,
637 )
643 )
638 coreconfigitem('hostfingerprints', '.*',
644 coreconfigitem('hostfingerprints', '.*',
639 default=list,
645 default=list,
640 generic=True,
646 generic=True,
641 )
647 )
642 coreconfigitem('hostsecurity', 'ciphers',
648 coreconfigitem('hostsecurity', 'ciphers',
643 default=None,
649 default=None,
644 )
650 )
645 coreconfigitem('hostsecurity', 'disabletls10warning',
651 coreconfigitem('hostsecurity', 'disabletls10warning',
646 default=False,
652 default=False,
647 )
653 )
648 coreconfigitem('hostsecurity', 'minimumprotocol',
654 coreconfigitem('hostsecurity', 'minimumprotocol',
649 default=dynamicdefault,
655 default=dynamicdefault,
650 )
656 )
651 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
657 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
652 default=dynamicdefault,
658 default=dynamicdefault,
653 generic=True,
659 generic=True,
654 )
660 )
655 coreconfigitem('hostsecurity', '.*:ciphers$',
661 coreconfigitem('hostsecurity', '.*:ciphers$',
656 default=dynamicdefault,
662 default=dynamicdefault,
657 generic=True,
663 generic=True,
658 )
664 )
659 coreconfigitem('hostsecurity', '.*:fingerprints$',
665 coreconfigitem('hostsecurity', '.*:fingerprints$',
660 default=list,
666 default=list,
661 generic=True,
667 generic=True,
662 )
668 )
663 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
669 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
664 default=None,
670 default=None,
665 generic=True,
671 generic=True,
666 )
672 )
667
673
668 coreconfigitem('http_proxy', 'always',
674 coreconfigitem('http_proxy', 'always',
669 default=False,
675 default=False,
670 )
676 )
671 coreconfigitem('http_proxy', 'host',
677 coreconfigitem('http_proxy', 'host',
672 default=None,
678 default=None,
673 )
679 )
674 coreconfigitem('http_proxy', 'no',
680 coreconfigitem('http_proxy', 'no',
675 default=list,
681 default=list,
676 )
682 )
677 coreconfigitem('http_proxy', 'passwd',
683 coreconfigitem('http_proxy', 'passwd',
678 default=None,
684 default=None,
679 )
685 )
680 coreconfigitem('http_proxy', 'user',
686 coreconfigitem('http_proxy', 'user',
681 default=None,
687 default=None,
682 )
688 )
683 coreconfigitem('logtoprocess', 'commandexception',
689 coreconfigitem('logtoprocess', 'commandexception',
684 default=None,
690 default=None,
685 )
691 )
686 coreconfigitem('logtoprocess', 'commandfinish',
692 coreconfigitem('logtoprocess', 'commandfinish',
687 default=None,
693 default=None,
688 )
694 )
689 coreconfigitem('logtoprocess', 'command',
695 coreconfigitem('logtoprocess', 'command',
690 default=None,
696 default=None,
691 )
697 )
692 coreconfigitem('logtoprocess', 'develwarn',
698 coreconfigitem('logtoprocess', 'develwarn',
693 default=None,
699 default=None,
694 )
700 )
695 coreconfigitem('logtoprocess', 'uiblocked',
701 coreconfigitem('logtoprocess', 'uiblocked',
696 default=None,
702 default=None,
697 )
703 )
698 coreconfigitem('merge', 'checkunknown',
704 coreconfigitem('merge', 'checkunknown',
699 default='abort',
705 default='abort',
700 )
706 )
701 coreconfigitem('merge', 'checkignored',
707 coreconfigitem('merge', 'checkignored',
702 default='abort',
708 default='abort',
703 )
709 )
704 coreconfigitem('experimental', 'merge.checkpathconflicts',
710 coreconfigitem('experimental', 'merge.checkpathconflicts',
705 default=False,
711 default=False,
706 )
712 )
707 coreconfigitem('merge', 'followcopies',
713 coreconfigitem('merge', 'followcopies',
708 default=True,
714 default=True,
709 )
715 )
710 coreconfigitem('merge', 'on-failure',
716 coreconfigitem('merge', 'on-failure',
711 default='continue',
717 default='continue',
712 )
718 )
713 coreconfigitem('merge', 'preferancestor',
719 coreconfigitem('merge', 'preferancestor',
714 default=lambda: ['*'],
720 default=lambda: ['*'],
715 )
721 )
716 coreconfigitem('merge-tools', '.*',
722 coreconfigitem('merge-tools', '.*',
717 default=None,
723 default=None,
718 generic=True,
724 generic=True,
719 )
725 )
720 coreconfigitem('merge-tools', br'.*\.args$',
726 coreconfigitem('merge-tools', br'.*\.args$',
721 default="$local $base $other",
727 default="$local $base $other",
722 generic=True,
728 generic=True,
723 priority=-1,
729 priority=-1,
724 )
730 )
725 coreconfigitem('merge-tools', br'.*\.binary$',
731 coreconfigitem('merge-tools', br'.*\.binary$',
726 default=False,
732 default=False,
727 generic=True,
733 generic=True,
728 priority=-1,
734 priority=-1,
729 )
735 )
730 coreconfigitem('merge-tools', br'.*\.check$',
736 coreconfigitem('merge-tools', br'.*\.check$',
731 default=list,
737 default=list,
732 generic=True,
738 generic=True,
733 priority=-1,
739 priority=-1,
734 )
740 )
735 coreconfigitem('merge-tools', br'.*\.checkchanged$',
741 coreconfigitem('merge-tools', br'.*\.checkchanged$',
736 default=False,
742 default=False,
737 generic=True,
743 generic=True,
738 priority=-1,
744 priority=-1,
739 )
745 )
740 coreconfigitem('merge-tools', br'.*\.executable$',
746 coreconfigitem('merge-tools', br'.*\.executable$',
741 default=dynamicdefault,
747 default=dynamicdefault,
742 generic=True,
748 generic=True,
743 priority=-1,
749 priority=-1,
744 )
750 )
745 coreconfigitem('merge-tools', br'.*\.fixeol$',
751 coreconfigitem('merge-tools', br'.*\.fixeol$',
746 default=False,
752 default=False,
747 generic=True,
753 generic=True,
748 priority=-1,
754 priority=-1,
749 )
755 )
750 coreconfigitem('merge-tools', br'.*\.gui$',
756 coreconfigitem('merge-tools', br'.*\.gui$',
751 default=False,
757 default=False,
752 generic=True,
758 generic=True,
753 priority=-1,
759 priority=-1,
754 )
760 )
755 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
761 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
756 default='basic',
762 default='basic',
757 generic=True,
763 generic=True,
758 priority=-1,
764 priority=-1,
759 )
765 )
760 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
766 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
761 default=dynamicdefault, # take from ui.mergemarkertemplate
767 default=dynamicdefault, # take from ui.mergemarkertemplate
762 generic=True,
768 generic=True,
763 priority=-1,
769 priority=-1,
764 )
770 )
765 coreconfigitem('merge-tools', br'.*\.priority$',
771 coreconfigitem('merge-tools', br'.*\.priority$',
766 default=0,
772 default=0,
767 generic=True,
773 generic=True,
768 priority=-1,
774 priority=-1,
769 )
775 )
770 coreconfigitem('merge-tools', br'.*\.premerge$',
776 coreconfigitem('merge-tools', br'.*\.premerge$',
771 default=dynamicdefault,
777 default=dynamicdefault,
772 generic=True,
778 generic=True,
773 priority=-1,
779 priority=-1,
774 )
780 )
775 coreconfigitem('merge-tools', br'.*\.symlink$',
781 coreconfigitem('merge-tools', br'.*\.symlink$',
776 default=False,
782 default=False,
777 generic=True,
783 generic=True,
778 priority=-1,
784 priority=-1,
779 )
785 )
780 coreconfigitem('pager', 'attend-.*',
786 coreconfigitem('pager', 'attend-.*',
781 default=dynamicdefault,
787 default=dynamicdefault,
782 generic=True,
788 generic=True,
783 )
789 )
784 coreconfigitem('pager', 'ignore',
790 coreconfigitem('pager', 'ignore',
785 default=list,
791 default=list,
786 )
792 )
787 coreconfigitem('pager', 'pager',
793 coreconfigitem('pager', 'pager',
788 default=dynamicdefault,
794 default=dynamicdefault,
789 )
795 )
790 coreconfigitem('patch', 'eol',
796 coreconfigitem('patch', 'eol',
791 default='strict',
797 default='strict',
792 )
798 )
793 coreconfigitem('patch', 'fuzz',
799 coreconfigitem('patch', 'fuzz',
794 default=2,
800 default=2,
795 )
801 )
796 coreconfigitem('paths', 'default',
802 coreconfigitem('paths', 'default',
797 default=None,
803 default=None,
798 )
804 )
799 coreconfigitem('paths', 'default-push',
805 coreconfigitem('paths', 'default-push',
800 default=None,
806 default=None,
801 )
807 )
802 coreconfigitem('paths', '.*',
808 coreconfigitem('paths', '.*',
803 default=None,
809 default=None,
804 generic=True,
810 generic=True,
805 )
811 )
806 coreconfigitem('phases', 'checksubrepos',
812 coreconfigitem('phases', 'checksubrepos',
807 default='follow',
813 default='follow',
808 )
814 )
809 coreconfigitem('phases', 'new-commit',
815 coreconfigitem('phases', 'new-commit',
810 default='draft',
816 default='draft',
811 )
817 )
812 coreconfigitem('phases', 'publish',
818 coreconfigitem('phases', 'publish',
813 default=True,
819 default=True,
814 )
820 )
815 coreconfigitem('profiling', 'enabled',
821 coreconfigitem('profiling', 'enabled',
816 default=False,
822 default=False,
817 )
823 )
818 coreconfigitem('profiling', 'format',
824 coreconfigitem('profiling', 'format',
819 default='text',
825 default='text',
820 )
826 )
821 coreconfigitem('profiling', 'freq',
827 coreconfigitem('profiling', 'freq',
822 default=1000,
828 default=1000,
823 )
829 )
824 coreconfigitem('profiling', 'limit',
830 coreconfigitem('profiling', 'limit',
825 default=30,
831 default=30,
826 )
832 )
827 coreconfigitem('profiling', 'nested',
833 coreconfigitem('profiling', 'nested',
828 default=0,
834 default=0,
829 )
835 )
830 coreconfigitem('profiling', 'output',
836 coreconfigitem('profiling', 'output',
831 default=None,
837 default=None,
832 )
838 )
833 coreconfigitem('profiling', 'showmax',
839 coreconfigitem('profiling', 'showmax',
834 default=0.999,
840 default=0.999,
835 )
841 )
836 coreconfigitem('profiling', 'showmin',
842 coreconfigitem('profiling', 'showmin',
837 default=dynamicdefault,
843 default=dynamicdefault,
838 )
844 )
839 coreconfigitem('profiling', 'sort',
845 coreconfigitem('profiling', 'sort',
840 default='inlinetime',
846 default='inlinetime',
841 )
847 )
842 coreconfigitem('profiling', 'statformat',
848 coreconfigitem('profiling', 'statformat',
843 default='hotpath',
849 default='hotpath',
844 )
850 )
845 coreconfigitem('profiling', 'type',
851 coreconfigitem('profiling', 'type',
846 default='stat',
852 default='stat',
847 )
853 )
848 coreconfigitem('progress', 'assume-tty',
854 coreconfigitem('progress', 'assume-tty',
849 default=False,
855 default=False,
850 )
856 )
851 coreconfigitem('progress', 'changedelay',
857 coreconfigitem('progress', 'changedelay',
852 default=1,
858 default=1,
853 )
859 )
854 coreconfigitem('progress', 'clear-complete',
860 coreconfigitem('progress', 'clear-complete',
855 default=True,
861 default=True,
856 )
862 )
857 coreconfigitem('progress', 'debug',
863 coreconfigitem('progress', 'debug',
858 default=False,
864 default=False,
859 )
865 )
860 coreconfigitem('progress', 'delay',
866 coreconfigitem('progress', 'delay',
861 default=3,
867 default=3,
862 )
868 )
863 coreconfigitem('progress', 'disable',
869 coreconfigitem('progress', 'disable',
864 default=False,
870 default=False,
865 )
871 )
866 coreconfigitem('progress', 'estimateinterval',
872 coreconfigitem('progress', 'estimateinterval',
867 default=60.0,
873 default=60.0,
868 )
874 )
869 coreconfigitem('progress', 'format',
875 coreconfigitem('progress', 'format',
870 default=lambda: ['topic', 'bar', 'number', 'estimate'],
876 default=lambda: ['topic', 'bar', 'number', 'estimate'],
871 )
877 )
872 coreconfigitem('progress', 'refresh',
878 coreconfigitem('progress', 'refresh',
873 default=0.1,
879 default=0.1,
874 )
880 )
875 coreconfigitem('progress', 'width',
881 coreconfigitem('progress', 'width',
876 default=dynamicdefault,
882 default=dynamicdefault,
877 )
883 )
878 coreconfigitem('push', 'pushvars.server',
884 coreconfigitem('push', 'pushvars.server',
879 default=False,
885 default=False,
880 )
886 )
881 coreconfigitem('server', 'bookmarks-pushkey-compat',
887 coreconfigitem('server', 'bookmarks-pushkey-compat',
882 default=True,
888 default=True,
883 )
889 )
884 coreconfigitem('server', 'bundle1',
890 coreconfigitem('server', 'bundle1',
885 default=True,
891 default=True,
886 )
892 )
887 coreconfigitem('server', 'bundle1gd',
893 coreconfigitem('server', 'bundle1gd',
888 default=None,
894 default=None,
889 )
895 )
890 coreconfigitem('server', 'bundle1.pull',
896 coreconfigitem('server', 'bundle1.pull',
891 default=None,
897 default=None,
892 )
898 )
893 coreconfigitem('server', 'bundle1gd.pull',
899 coreconfigitem('server', 'bundle1gd.pull',
894 default=None,
900 default=None,
895 )
901 )
896 coreconfigitem('server', 'bundle1.push',
902 coreconfigitem('server', 'bundle1.push',
897 default=None,
903 default=None,
898 )
904 )
899 coreconfigitem('server', 'bundle1gd.push',
905 coreconfigitem('server', 'bundle1gd.push',
900 default=None,
906 default=None,
901 )
907 )
902 coreconfigitem('server', 'compressionengines',
908 coreconfigitem('server', 'compressionengines',
903 default=list,
909 default=list,
904 )
910 )
905 coreconfigitem('server', 'concurrent-push-mode',
911 coreconfigitem('server', 'concurrent-push-mode',
906 default='strict',
912 default='strict',
907 )
913 )
908 coreconfigitem('server', 'disablefullbundle',
914 coreconfigitem('server', 'disablefullbundle',
909 default=False,
915 default=False,
910 )
916 )
911 coreconfigitem('server', 'maxhttpheaderlen',
917 coreconfigitem('server', 'maxhttpheaderlen',
912 default=1024,
918 default=1024,
913 )
919 )
914 coreconfigitem('server', 'preferuncompressed',
920 coreconfigitem('server', 'preferuncompressed',
915 default=False,
921 default=False,
916 )
922 )
917 coreconfigitem('server', 'uncompressed',
923 coreconfigitem('server', 'uncompressed',
918 default=True,
924 default=True,
919 )
925 )
920 coreconfigitem('server', 'uncompressedallowsecret',
926 coreconfigitem('server', 'uncompressedallowsecret',
921 default=False,
927 default=False,
922 )
928 )
923 coreconfigitem('server', 'validate',
929 coreconfigitem('server', 'validate',
924 default=False,
930 default=False,
925 )
931 )
926 coreconfigitem('server', 'zliblevel',
932 coreconfigitem('server', 'zliblevel',
927 default=-1,
933 default=-1,
928 )
934 )
929 coreconfigitem('share', 'pool',
935 coreconfigitem('share', 'pool',
930 default=None,
936 default=None,
931 )
937 )
932 coreconfigitem('share', 'poolnaming',
938 coreconfigitem('share', 'poolnaming',
933 default='identity',
939 default='identity',
934 )
940 )
935 coreconfigitem('smtp', 'host',
941 coreconfigitem('smtp', 'host',
936 default=None,
942 default=None,
937 )
943 )
938 coreconfigitem('smtp', 'local_hostname',
944 coreconfigitem('smtp', 'local_hostname',
939 default=None,
945 default=None,
940 )
946 )
941 coreconfigitem('smtp', 'password',
947 coreconfigitem('smtp', 'password',
942 default=None,
948 default=None,
943 )
949 )
944 coreconfigitem('smtp', 'port',
950 coreconfigitem('smtp', 'port',
945 default=dynamicdefault,
951 default=dynamicdefault,
946 )
952 )
947 coreconfigitem('smtp', 'tls',
953 coreconfigitem('smtp', 'tls',
948 default='none',
954 default='none',
949 )
955 )
950 coreconfigitem('smtp', 'username',
956 coreconfigitem('smtp', 'username',
951 default=None,
957 default=None,
952 )
958 )
953 coreconfigitem('sparse', 'missingwarning',
959 coreconfigitem('sparse', 'missingwarning',
954 default=True,
960 default=True,
955 )
961 )
956 coreconfigitem('subrepos', 'allowed',
962 coreconfigitem('subrepos', 'allowed',
957 default=dynamicdefault, # to make backporting simpler
963 default=dynamicdefault, # to make backporting simpler
958 )
964 )
959 coreconfigitem('subrepos', 'hg:allowed',
965 coreconfigitem('subrepos', 'hg:allowed',
960 default=dynamicdefault,
966 default=dynamicdefault,
961 )
967 )
962 coreconfigitem('subrepos', 'git:allowed',
968 coreconfigitem('subrepos', 'git:allowed',
963 default=dynamicdefault,
969 default=dynamicdefault,
964 )
970 )
965 coreconfigitem('subrepos', 'svn:allowed',
971 coreconfigitem('subrepos', 'svn:allowed',
966 default=dynamicdefault,
972 default=dynamicdefault,
967 )
973 )
968 coreconfigitem('templates', '.*',
974 coreconfigitem('templates', '.*',
969 default=None,
975 default=None,
970 generic=True,
976 generic=True,
971 )
977 )
972 coreconfigitem('trusted', 'groups',
978 coreconfigitem('trusted', 'groups',
973 default=list,
979 default=list,
974 )
980 )
975 coreconfigitem('trusted', 'users',
981 coreconfigitem('trusted', 'users',
976 default=list,
982 default=list,
977 )
983 )
978 coreconfigitem('ui', '_usedassubrepo',
984 coreconfigitem('ui', '_usedassubrepo',
979 default=False,
985 default=False,
980 )
986 )
981 coreconfigitem('ui', 'allowemptycommit',
987 coreconfigitem('ui', 'allowemptycommit',
982 default=False,
988 default=False,
983 )
989 )
984 coreconfigitem('ui', 'archivemeta',
990 coreconfigitem('ui', 'archivemeta',
985 default=True,
991 default=True,
986 )
992 )
987 coreconfigitem('ui', 'askusername',
993 coreconfigitem('ui', 'askusername',
988 default=False,
994 default=False,
989 )
995 )
990 coreconfigitem('ui', 'clonebundlefallback',
996 coreconfigitem('ui', 'clonebundlefallback',
991 default=False,
997 default=False,
992 )
998 )
993 coreconfigitem('ui', 'clonebundleprefers',
999 coreconfigitem('ui', 'clonebundleprefers',
994 default=list,
1000 default=list,
995 )
1001 )
996 coreconfigitem('ui', 'clonebundles',
1002 coreconfigitem('ui', 'clonebundles',
997 default=True,
1003 default=True,
998 )
1004 )
999 coreconfigitem('ui', 'color',
1005 coreconfigitem('ui', 'color',
1000 default='auto',
1006 default='auto',
1001 )
1007 )
1002 coreconfigitem('ui', 'commitsubrepos',
1008 coreconfigitem('ui', 'commitsubrepos',
1003 default=False,
1009 default=False,
1004 )
1010 )
1005 coreconfigitem('ui', 'debug',
1011 coreconfigitem('ui', 'debug',
1006 default=False,
1012 default=False,
1007 )
1013 )
1008 coreconfigitem('ui', 'debugger',
1014 coreconfigitem('ui', 'debugger',
1009 default=None,
1015 default=None,
1010 )
1016 )
1011 coreconfigitem('ui', 'editor',
1017 coreconfigitem('ui', 'editor',
1012 default=dynamicdefault,
1018 default=dynamicdefault,
1013 )
1019 )
1014 coreconfigitem('ui', 'fallbackencoding',
1020 coreconfigitem('ui', 'fallbackencoding',
1015 default=None,
1021 default=None,
1016 )
1022 )
1017 coreconfigitem('ui', 'forcecwd',
1023 coreconfigitem('ui', 'forcecwd',
1018 default=None,
1024 default=None,
1019 )
1025 )
1020 coreconfigitem('ui', 'forcemerge',
1026 coreconfigitem('ui', 'forcemerge',
1021 default=None,
1027 default=None,
1022 )
1028 )
1023 coreconfigitem('ui', 'formatdebug',
1029 coreconfigitem('ui', 'formatdebug',
1024 default=False,
1030 default=False,
1025 )
1031 )
1026 coreconfigitem('ui', 'formatjson',
1032 coreconfigitem('ui', 'formatjson',
1027 default=False,
1033 default=False,
1028 )
1034 )
1029 coreconfigitem('ui', 'formatted',
1035 coreconfigitem('ui', 'formatted',
1030 default=None,
1036 default=None,
1031 )
1037 )
1032 coreconfigitem('ui', 'graphnodetemplate',
1038 coreconfigitem('ui', 'graphnodetemplate',
1033 default=None,
1039 default=None,
1034 )
1040 )
1035 coreconfigitem('ui', 'interactive',
1041 coreconfigitem('ui', 'interactive',
1036 default=None,
1042 default=None,
1037 )
1043 )
1038 coreconfigitem('ui', 'interface',
1044 coreconfigitem('ui', 'interface',
1039 default=None,
1045 default=None,
1040 )
1046 )
1041 coreconfigitem('ui', 'interface.chunkselector',
1047 coreconfigitem('ui', 'interface.chunkselector',
1042 default=None,
1048 default=None,
1043 )
1049 )
1044 coreconfigitem('ui', 'logblockedtimes',
1050 coreconfigitem('ui', 'logblockedtimes',
1045 default=False,
1051 default=False,
1046 )
1052 )
1047 coreconfigitem('ui', 'logtemplate',
1053 coreconfigitem('ui', 'logtemplate',
1048 default=None,
1054 default=None,
1049 )
1055 )
1050 coreconfigitem('ui', 'merge',
1056 coreconfigitem('ui', 'merge',
1051 default=None,
1057 default=None,
1052 )
1058 )
1053 coreconfigitem('ui', 'mergemarkers',
1059 coreconfigitem('ui', 'mergemarkers',
1054 default='basic',
1060 default='basic',
1055 )
1061 )
1056 coreconfigitem('ui', 'mergemarkertemplate',
1062 coreconfigitem('ui', 'mergemarkertemplate',
1057 default=('{node|short} '
1063 default=('{node|short} '
1058 '{ifeq(tags, "tip", "", '
1064 '{ifeq(tags, "tip", "", '
1059 'ifeq(tags, "", "", "{tags} "))}'
1065 'ifeq(tags, "", "", "{tags} "))}'
1060 '{if(bookmarks, "{bookmarks} ")}'
1066 '{if(bookmarks, "{bookmarks} ")}'
1061 '{ifeq(branch, "default", "", "{branch} ")}'
1067 '{ifeq(branch, "default", "", "{branch} ")}'
1062 '- {author|user}: {desc|firstline}')
1068 '- {author|user}: {desc|firstline}')
1063 )
1069 )
1064 coreconfigitem('ui', 'nontty',
1070 coreconfigitem('ui', 'nontty',
1065 default=False,
1071 default=False,
1066 )
1072 )
1067 coreconfigitem('ui', 'origbackuppath',
1073 coreconfigitem('ui', 'origbackuppath',
1068 default=None,
1074 default=None,
1069 )
1075 )
1070 coreconfigitem('ui', 'paginate',
1076 coreconfigitem('ui', 'paginate',
1071 default=True,
1077 default=True,
1072 )
1078 )
1073 coreconfigitem('ui', 'patch',
1079 coreconfigitem('ui', 'patch',
1074 default=None,
1080 default=None,
1075 )
1081 )
1076 coreconfigitem('ui', 'portablefilenames',
1082 coreconfigitem('ui', 'portablefilenames',
1077 default='warn',
1083 default='warn',
1078 )
1084 )
1079 coreconfigitem('ui', 'promptecho',
1085 coreconfigitem('ui', 'promptecho',
1080 default=False,
1086 default=False,
1081 )
1087 )
1082 coreconfigitem('ui', 'quiet',
1088 coreconfigitem('ui', 'quiet',
1083 default=False,
1089 default=False,
1084 )
1090 )
1085 coreconfigitem('ui', 'quietbookmarkmove',
1091 coreconfigitem('ui', 'quietbookmarkmove',
1086 default=False,
1092 default=False,
1087 )
1093 )
1088 coreconfigitem('ui', 'remotecmd',
1094 coreconfigitem('ui', 'remotecmd',
1089 default='hg',
1095 default='hg',
1090 )
1096 )
1091 coreconfigitem('ui', 'report_untrusted',
1097 coreconfigitem('ui', 'report_untrusted',
1092 default=True,
1098 default=True,
1093 )
1099 )
1094 coreconfigitem('ui', 'rollback',
1100 coreconfigitem('ui', 'rollback',
1095 default=True,
1101 default=True,
1096 )
1102 )
1097 coreconfigitem('ui', 'slash',
1103 coreconfigitem('ui', 'slash',
1098 default=False,
1104 default=False,
1099 )
1105 )
1100 coreconfigitem('ui', 'ssh',
1106 coreconfigitem('ui', 'ssh',
1101 default='ssh',
1107 default='ssh',
1102 )
1108 )
1103 coreconfigitem('ui', 'ssherrorhint',
1109 coreconfigitem('ui', 'ssherrorhint',
1104 default=None,
1110 default=None,
1105 )
1111 )
1106 coreconfigitem('ui', 'statuscopies',
1112 coreconfigitem('ui', 'statuscopies',
1107 default=False,
1113 default=False,
1108 )
1114 )
1109 coreconfigitem('ui', 'strict',
1115 coreconfigitem('ui', 'strict',
1110 default=False,
1116 default=False,
1111 )
1117 )
1112 coreconfigitem('ui', 'style',
1118 coreconfigitem('ui', 'style',
1113 default='',
1119 default='',
1114 )
1120 )
1115 coreconfigitem('ui', 'supportcontact',
1121 coreconfigitem('ui', 'supportcontact',
1116 default=None,
1122 default=None,
1117 )
1123 )
1118 coreconfigitem('ui', 'textwidth',
1124 coreconfigitem('ui', 'textwidth',
1119 default=78,
1125 default=78,
1120 )
1126 )
1121 coreconfigitem('ui', 'timeout',
1127 coreconfigitem('ui', 'timeout',
1122 default='600',
1128 default='600',
1123 )
1129 )
1124 coreconfigitem('ui', 'timeout.warn',
1130 coreconfigitem('ui', 'timeout.warn',
1125 default=0,
1131 default=0,
1126 )
1132 )
1127 coreconfigitem('ui', 'traceback',
1133 coreconfigitem('ui', 'traceback',
1128 default=False,
1134 default=False,
1129 )
1135 )
1130 coreconfigitem('ui', 'tweakdefaults',
1136 coreconfigitem('ui', 'tweakdefaults',
1131 default=False,
1137 default=False,
1132 )
1138 )
1133 coreconfigitem('ui', 'username',
1139 coreconfigitem('ui', 'username',
1134 alias=[('ui', 'user')]
1140 alias=[('ui', 'user')]
1135 )
1141 )
1136 coreconfigitem('ui', 'verbose',
1142 coreconfigitem('ui', 'verbose',
1137 default=False,
1143 default=False,
1138 )
1144 )
1139 coreconfigitem('verify', 'skipflags',
1145 coreconfigitem('verify', 'skipflags',
1140 default=None,
1146 default=None,
1141 )
1147 )
1142 coreconfigitem('web', 'allowbz2',
1148 coreconfigitem('web', 'allowbz2',
1143 default=False,
1149 default=False,
1144 )
1150 )
1145 coreconfigitem('web', 'allowgz',
1151 coreconfigitem('web', 'allowgz',
1146 default=False,
1152 default=False,
1147 )
1153 )
1148 coreconfigitem('web', 'allow-pull',
1154 coreconfigitem('web', 'allow-pull',
1149 alias=[('web', 'allowpull')],
1155 alias=[('web', 'allowpull')],
1150 default=True,
1156 default=True,
1151 )
1157 )
1152 coreconfigitem('web', 'allow-push',
1158 coreconfigitem('web', 'allow-push',
1153 alias=[('web', 'allow_push')],
1159 alias=[('web', 'allow_push')],
1154 default=list,
1160 default=list,
1155 )
1161 )
1156 coreconfigitem('web', 'allowzip',
1162 coreconfigitem('web', 'allowzip',
1157 default=False,
1163 default=False,
1158 )
1164 )
1159 coreconfigitem('web', 'archivesubrepos',
1165 coreconfigitem('web', 'archivesubrepos',
1160 default=False,
1166 default=False,
1161 )
1167 )
1162 coreconfigitem('web', 'cache',
1168 coreconfigitem('web', 'cache',
1163 default=True,
1169 default=True,
1164 )
1170 )
1165 coreconfigitem('web', 'contact',
1171 coreconfigitem('web', 'contact',
1166 default=None,
1172 default=None,
1167 )
1173 )
1168 coreconfigitem('web', 'deny_push',
1174 coreconfigitem('web', 'deny_push',
1169 default=list,
1175 default=list,
1170 )
1176 )
1171 coreconfigitem('web', 'guessmime',
1177 coreconfigitem('web', 'guessmime',
1172 default=False,
1178 default=False,
1173 )
1179 )
1174 coreconfigitem('web', 'hidden',
1180 coreconfigitem('web', 'hidden',
1175 default=False,
1181 default=False,
1176 )
1182 )
1177 coreconfigitem('web', 'labels',
1183 coreconfigitem('web', 'labels',
1178 default=list,
1184 default=list,
1179 )
1185 )
1180 coreconfigitem('web', 'logoimg',
1186 coreconfigitem('web', 'logoimg',
1181 default='hglogo.png',
1187 default='hglogo.png',
1182 )
1188 )
1183 coreconfigitem('web', 'logourl',
1189 coreconfigitem('web', 'logourl',
1184 default='https://mercurial-scm.org/',
1190 default='https://mercurial-scm.org/',
1185 )
1191 )
1186 coreconfigitem('web', 'accesslog',
1192 coreconfigitem('web', 'accesslog',
1187 default='-',
1193 default='-',
1188 )
1194 )
1189 coreconfigitem('web', 'address',
1195 coreconfigitem('web', 'address',
1190 default='',
1196 default='',
1191 )
1197 )
1192 coreconfigitem('web', 'allow_archive',
1198 coreconfigitem('web', 'allow_archive',
1193 default=list,
1199 default=list,
1194 )
1200 )
1195 coreconfigitem('web', 'allow_read',
1201 coreconfigitem('web', 'allow_read',
1196 default=list,
1202 default=list,
1197 )
1203 )
1198 coreconfigitem('web', 'baseurl',
1204 coreconfigitem('web', 'baseurl',
1199 default=None,
1205 default=None,
1200 )
1206 )
1201 coreconfigitem('web', 'cacerts',
1207 coreconfigitem('web', 'cacerts',
1202 default=None,
1208 default=None,
1203 )
1209 )
1204 coreconfigitem('web', 'certificate',
1210 coreconfigitem('web', 'certificate',
1205 default=None,
1211 default=None,
1206 )
1212 )
1207 coreconfigitem('web', 'collapse',
1213 coreconfigitem('web', 'collapse',
1208 default=False,
1214 default=False,
1209 )
1215 )
1210 coreconfigitem('web', 'csp',
1216 coreconfigitem('web', 'csp',
1211 default=None,
1217 default=None,
1212 )
1218 )
1213 coreconfigitem('web', 'deny_read',
1219 coreconfigitem('web', 'deny_read',
1214 default=list,
1220 default=list,
1215 )
1221 )
1216 coreconfigitem('web', 'descend',
1222 coreconfigitem('web', 'descend',
1217 default=True,
1223 default=True,
1218 )
1224 )
1219 coreconfigitem('web', 'description',
1225 coreconfigitem('web', 'description',
1220 default="",
1226 default="",
1221 )
1227 )
1222 coreconfigitem('web', 'encoding',
1228 coreconfigitem('web', 'encoding',
1223 default=lambda: encoding.encoding,
1229 default=lambda: encoding.encoding,
1224 )
1230 )
1225 coreconfigitem('web', 'errorlog',
1231 coreconfigitem('web', 'errorlog',
1226 default='-',
1232 default='-',
1227 )
1233 )
1228 coreconfigitem('web', 'ipv6',
1234 coreconfigitem('web', 'ipv6',
1229 default=False,
1235 default=False,
1230 )
1236 )
1231 coreconfigitem('web', 'maxchanges',
1237 coreconfigitem('web', 'maxchanges',
1232 default=10,
1238 default=10,
1233 )
1239 )
1234 coreconfigitem('web', 'maxfiles',
1240 coreconfigitem('web', 'maxfiles',
1235 default=10,
1241 default=10,
1236 )
1242 )
1237 coreconfigitem('web', 'maxshortchanges',
1243 coreconfigitem('web', 'maxshortchanges',
1238 default=60,
1244 default=60,
1239 )
1245 )
1240 coreconfigitem('web', 'motd',
1246 coreconfigitem('web', 'motd',
1241 default='',
1247 default='',
1242 )
1248 )
1243 coreconfigitem('web', 'name',
1249 coreconfigitem('web', 'name',
1244 default=dynamicdefault,
1250 default=dynamicdefault,
1245 )
1251 )
1246 coreconfigitem('web', 'port',
1252 coreconfigitem('web', 'port',
1247 default=8000,
1253 default=8000,
1248 )
1254 )
1249 coreconfigitem('web', 'prefix',
1255 coreconfigitem('web', 'prefix',
1250 default='',
1256 default='',
1251 )
1257 )
1252 coreconfigitem('web', 'push_ssl',
1258 coreconfigitem('web', 'push_ssl',
1253 default=True,
1259 default=True,
1254 )
1260 )
1255 coreconfigitem('web', 'refreshinterval',
1261 coreconfigitem('web', 'refreshinterval',
1256 default=20,
1262 default=20,
1257 )
1263 )
1258 coreconfigitem('web', 'server-header',
1264 coreconfigitem('web', 'server-header',
1259 default=None,
1265 default=None,
1260 )
1266 )
1261 coreconfigitem('web', 'staticurl',
1267 coreconfigitem('web', 'staticurl',
1262 default=None,
1268 default=None,
1263 )
1269 )
1264 coreconfigitem('web', 'stripes',
1270 coreconfigitem('web', 'stripes',
1265 default=1,
1271 default=1,
1266 )
1272 )
1267 coreconfigitem('web', 'style',
1273 coreconfigitem('web', 'style',
1268 default='paper',
1274 default='paper',
1269 )
1275 )
1270 coreconfigitem('web', 'templates',
1276 coreconfigitem('web', 'templates',
1271 default=None,
1277 default=None,
1272 )
1278 )
1273 coreconfigitem('web', 'view',
1279 coreconfigitem('web', 'view',
1274 default='served',
1280 default='served',
1275 )
1281 )
1276 coreconfigitem('worker', 'backgroundclose',
1282 coreconfigitem('worker', 'backgroundclose',
1277 default=dynamicdefault,
1283 default=dynamicdefault,
1278 )
1284 )
1279 # Windows defaults to a limit of 512 open files. A buffer of 128
1285 # Windows defaults to a limit of 512 open files. A buffer of 128
1280 # should give us enough headway.
1286 # should give us enough headway.
1281 coreconfigitem('worker', 'backgroundclosemaxqueue',
1287 coreconfigitem('worker', 'backgroundclosemaxqueue',
1282 default=384,
1288 default=384,
1283 )
1289 )
1284 coreconfigitem('worker', 'backgroundcloseminfilecount',
1290 coreconfigitem('worker', 'backgroundcloseminfilecount',
1285 default=2048,
1291 default=2048,
1286 )
1292 )
1287 coreconfigitem('worker', 'backgroundclosethreadcount',
1293 coreconfigitem('worker', 'backgroundclosethreadcount',
1288 default=4,
1294 default=4,
1289 )
1295 )
1290 coreconfigitem('worker', 'enabled',
1296 coreconfigitem('worker', 'enabled',
1291 default=True,
1297 default=True,
1292 )
1298 )
1293 coreconfigitem('worker', 'numcpus',
1299 coreconfigitem('worker', 'numcpus',
1294 default=None,
1300 default=None,
1295 )
1301 )
1296
1302
1297 # Rebase related configuration moved to core because other extension are doing
1303 # Rebase related configuration moved to core because other extension are doing
1298 # strange things. For example, shelve import the extensions to reuse some bit
1304 # strange things. For example, shelve import the extensions to reuse some bit
1299 # without formally loading it.
1305 # without formally loading it.
1300 coreconfigitem('commands', 'rebase.requiredest',
1306 coreconfigitem('commands', 'rebase.requiredest',
1301 default=False,
1307 default=False,
1302 )
1308 )
1303 coreconfigitem('experimental', 'rebaseskipobsolete',
1309 coreconfigitem('experimental', 'rebaseskipobsolete',
1304 default=True,
1310 default=True,
1305 )
1311 )
1306 coreconfigitem('rebase', 'singletransaction',
1312 coreconfigitem('rebase', 'singletransaction',
1307 default=False,
1313 default=False,
1308 )
1314 )
1309 coreconfigitem('rebase', 'experimental.inmemory',
1315 coreconfigitem('rebase', 'experimental.inmemory',
1310 default=False,
1316 default=False,
1311 )
1317 )
@@ -1,454 +1,461 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 contextlib
11 import contextlib
12 import os
12 import os
13
13
14 from .common import (
14 from .common import (
15 ErrorResponse,
15 ErrorResponse,
16 HTTP_BAD_REQUEST,
16 HTTP_BAD_REQUEST,
17 cspvalues,
17 cspvalues,
18 permhooks,
18 permhooks,
19 statusmessage,
19 statusmessage,
20 )
20 )
21
21
22 from .. import (
22 from .. import (
23 encoding,
23 encoding,
24 error,
24 error,
25 formatter,
25 formatter,
26 hg,
26 hg,
27 hook,
27 hook,
28 profiling,
28 profiling,
29 pycompat,
29 pycompat,
30 repoview,
30 repoview,
31 templatefilters,
31 templatefilters,
32 templater,
32 templater,
33 ui as uimod,
33 ui as uimod,
34 util,
34 util,
35 wireprotoserver,
35 wireprotoserver,
36 )
36 )
37
37
38 from . import (
38 from . import (
39 request as requestmod,
39 request as requestmod,
40 webcommands,
40 webcommands,
41 webutil,
41 webutil,
42 wsgicgi,
42 wsgicgi,
43 )
43 )
44
44
45 archivespecs = util.sortdict((
45 archivespecs = util.sortdict((
46 ('zip', ('application/zip', 'zip', '.zip', None)),
46 ('zip', ('application/zip', 'zip', '.zip', None)),
47 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
47 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
48 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
48 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
49 ))
49 ))
50
50
51 def getstyle(req, configfn, templatepath):
51 def getstyle(req, configfn, templatepath):
52 styles = (
52 styles = (
53 req.qsparams.get('style', None),
53 req.qsparams.get('style', None),
54 configfn('web', 'style'),
54 configfn('web', 'style'),
55 'paper',
55 'paper',
56 )
56 )
57 return styles, templater.stylemap(styles, templatepath)
57 return styles, templater.stylemap(styles, templatepath)
58
58
59 def makebreadcrumb(url, prefix=''):
59 def makebreadcrumb(url, prefix=''):
60 '''Return a 'URL breadcrumb' list
60 '''Return a 'URL breadcrumb' list
61
61
62 A 'URL breadcrumb' is a list of URL-name pairs,
62 A 'URL breadcrumb' is a list of URL-name pairs,
63 corresponding to each of the path items on a URL.
63 corresponding to each of the path items on a URL.
64 This can be used to create path navigation entries.
64 This can be used to create path navigation entries.
65 '''
65 '''
66 if url.endswith('/'):
66 if url.endswith('/'):
67 url = url[:-1]
67 url = url[:-1]
68 if prefix:
68 if prefix:
69 url = '/' + prefix + url
69 url = '/' + prefix + url
70 relpath = url
70 relpath = url
71 if relpath.startswith('/'):
71 if relpath.startswith('/'):
72 relpath = relpath[1:]
72 relpath = relpath[1:]
73
73
74 breadcrumb = []
74 breadcrumb = []
75 urlel = url
75 urlel = url
76 pathitems = [''] + relpath.split('/')
76 pathitems = [''] + relpath.split('/')
77 for pathel in reversed(pathitems):
77 for pathel in reversed(pathitems):
78 if not pathel or not urlel:
78 if not pathel or not urlel:
79 break
79 break
80 breadcrumb.append({'url': urlel, 'name': pathel})
80 breadcrumb.append({'url': urlel, 'name': pathel})
81 urlel = os.path.dirname(urlel)
81 urlel = os.path.dirname(urlel)
82 return reversed(breadcrumb)
82 return reversed(breadcrumb)
83
83
84 class requestcontext(object):
84 class requestcontext(object):
85 """Holds state/context for an individual request.
85 """Holds state/context for an individual request.
86
86
87 Servers can be multi-threaded. Holding state on the WSGI application
87 Servers can be multi-threaded. Holding state on the WSGI application
88 is prone to race conditions. Instances of this class exist to hold
88 is prone to race conditions. Instances of this class exist to hold
89 mutable and race-free state for requests.
89 mutable and race-free state for requests.
90 """
90 """
91 def __init__(self, app, repo, req, res):
91 def __init__(self, app, repo, req, res):
92 self.repo = repo
92 self.repo = repo
93 self.reponame = app.reponame
93 self.reponame = app.reponame
94 self.req = req
94 self.req = req
95 self.res = res
95 self.res = res
96
96
97 self.archivespecs = archivespecs
97 self.archivespecs = archivespecs
98
98
99 self.maxchanges = self.configint('web', 'maxchanges')
99 self.maxchanges = self.configint('web', 'maxchanges')
100 self.stripecount = self.configint('web', 'stripes')
100 self.stripecount = self.configint('web', 'stripes')
101 self.maxshortchanges = self.configint('web', 'maxshortchanges')
101 self.maxshortchanges = self.configint('web', 'maxshortchanges')
102 self.maxfiles = self.configint('web', 'maxfiles')
102 self.maxfiles = self.configint('web', 'maxfiles')
103 self.allowpull = self.configbool('web', 'allow-pull')
103 self.allowpull = self.configbool('web', 'allow-pull')
104
104
105 # we use untrusted=False to prevent a repo owner from using
105 # we use untrusted=False to prevent a repo owner from using
106 # web.templates in .hg/hgrc to get access to any file readable
106 # web.templates in .hg/hgrc to get access to any file readable
107 # by the user running the CGI script
107 # by the user running the CGI script
108 self.templatepath = self.config('web', 'templates', untrusted=False)
108 self.templatepath = self.config('web', 'templates', untrusted=False)
109
109
110 # This object is more expensive to build than simple config values.
110 # This object is more expensive to build than simple config values.
111 # It is shared across requests. The app will replace the object
111 # It is shared across requests. The app will replace the object
112 # if it is updated. Since this is a reference and nothing should
112 # if it is updated. Since this is a reference and nothing should
113 # modify the underlying object, it should be constant for the lifetime
113 # modify the underlying object, it should be constant for the lifetime
114 # of the request.
114 # of the request.
115 self.websubtable = app.websubtable
115 self.websubtable = app.websubtable
116
116
117 self.csp, self.nonce = cspvalues(self.repo.ui)
117 self.csp, self.nonce = cspvalues(self.repo.ui)
118
118
119 # Trust the settings from the .hg/hgrc files by default.
119 # Trust the settings from the .hg/hgrc files by default.
120 def config(self, section, name, default=uimod._unset, untrusted=True):
120 def config(self, section, name, default=uimod._unset, untrusted=True):
121 return self.repo.ui.config(section, name, default,
121 return self.repo.ui.config(section, name, default,
122 untrusted=untrusted)
122 untrusted=untrusted)
123
123
124 def configbool(self, section, name, default=uimod._unset, untrusted=True):
124 def configbool(self, section, name, default=uimod._unset, untrusted=True):
125 return self.repo.ui.configbool(section, name, default,
125 return self.repo.ui.configbool(section, name, default,
126 untrusted=untrusted)
126 untrusted=untrusted)
127
127
128 def configint(self, section, name, default=uimod._unset, untrusted=True):
128 def configint(self, section, name, default=uimod._unset, untrusted=True):
129 return self.repo.ui.configint(section, name, default,
129 return self.repo.ui.configint(section, name, default,
130 untrusted=untrusted)
130 untrusted=untrusted)
131
131
132 def configlist(self, section, name, default=uimod._unset, untrusted=True):
132 def configlist(self, section, name, default=uimod._unset, untrusted=True):
133 return self.repo.ui.configlist(section, name, default,
133 return self.repo.ui.configlist(section, name, default,
134 untrusted=untrusted)
134 untrusted=untrusted)
135
135
136 def archivelist(self, nodeid):
136 def archivelist(self, nodeid):
137 allowed = self.configlist('web', 'allow_archive')
137 allowed = self.configlist('web', 'allow_archive')
138 for typ, spec in self.archivespecs.iteritems():
138 for typ, spec in self.archivespecs.iteritems():
139 if typ in allowed or self.configbool('web', 'allow%s' % typ):
139 if typ in allowed or self.configbool('web', 'allow%s' % typ):
140 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
140 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
141
141
142 def templater(self, req):
142 def templater(self, req):
143 # determine scheme, port and server name
143 # determine scheme, port and server name
144 # this is needed to create absolute urls
144 # this is needed to create absolute urls
145 logourl = self.config('web', 'logourl')
145 logourl = self.config('web', 'logourl')
146 logoimg = self.config('web', 'logoimg')
146 logoimg = self.config('web', 'logoimg')
147 staticurl = (self.config('web', 'staticurl')
147 staticurl = (self.config('web', 'staticurl')
148 or req.apppath + '/static/')
148 or req.apppath + '/static/')
149 if not staticurl.endswith('/'):
149 if not staticurl.endswith('/'):
150 staticurl += '/'
150 staticurl += '/'
151
151
152 # some functions for the templater
152 # some functions for the templater
153
153
154 def motd(**map):
154 def motd(**map):
155 yield self.config('web', 'motd')
155 yield self.config('web', 'motd')
156
156
157 # figure out which style to use
157 # figure out which style to use
158
158
159 vars = {}
159 vars = {}
160 styles, (style, mapfile) = getstyle(req, self.config,
160 styles, (style, mapfile) = getstyle(req, self.config,
161 self.templatepath)
161 self.templatepath)
162 if style == styles[0]:
162 if style == styles[0]:
163 vars['style'] = style
163 vars['style'] = style
164
164
165 sessionvars = webutil.sessionvars(vars, '?')
165 sessionvars = webutil.sessionvars(vars, '?')
166
166
167 if not self.reponame:
167 if not self.reponame:
168 self.reponame = (self.config('web', 'name', '')
168 self.reponame = (self.config('web', 'name', '')
169 or req.reponame
169 or req.reponame
170 or req.apppath
170 or req.apppath
171 or self.repo.root)
171 or self.repo.root)
172
172
173 def websubfilter(text):
173 def websubfilter(text):
174 return templatefilters.websub(text, self.websubtable)
174 return templatefilters.websub(text, self.websubtable)
175
175
176 # create the templater
176 # create the templater
177 # TODO: export all keywords: defaults = templatekw.keywords.copy()
177 # TODO: export all keywords: defaults = templatekw.keywords.copy()
178 defaults = {
178 defaults = {
179 'url': req.apppath + '/',
179 'url': req.apppath + '/',
180 'logourl': logourl,
180 'logourl': logourl,
181 'logoimg': logoimg,
181 'logoimg': logoimg,
182 'staticurl': staticurl,
182 'staticurl': staticurl,
183 'urlbase': req.advertisedbaseurl,
183 'urlbase': req.advertisedbaseurl,
184 'repo': self.reponame,
184 'repo': self.reponame,
185 'encoding': encoding.encoding,
185 'encoding': encoding.encoding,
186 'motd': motd,
186 'motd': motd,
187 'sessionvars': sessionvars,
187 'sessionvars': sessionvars,
188 'pathdef': makebreadcrumb(req.apppath),
188 'pathdef': makebreadcrumb(req.apppath),
189 'style': style,
189 'style': style,
190 'nonce': self.nonce,
190 'nonce': self.nonce,
191 }
191 }
192 tres = formatter.templateresources(self.repo.ui, self.repo)
192 tres = formatter.templateresources(self.repo.ui, self.repo)
193 tmpl = templater.templater.frommapfile(mapfile,
193 tmpl = templater.templater.frommapfile(mapfile,
194 filters={'websub': websubfilter},
194 filters={'websub': websubfilter},
195 defaults=defaults,
195 defaults=defaults,
196 resources=tres)
196 resources=tres)
197 return tmpl
197 return tmpl
198
198
199 def sendtemplate(self, name, **kwargs):
199 def sendtemplate(self, name, **kwargs):
200 """Helper function to send a response generated from a template."""
200 """Helper function to send a response generated from a template."""
201 kwargs = pycompat.byteskwargs(kwargs)
201 kwargs = pycompat.byteskwargs(kwargs)
202 self.res.setbodygen(self.tmpl.generate(name, kwargs))
202 self.res.setbodygen(self.tmpl.generate(name, kwargs))
203 return self.res.sendresponse()
203 return self.res.sendresponse()
204
204
205 class hgweb(object):
205 class hgweb(object):
206 """HTTP server for individual repositories.
206 """HTTP server for individual repositories.
207
207
208 Instances of this class serve HTTP responses for a particular
208 Instances of this class serve HTTP responses for a particular
209 repository.
209 repository.
210
210
211 Instances are typically used as WSGI applications.
211 Instances are typically used as WSGI applications.
212
212
213 Some servers are multi-threaded. On these servers, there may
213 Some servers are multi-threaded. On these servers, there may
214 be multiple active threads inside __call__.
214 be multiple active threads inside __call__.
215 """
215 """
216 def __init__(self, repo, name=None, baseui=None):
216 def __init__(self, repo, name=None, baseui=None):
217 if isinstance(repo, str):
217 if isinstance(repo, str):
218 if baseui:
218 if baseui:
219 u = baseui.copy()
219 u = baseui.copy()
220 else:
220 else:
221 u = uimod.ui.load()
221 u = uimod.ui.load()
222 r = hg.repository(u, repo)
222 r = hg.repository(u, repo)
223 else:
223 else:
224 # we trust caller to give us a private copy
224 # we trust caller to give us a private copy
225 r = repo
225 r = repo
226
226
227 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
227 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
228 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
228 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
229 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
229 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
230 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
230 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
231 # resolve file patterns relative to repo root
231 # resolve file patterns relative to repo root
232 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
232 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
233 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
233 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
234 # displaying bundling progress bar while serving feel wrong and may
234 # displaying bundling progress bar while serving feel wrong and may
235 # break some wsgi implementation.
235 # break some wsgi implementation.
236 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
236 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
237 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
237 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
238 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
238 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
239 self._lastrepo = self._repos[0]
239 self._lastrepo = self._repos[0]
240 hook.redirect(True)
240 hook.redirect(True)
241 self.reponame = name
241 self.reponame = name
242
242
243 def _webifyrepo(self, repo):
243 def _webifyrepo(self, repo):
244 repo = getwebview(repo)
244 repo = getwebview(repo)
245 self.websubtable = webutil.getwebsubs(repo)
245 self.websubtable = webutil.getwebsubs(repo)
246 return repo
246 return repo
247
247
248 @contextlib.contextmanager
248 @contextlib.contextmanager
249 def _obtainrepo(self):
249 def _obtainrepo(self):
250 """Obtain a repo unique to the caller.
250 """Obtain a repo unique to the caller.
251
251
252 Internally we maintain a stack of cachedlocalrepo instances
252 Internally we maintain a stack of cachedlocalrepo instances
253 to be handed out. If one is available, we pop it and return it,
253 to be handed out. If one is available, we pop it and return it,
254 ensuring it is up to date in the process. If one is not available,
254 ensuring it is up to date in the process. If one is not available,
255 we clone the most recently used repo instance and return it.
255 we clone the most recently used repo instance and return it.
256
256
257 It is currently possible for the stack to grow without bounds
257 It is currently possible for the stack to grow without bounds
258 if the server allows infinite threads. However, servers should
258 if the server allows infinite threads. However, servers should
259 have a thread limit, thus establishing our limit.
259 have a thread limit, thus establishing our limit.
260 """
260 """
261 if self._repos:
261 if self._repos:
262 cached = self._repos.pop()
262 cached = self._repos.pop()
263 r, created = cached.fetch()
263 r, created = cached.fetch()
264 else:
264 else:
265 cached = self._lastrepo.copy()
265 cached = self._lastrepo.copy()
266 r, created = cached.fetch()
266 r, created = cached.fetch()
267 if created:
267 if created:
268 r = self._webifyrepo(r)
268 r = self._webifyrepo(r)
269
269
270 self._lastrepo = cached
270 self._lastrepo = cached
271 self.mtime = cached.mtime
271 self.mtime = cached.mtime
272 try:
272 try:
273 yield r
273 yield r
274 finally:
274 finally:
275 self._repos.append(cached)
275 self._repos.append(cached)
276
276
277 def run(self):
277 def run(self):
278 """Start a server from CGI environment.
278 """Start a server from CGI environment.
279
279
280 Modern servers should be using WSGI and should avoid this
280 Modern servers should be using WSGI and should avoid this
281 method, if possible.
281 method, if possible.
282 """
282 """
283 if not encoding.environ.get('GATEWAY_INTERFACE',
283 if not encoding.environ.get('GATEWAY_INTERFACE',
284 '').startswith("CGI/1."):
284 '').startswith("CGI/1."):
285 raise RuntimeError("This function is only intended to be "
285 raise RuntimeError("This function is only intended to be "
286 "called while running as a CGI script.")
286 "called while running as a CGI script.")
287 wsgicgi.launch(self)
287 wsgicgi.launch(self)
288
288
289 def __call__(self, env, respond):
289 def __call__(self, env, respond):
290 """Run the WSGI application.
290 """Run the WSGI application.
291
291
292 This may be called by multiple threads.
292 This may be called by multiple threads.
293 """
293 """
294 req = requestmod.parserequestfromenv(env)
294 req = requestmod.parserequestfromenv(env)
295 res = requestmod.wsgiresponse(req, respond)
295 res = requestmod.wsgiresponse(req, respond)
296
296
297 return self.run_wsgi(req, res)
297 return self.run_wsgi(req, res)
298
298
299 def run_wsgi(self, req, res):
299 def run_wsgi(self, req, res):
300 """Internal method to run the WSGI application.
300 """Internal method to run the WSGI application.
301
301
302 This is typically only called by Mercurial. External consumers
302 This is typically only called by Mercurial. External consumers
303 should be using instances of this class as the WSGI application.
303 should be using instances of this class as the WSGI application.
304 """
304 """
305 with self._obtainrepo() as repo:
305 with self._obtainrepo() as repo:
306 profile = repo.ui.configbool('profiling', 'enabled')
306 profile = repo.ui.configbool('profiling', 'enabled')
307 with profiling.profile(repo.ui, enabled=profile):
307 with profiling.profile(repo.ui, enabled=profile):
308 for r in self._runwsgi(req, res, repo):
308 for r in self._runwsgi(req, res, repo):
309 yield r
309 yield r
310
310
311 def _runwsgi(self, req, res, repo):
311 def _runwsgi(self, req, res, repo):
312 rctx = requestcontext(self, repo, req, res)
312 rctx = requestcontext(self, repo, req, res)
313
313
314 # This state is global across all threads.
314 # This state is global across all threads.
315 encoding.encoding = rctx.config('web', 'encoding')
315 encoding.encoding = rctx.config('web', 'encoding')
316 rctx.repo.ui.environ = req.rawenv
316 rctx.repo.ui.environ = req.rawenv
317
317
318 if rctx.csp:
318 if rctx.csp:
319 # hgwebdir may have added CSP header. Since we generate our own,
319 # hgwebdir may have added CSP header. Since we generate our own,
320 # replace it.
320 # replace it.
321 res.headers['Content-Security-Policy'] = rctx.csp
321 res.headers['Content-Security-Policy'] = rctx.csp
322
322
323 # /api/* is reserved for various API implementations. Dispatch
324 # accordingly.
325 if req.dispatchparts and req.dispatchparts[0] == b'api':
326 wireprotoserver.handlewsgiapirequest(rctx, req, res,
327 self.check_perm)
328 return res.sendresponse()
329
323 handled = wireprotoserver.handlewsgirequest(
330 handled = wireprotoserver.handlewsgirequest(
324 rctx, req, res, self.check_perm)
331 rctx, req, res, self.check_perm)
325 if handled:
332 if handled:
326 return res.sendresponse()
333 return res.sendresponse()
327
334
328 # Old implementations of hgweb supported dispatching the request via
335 # Old implementations of hgweb supported dispatching the request via
329 # the initial query string parameter instead of using PATH_INFO.
336 # the initial query string parameter instead of using PATH_INFO.
330 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
337 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
331 # a value), we use it. Otherwise fall back to the query string.
338 # a value), we use it. Otherwise fall back to the query string.
332 if req.dispatchpath is not None:
339 if req.dispatchpath is not None:
333 query = req.dispatchpath
340 query = req.dispatchpath
334 else:
341 else:
335 query = req.querystring.partition('&')[0].partition(';')[0]
342 query = req.querystring.partition('&')[0].partition(';')[0]
336
343
337 # translate user-visible url structure to internal structure
344 # translate user-visible url structure to internal structure
338
345
339 args = query.split('/', 2)
346 args = query.split('/', 2)
340 if 'cmd' not in req.qsparams and args and args[0]:
347 if 'cmd' not in req.qsparams and args and args[0]:
341 cmd = args.pop(0)
348 cmd = args.pop(0)
342 style = cmd.rfind('-')
349 style = cmd.rfind('-')
343 if style != -1:
350 if style != -1:
344 req.qsparams['style'] = cmd[:style]
351 req.qsparams['style'] = cmd[:style]
345 cmd = cmd[style + 1:]
352 cmd = cmd[style + 1:]
346
353
347 # avoid accepting e.g. style parameter as command
354 # avoid accepting e.g. style parameter as command
348 if util.safehasattr(webcommands, cmd):
355 if util.safehasattr(webcommands, cmd):
349 req.qsparams['cmd'] = cmd
356 req.qsparams['cmd'] = cmd
350
357
351 if cmd == 'static':
358 if cmd == 'static':
352 req.qsparams['file'] = '/'.join(args)
359 req.qsparams['file'] = '/'.join(args)
353 else:
360 else:
354 if args and args[0]:
361 if args and args[0]:
355 node = args.pop(0).replace('%2F', '/')
362 node = args.pop(0).replace('%2F', '/')
356 req.qsparams['node'] = node
363 req.qsparams['node'] = node
357 if args:
364 if args:
358 if 'file' in req.qsparams:
365 if 'file' in req.qsparams:
359 del req.qsparams['file']
366 del req.qsparams['file']
360 for a in args:
367 for a in args:
361 req.qsparams.add('file', a)
368 req.qsparams.add('file', a)
362
369
363 ua = req.headers.get('User-Agent', '')
370 ua = req.headers.get('User-Agent', '')
364 if cmd == 'rev' and 'mercurial' in ua:
371 if cmd == 'rev' and 'mercurial' in ua:
365 req.qsparams['style'] = 'raw'
372 req.qsparams['style'] = 'raw'
366
373
367 if cmd == 'archive':
374 if cmd == 'archive':
368 fn = req.qsparams['node']
375 fn = req.qsparams['node']
369 for type_, spec in rctx.archivespecs.iteritems():
376 for type_, spec in rctx.archivespecs.iteritems():
370 ext = spec[2]
377 ext = spec[2]
371 if fn.endswith(ext):
378 if fn.endswith(ext):
372 req.qsparams['node'] = fn[:-len(ext)]
379 req.qsparams['node'] = fn[:-len(ext)]
373 req.qsparams['type'] = type_
380 req.qsparams['type'] = type_
374 else:
381 else:
375 cmd = req.qsparams.get('cmd', '')
382 cmd = req.qsparams.get('cmd', '')
376
383
377 # process the web interface request
384 # process the web interface request
378
385
379 try:
386 try:
380 rctx.tmpl = rctx.templater(req)
387 rctx.tmpl = rctx.templater(req)
381 ctype = rctx.tmpl.render('mimetype',
388 ctype = rctx.tmpl.render('mimetype',
382 {'encoding': encoding.encoding})
389 {'encoding': encoding.encoding})
383
390
384 # check read permissions non-static content
391 # check read permissions non-static content
385 if cmd != 'static':
392 if cmd != 'static':
386 self.check_perm(rctx, req, None)
393 self.check_perm(rctx, req, None)
387
394
388 if cmd == '':
395 if cmd == '':
389 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
396 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
390 cmd = req.qsparams['cmd']
397 cmd = req.qsparams['cmd']
391
398
392 # Don't enable caching if using a CSP nonce because then it wouldn't
399 # Don't enable caching if using a CSP nonce because then it wouldn't
393 # be a nonce.
400 # be a nonce.
394 if rctx.configbool('web', 'cache') and not rctx.nonce:
401 if rctx.configbool('web', 'cache') and not rctx.nonce:
395 tag = 'W/"%d"' % self.mtime
402 tag = 'W/"%d"' % self.mtime
396 if req.headers.get('If-None-Match') == tag:
403 if req.headers.get('If-None-Match') == tag:
397 res.status = '304 Not Modified'
404 res.status = '304 Not Modified'
398 # Response body not allowed on 304.
405 # Response body not allowed on 304.
399 res.setbodybytes('')
406 res.setbodybytes('')
400 return res.sendresponse()
407 return res.sendresponse()
401
408
402 res.headers['ETag'] = tag
409 res.headers['ETag'] = tag
403
410
404 if cmd not in webcommands.__all__:
411 if cmd not in webcommands.__all__:
405 msg = 'no such method: %s' % cmd
412 msg = 'no such method: %s' % cmd
406 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
413 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
407 else:
414 else:
408 # Set some globals appropriate for web handlers. Commands can
415 # Set some globals appropriate for web handlers. Commands can
409 # override easily enough.
416 # override easily enough.
410 res.status = '200 Script output follows'
417 res.status = '200 Script output follows'
411 res.headers['Content-Type'] = ctype
418 res.headers['Content-Type'] = ctype
412 return getattr(webcommands, cmd)(rctx)
419 return getattr(webcommands, cmd)(rctx)
413
420
414 except (error.LookupError, error.RepoLookupError) as err:
421 except (error.LookupError, error.RepoLookupError) as err:
415 msg = pycompat.bytestr(err)
422 msg = pycompat.bytestr(err)
416 if (util.safehasattr(err, 'name') and
423 if (util.safehasattr(err, 'name') and
417 not isinstance(err, error.ManifestLookupError)):
424 not isinstance(err, error.ManifestLookupError)):
418 msg = 'revision not found: %s' % err.name
425 msg = 'revision not found: %s' % err.name
419
426
420 res.status = '404 Not Found'
427 res.status = '404 Not Found'
421 res.headers['Content-Type'] = ctype
428 res.headers['Content-Type'] = ctype
422 return rctx.sendtemplate('error', error=msg)
429 return rctx.sendtemplate('error', error=msg)
423 except (error.RepoError, error.RevlogError) as e:
430 except (error.RepoError, error.RevlogError) as e:
424 res.status = '500 Internal Server Error'
431 res.status = '500 Internal Server Error'
425 res.headers['Content-Type'] = ctype
432 res.headers['Content-Type'] = ctype
426 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
433 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
427 except ErrorResponse as e:
434 except ErrorResponse as e:
428 res.status = statusmessage(e.code, pycompat.bytestr(e))
435 res.status = statusmessage(e.code, pycompat.bytestr(e))
429 res.headers['Content-Type'] = ctype
436 res.headers['Content-Type'] = ctype
430 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
437 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
431
438
432 def check_perm(self, rctx, req, op):
439 def check_perm(self, rctx, req, op):
433 for permhook in permhooks:
440 for permhook in permhooks:
434 permhook(rctx, req, op)
441 permhook(rctx, req, op)
435
442
436 def getwebview(repo):
443 def getwebview(repo):
437 """The 'web.view' config controls changeset filter to hgweb. Possible
444 """The 'web.view' config controls changeset filter to hgweb. Possible
438 values are ``served``, ``visible`` and ``all``. Default is ``served``.
445 values are ``served``, ``visible`` and ``all``. Default is ``served``.
439 The ``served`` filter only shows changesets that can be pulled from the
446 The ``served`` filter only shows changesets that can be pulled from the
440 hgweb instance. The``visible`` filter includes secret changesets but
447 hgweb instance. The``visible`` filter includes secret changesets but
441 still excludes "hidden" one.
448 still excludes "hidden" one.
442
449
443 See the repoview module for details.
450 See the repoview module for details.
444
451
445 The option has been around undocumented since Mercurial 2.5, but no
452 The option has been around undocumented since Mercurial 2.5, but no
446 user ever asked about it. So we better keep it undocumented for now."""
453 user ever asked about it. So we better keep it undocumented for now."""
447 # experimental config: web.view
454 # experimental config: web.view
448 viewconfig = repo.ui.config('web', 'view', untrusted=True)
455 viewconfig = repo.ui.config('web', 'view', untrusted=True)
449 if viewconfig == 'all':
456 if viewconfig == 'all':
450 return repo.unfiltered()
457 return repo.unfiltered()
451 elif viewconfig in repoview.filtertable:
458 elif viewconfig in repoview.filtertable:
452 return repo.filtered(viewconfig)
459 return repo.filtered(viewconfig)
453 else:
460 else:
454 return repo.filtered('served')
461 return repo.filtered('served')
@@ -1,653 +1,723 b''
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 . import (
15 from . import (
16 encoding,
16 encoding,
17 error,
17 error,
18 hook,
18 hook,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 wireproto,
21 wireproto,
22 wireprototypes,
22 wireprototypes,
23 )
23 )
24
24
25 stringio = util.stringio
25 stringio = util.stringio
26
26
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 HTTP_OK = 200
30 HTTP_OK = 200
31
31
32 HGTYPE = 'application/mercurial-0.1'
32 HGTYPE = 'application/mercurial-0.1'
33 HGTYPE2 = 'application/mercurial-0.2'
33 HGTYPE2 = 'application/mercurial-0.2'
34 HGERRTYPE = 'application/hg-error'
34 HGERRTYPE = 'application/hg-error'
35
35
36 HTTPV2 = wireprototypes.HTTPV2
36 SSHV1 = wireprototypes.SSHV1
37 SSHV1 = wireprototypes.SSHV1
37 SSHV2 = wireprototypes.SSHV2
38 SSHV2 = wireprototypes.SSHV2
38
39
39 def decodevaluefromheaders(req, headerprefix):
40 def decodevaluefromheaders(req, headerprefix):
40 """Decode a long value from multiple HTTP request headers.
41 """Decode a long value from multiple HTTP request headers.
41
42
42 Returns the value as a bytes, not a str.
43 Returns the value as a bytes, not a str.
43 """
44 """
44 chunks = []
45 chunks = []
45 i = 1
46 i = 1
46 while True:
47 while True:
47 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 if v is None:
49 if v is None:
49 break
50 break
50 chunks.append(pycompat.bytesurl(v))
51 chunks.append(pycompat.bytesurl(v))
51 i += 1
52 i += 1
52
53
53 return ''.join(chunks)
54 return ''.join(chunks)
54
55
55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 def __init__(self, req, ui, checkperm):
57 def __init__(self, req, ui, checkperm):
57 self._req = req
58 self._req = req
58 self._ui = ui
59 self._ui = ui
59 self._checkperm = checkperm
60 self._checkperm = checkperm
60
61
61 @property
62 @property
62 def name(self):
63 def name(self):
63 return 'http-v1'
64 return 'http-v1'
64
65
65 def getargs(self, args):
66 def getargs(self, args):
66 knownargs = self._args()
67 knownargs = self._args()
67 data = {}
68 data = {}
68 keys = args.split()
69 keys = args.split()
69 for k in keys:
70 for k in keys:
70 if k == '*':
71 if k == '*':
71 star = {}
72 star = {}
72 for key in knownargs.keys():
73 for key in knownargs.keys():
73 if key != 'cmd' and key not in keys:
74 if key != 'cmd' and key not in keys:
74 star[key] = knownargs[key][0]
75 star[key] = knownargs[key][0]
75 data['*'] = star
76 data['*'] = star
76 else:
77 else:
77 data[k] = knownargs[k][0]
78 data[k] = knownargs[k][0]
78 return [data[k] for k in keys]
79 return [data[k] for k in keys]
79
80
80 def _args(self):
81 def _args(self):
81 args = self._req.qsparams.asdictoflists()
82 args = self._req.qsparams.asdictoflists()
82 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 if postlen:
84 if postlen:
84 args.update(urlreq.parseqs(
85 args.update(urlreq.parseqs(
85 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 return args
87 return args
87
88
88 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 return args
91 return args
91
92
92 def forwardpayload(self, fp):
93 def forwardpayload(self, fp):
93 # Existing clients *always* send Content-Length.
94 # Existing clients *always* send Content-Length.
94 length = int(self._req.headers[b'Content-Length'])
95 length = int(self._req.headers[b'Content-Length'])
95
96
96 # If httppostargs is used, we need to read Content-Length
97 # If httppostargs is used, we need to read Content-Length
97 # minus the amount that was consumed by args.
98 # minus the amount that was consumed by args.
98 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 fp.write(s)
101 fp.write(s)
101
102
102 @contextlib.contextmanager
103 @contextlib.contextmanager
103 def mayberedirectstdio(self):
104 def mayberedirectstdio(self):
104 oldout = self._ui.fout
105 oldout = self._ui.fout
105 olderr = self._ui.ferr
106 olderr = self._ui.ferr
106
107
107 out = util.stringio()
108 out = util.stringio()
108
109
109 try:
110 try:
110 self._ui.fout = out
111 self._ui.fout = out
111 self._ui.ferr = out
112 self._ui.ferr = out
112 yield out
113 yield out
113 finally:
114 finally:
114 self._ui.fout = oldout
115 self._ui.fout = oldout
115 self._ui.ferr = olderr
116 self._ui.ferr = olderr
116
117
117 def client(self):
118 def client(self):
118 return 'remote:%s:%s:%s' % (
119 return 'remote:%s:%s:%s' % (
119 self._req.urlscheme,
120 self._req.urlscheme,
120 urlreq.quote(self._req.remotehost or ''),
121 urlreq.quote(self._req.remotehost or ''),
121 urlreq.quote(self._req.remoteuser or ''))
122 urlreq.quote(self._req.remoteuser or ''))
122
123
123 def addcapabilities(self, repo, caps):
124 def addcapabilities(self, repo, caps):
124 caps.append('httpheader=%d' %
125 caps.append('httpheader=%d' %
125 repo.ui.configint('server', 'maxhttpheaderlen'))
126 repo.ui.configint('server', 'maxhttpheaderlen'))
126 if repo.ui.configbool('experimental', 'httppostargs'):
127 if repo.ui.configbool('experimental', 'httppostargs'):
127 caps.append('httppostargs')
128 caps.append('httppostargs')
128
129
129 # FUTURE advertise 0.2rx once support is implemented
130 # FUTURE advertise 0.2rx once support is implemented
130 # FUTURE advertise minrx and mintx after consulting config option
131 # FUTURE advertise minrx and mintx after consulting config option
131 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132
133
133 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 if compengines:
135 if compengines:
135 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 for e in compengines)
137 for e in compengines)
137 caps.append('compression=%s' % comptypes)
138 caps.append('compression=%s' % comptypes)
138
139
139 return caps
140 return caps
140
141
141 def checkperm(self, perm):
142 def checkperm(self, perm):
142 return self._checkperm(perm)
143 return self._checkperm(perm)
143
144
144 # This method exists mostly so that extensions like remotefilelog can
145 # This method exists mostly so that extensions like remotefilelog can
145 # disable a kludgey legacy method only over http. As of early 2018,
146 # disable a kludgey legacy method only over http. As of early 2018,
146 # there are no other known users, so with any luck we can discard this
147 # there are no other known users, so with any luck we can discard this
147 # hook if remotefilelog becomes a first-party extension.
148 # hook if remotefilelog becomes a first-party extension.
148 def iscmd(cmd):
149 def iscmd(cmd):
149 return cmd in wireproto.commands
150 return cmd in wireproto.commands
150
151
151 def handlewsgirequest(rctx, req, res, checkperm):
152 def handlewsgirequest(rctx, req, res, checkperm):
152 """Possibly process a wire protocol request.
153 """Possibly process a wire protocol request.
153
154
154 If the current request is a wire protocol request, the request is
155 If the current request is a wire protocol request, the request is
155 processed by this function.
156 processed by this function.
156
157
157 ``req`` is a ``parsedrequest`` instance.
158 ``req`` is a ``parsedrequest`` instance.
158 ``res`` is a ``wsgiresponse`` instance.
159 ``res`` is a ``wsgiresponse`` instance.
159
160
160 Returns a bool indicating if the request was serviced. If set, the caller
161 Returns a bool indicating if the request was serviced. If set, the caller
161 should stop processing the request, as a response has already been issued.
162 should stop processing the request, as a response has already been issued.
162 """
163 """
163 # Avoid cycle involving hg module.
164 # Avoid cycle involving hg module.
164 from .hgweb import common as hgwebcommon
165 from .hgweb import common as hgwebcommon
165
166
166 repo = rctx.repo
167 repo = rctx.repo
167
168
168 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
169 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
169 # string parameter. If it isn't present, this isn't a wire protocol
170 # string parameter. If it isn't present, this isn't a wire protocol
170 # request.
171 # request.
171 if 'cmd' not in req.qsparams:
172 if 'cmd' not in req.qsparams:
172 return False
173 return False
173
174
174 cmd = req.qsparams['cmd']
175 cmd = req.qsparams['cmd']
175
176
176 # The "cmd" request parameter is used by both the wire protocol and hgweb.
177 # The "cmd" request parameter is used by both the wire protocol and hgweb.
177 # While not all wire protocol commands are available for all transports,
178 # While not all wire protocol commands are available for all transports,
178 # if we see a "cmd" value that resembles a known wire protocol command, we
179 # if we see a "cmd" value that resembles a known wire protocol command, we
179 # route it to a protocol handler. This is better than routing possible
180 # route it to a protocol handler. This is better than routing possible
180 # wire protocol requests to hgweb because it prevents hgweb from using
181 # wire protocol requests to hgweb because it prevents hgweb from using
181 # known wire protocol commands and it is less confusing for machine
182 # known wire protocol commands and it is less confusing for machine
182 # clients.
183 # clients.
183 if not iscmd(cmd):
184 if not iscmd(cmd):
184 return False
185 return False
185
186
186 # The "cmd" query string argument is only valid on the root path of the
187 # The "cmd" query string argument is only valid on the root path of the
187 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
188 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
188 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
189 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
189 # in this case. We send an HTTP 404 for backwards compatibility reasons.
190 # in this case. We send an HTTP 404 for backwards compatibility reasons.
190 if req.dispatchpath:
191 if req.dispatchpath:
191 res.status = hgwebcommon.statusmessage(404)
192 res.status = hgwebcommon.statusmessage(404)
192 res.headers['Content-Type'] = HGTYPE
193 res.headers['Content-Type'] = HGTYPE
193 # TODO This is not a good response to issue for this request. This
194 # TODO This is not a good response to issue for this request. This
194 # is mostly for BC for now.
195 # is mostly for BC for now.
195 res.setbodybytes('0\n%s\n' % b'Not Found')
196 res.setbodybytes('0\n%s\n' % b'Not Found')
196 return True
197 return True
197
198
198 proto = httpv1protocolhandler(req, repo.ui,
199 proto = httpv1protocolhandler(req, repo.ui,
199 lambda perm: checkperm(rctx, req, perm))
200 lambda perm: checkperm(rctx, req, perm))
200
201
201 # The permissions checker should be the only thing that can raise an
202 # The permissions checker should be the only thing that can raise an
202 # ErrorResponse. It is kind of a layer violation to catch an hgweb
203 # ErrorResponse. It is kind of a layer violation to catch an hgweb
203 # exception here. So consider refactoring into a exception type that
204 # exception here. So consider refactoring into a exception type that
204 # is associated with the wire protocol.
205 # is associated with the wire protocol.
205 try:
206 try:
206 _callhttp(repo, req, res, proto, cmd)
207 _callhttp(repo, req, res, proto, cmd)
207 except hgwebcommon.ErrorResponse as e:
208 except hgwebcommon.ErrorResponse as e:
208 for k, v in e.headers:
209 for k, v in e.headers:
209 res.headers[k] = v
210 res.headers[k] = v
210 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
211 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
211 # TODO This response body assumes the failed command was
212 # TODO This response body assumes the failed command was
212 # "unbundle." That assumption is not always valid.
213 # "unbundle." That assumption is not always valid.
213 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
214 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
214
215
215 return True
216 return True
216
217
218 def handlewsgiapirequest(rctx, req, res, checkperm):
219 """Handle requests to /api/*."""
220 assert req.dispatchparts[0] == b'api'
221
222 repo = rctx.repo
223
224 # This whole URL space is experimental for now. But we want to
225 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
226 if not repo.ui.configbool('experimental', 'web.apiserver'):
227 res.status = b'404 Not Found'
228 res.headers[b'Content-Type'] = b'text/plain'
229 res.setbodybytes(_('Experimental API server endpoint not enabled'))
230 return
231
232 # The URL space is /api/<protocol>/*. The structure of URLs under varies
233 # by <protocol>.
234
235 # Registered APIs are made available via config options of the name of
236 # the protocol.
237 availableapis = set()
238 for k, v in API_HANDLERS.items():
239 section, option = v['config']
240 if repo.ui.configbool(section, option):
241 availableapis.add(k)
242
243 # Requests to /api/ list available APIs.
244 if req.dispatchparts == [b'api']:
245 res.status = b'200 OK'
246 res.headers[b'Content-Type'] = b'text/plain'
247 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
248 'one of the following:\n')]
249 if availableapis:
250 lines.extend(sorted(availableapis))
251 else:
252 lines.append(_('(no available APIs)\n'))
253 res.setbodybytes(b'\n'.join(lines))
254 return
255
256 proto = req.dispatchparts[1]
257
258 if proto not in API_HANDLERS:
259 res.status = b'404 Not Found'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
262 proto, b', '.join(sorted(availableapis))))
263 return
264
265 if proto not in availableapis:
266 res.status = b'404 Not Found'
267 res.headers[b'Content-Type'] = b'text/plain'
268 res.setbodybytes(_('API %s not enabled\n') % proto)
269 return
270
271 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
272 req.dispatchparts[2:])
273
274 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
275 res.status = b'200 OK'
276 res.headers[b'Content-Type'] = b'text/plain'
277 res.setbodybytes(b'/'.join(urlparts) + b'\n')
278
279 # Maps API name to metadata so custom API can be registered.
280 API_HANDLERS = {
281 HTTPV2: {
282 'config': ('experimental', 'web.api.http-v2'),
283 'handler': _handlehttpv2request,
284 },
285 }
286
217 def _httpresponsetype(ui, req, prefer_uncompressed):
287 def _httpresponsetype(ui, req, prefer_uncompressed):
218 """Determine the appropriate response type and compression settings.
288 """Determine the appropriate response type and compression settings.
219
289
220 Returns a tuple of (mediatype, compengine, engineopts).
290 Returns a tuple of (mediatype, compengine, engineopts).
221 """
291 """
222 # Determine the response media type and compression engine based
292 # Determine the response media type and compression engine based
223 # on the request parameters.
293 # on the request parameters.
224 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
294 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
225
295
226 if '0.2' in protocaps:
296 if '0.2' in protocaps:
227 # All clients are expected to support uncompressed data.
297 # All clients are expected to support uncompressed data.
228 if prefer_uncompressed:
298 if prefer_uncompressed:
229 return HGTYPE2, util._noopengine(), {}
299 return HGTYPE2, util._noopengine(), {}
230
300
231 # Default as defined by wire protocol spec.
301 # Default as defined by wire protocol spec.
232 compformats = ['zlib', 'none']
302 compformats = ['zlib', 'none']
233 for cap in protocaps:
303 for cap in protocaps:
234 if cap.startswith('comp='):
304 if cap.startswith('comp='):
235 compformats = cap[5:].split(',')
305 compformats = cap[5:].split(',')
236 break
306 break
237
307
238 # Now find an agreed upon compression format.
308 # Now find an agreed upon compression format.
239 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
309 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
240 if engine.wireprotosupport().name in compformats:
310 if engine.wireprotosupport().name in compformats:
241 opts = {}
311 opts = {}
242 level = ui.configint('server', '%slevel' % engine.name())
312 level = ui.configint('server', '%slevel' % engine.name())
243 if level is not None:
313 if level is not None:
244 opts['level'] = level
314 opts['level'] = level
245
315
246 return HGTYPE2, engine, opts
316 return HGTYPE2, engine, opts
247
317
248 # No mutually supported compression format. Fall back to the
318 # No mutually supported compression format. Fall back to the
249 # legacy protocol.
319 # legacy protocol.
250
320
251 # Don't allow untrusted settings because disabling compression or
321 # Don't allow untrusted settings because disabling compression or
252 # setting a very high compression level could lead to flooding
322 # setting a very high compression level could lead to flooding
253 # the server's network or CPU.
323 # the server's network or CPU.
254 opts = {'level': ui.configint('server', 'zliblevel')}
324 opts = {'level': ui.configint('server', 'zliblevel')}
255 return HGTYPE, util.compengines['zlib'], opts
325 return HGTYPE, util.compengines['zlib'], opts
256
326
257 def _callhttp(repo, req, res, proto, cmd):
327 def _callhttp(repo, req, res, proto, cmd):
258 # Avoid cycle involving hg module.
328 # Avoid cycle involving hg module.
259 from .hgweb import common as hgwebcommon
329 from .hgweb import common as hgwebcommon
260
330
261 def genversion2(gen, engine, engineopts):
331 def genversion2(gen, engine, engineopts):
262 # application/mercurial-0.2 always sends a payload header
332 # application/mercurial-0.2 always sends a payload header
263 # identifying the compression engine.
333 # identifying the compression engine.
264 name = engine.wireprotosupport().name
334 name = engine.wireprotosupport().name
265 assert 0 < len(name) < 256
335 assert 0 < len(name) < 256
266 yield struct.pack('B', len(name))
336 yield struct.pack('B', len(name))
267 yield name
337 yield name
268
338
269 for chunk in gen:
339 for chunk in gen:
270 yield chunk
340 yield chunk
271
341
272 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
342 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
273 if code == HTTP_OK:
343 if code == HTTP_OK:
274 res.status = '200 Script output follows'
344 res.status = '200 Script output follows'
275 else:
345 else:
276 res.status = hgwebcommon.statusmessage(code)
346 res.status = hgwebcommon.statusmessage(code)
277
347
278 res.headers['Content-Type'] = contenttype
348 res.headers['Content-Type'] = contenttype
279
349
280 if bodybytes is not None:
350 if bodybytes is not None:
281 res.setbodybytes(bodybytes)
351 res.setbodybytes(bodybytes)
282 if bodygen is not None:
352 if bodygen is not None:
283 res.setbodygen(bodygen)
353 res.setbodygen(bodygen)
284
354
285 if not wireproto.commands.commandavailable(cmd, proto):
355 if not wireproto.commands.commandavailable(cmd, proto):
286 setresponse(HTTP_OK, HGERRTYPE,
356 setresponse(HTTP_OK, HGERRTYPE,
287 _('requested wire protocol command is not available over '
357 _('requested wire protocol command is not available over '
288 'HTTP'))
358 'HTTP'))
289 return
359 return
290
360
291 proto.checkperm(wireproto.commands[cmd].permission)
361 proto.checkperm(wireproto.commands[cmd].permission)
292
362
293 rsp = wireproto.dispatch(repo, proto, cmd)
363 rsp = wireproto.dispatch(repo, proto, cmd)
294
364
295 if isinstance(rsp, bytes):
365 if isinstance(rsp, bytes):
296 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
366 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
297 elif isinstance(rsp, wireprototypes.bytesresponse):
367 elif isinstance(rsp, wireprototypes.bytesresponse):
298 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
368 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
299 elif isinstance(rsp, wireprototypes.streamreslegacy):
369 elif isinstance(rsp, wireprototypes.streamreslegacy):
300 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
370 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
301 elif isinstance(rsp, wireprototypes.streamres):
371 elif isinstance(rsp, wireprototypes.streamres):
302 gen = rsp.gen
372 gen = rsp.gen
303
373
304 # This code for compression should not be streamres specific. It
374 # This code for compression should not be streamres specific. It
305 # is here because we only compress streamres at the moment.
375 # is here because we only compress streamres at the moment.
306 mediatype, engine, engineopts = _httpresponsetype(
376 mediatype, engine, engineopts = _httpresponsetype(
307 repo.ui, req, rsp.prefer_uncompressed)
377 repo.ui, req, rsp.prefer_uncompressed)
308 gen = engine.compressstream(gen, engineopts)
378 gen = engine.compressstream(gen, engineopts)
309
379
310 if mediatype == HGTYPE2:
380 if mediatype == HGTYPE2:
311 gen = genversion2(gen, engine, engineopts)
381 gen = genversion2(gen, engine, engineopts)
312
382
313 setresponse(HTTP_OK, mediatype, bodygen=gen)
383 setresponse(HTTP_OK, mediatype, bodygen=gen)
314 elif isinstance(rsp, wireprototypes.pushres):
384 elif isinstance(rsp, wireprototypes.pushres):
315 rsp = '%d\n%s' % (rsp.res, rsp.output)
385 rsp = '%d\n%s' % (rsp.res, rsp.output)
316 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
386 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
317 elif isinstance(rsp, wireprototypes.pusherr):
387 elif isinstance(rsp, wireprototypes.pusherr):
318 rsp = '0\n%s\n' % rsp.res
388 rsp = '0\n%s\n' % rsp.res
319 res.drain = True
389 res.drain = True
320 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
390 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
321 elif isinstance(rsp, wireprototypes.ooberror):
391 elif isinstance(rsp, wireprototypes.ooberror):
322 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
392 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
323 else:
393 else:
324 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
394 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
325
395
326 def _sshv1respondbytes(fout, value):
396 def _sshv1respondbytes(fout, value):
327 """Send a bytes response for protocol version 1."""
397 """Send a bytes response for protocol version 1."""
328 fout.write('%d\n' % len(value))
398 fout.write('%d\n' % len(value))
329 fout.write(value)
399 fout.write(value)
330 fout.flush()
400 fout.flush()
331
401
332 def _sshv1respondstream(fout, source):
402 def _sshv1respondstream(fout, source):
333 write = fout.write
403 write = fout.write
334 for chunk in source.gen:
404 for chunk in source.gen:
335 write(chunk)
405 write(chunk)
336 fout.flush()
406 fout.flush()
337
407
338 def _sshv1respondooberror(fout, ferr, rsp):
408 def _sshv1respondooberror(fout, ferr, rsp):
339 ferr.write(b'%s\n-\n' % rsp)
409 ferr.write(b'%s\n-\n' % rsp)
340 ferr.flush()
410 ferr.flush()
341 fout.write(b'\n')
411 fout.write(b'\n')
342 fout.flush()
412 fout.flush()
343
413
344 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
414 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
345 """Handler for requests services via version 1 of SSH protocol."""
415 """Handler for requests services via version 1 of SSH protocol."""
346 def __init__(self, ui, fin, fout):
416 def __init__(self, ui, fin, fout):
347 self._ui = ui
417 self._ui = ui
348 self._fin = fin
418 self._fin = fin
349 self._fout = fout
419 self._fout = fout
350
420
351 @property
421 @property
352 def name(self):
422 def name(self):
353 return wireprototypes.SSHV1
423 return wireprototypes.SSHV1
354
424
355 def getargs(self, args):
425 def getargs(self, args):
356 data = {}
426 data = {}
357 keys = args.split()
427 keys = args.split()
358 for n in xrange(len(keys)):
428 for n in xrange(len(keys)):
359 argline = self._fin.readline()[:-1]
429 argline = self._fin.readline()[:-1]
360 arg, l = argline.split()
430 arg, l = argline.split()
361 if arg not in keys:
431 if arg not in keys:
362 raise error.Abort(_("unexpected parameter %r") % arg)
432 raise error.Abort(_("unexpected parameter %r") % arg)
363 if arg == '*':
433 if arg == '*':
364 star = {}
434 star = {}
365 for k in xrange(int(l)):
435 for k in xrange(int(l)):
366 argline = self._fin.readline()[:-1]
436 argline = self._fin.readline()[:-1]
367 arg, l = argline.split()
437 arg, l = argline.split()
368 val = self._fin.read(int(l))
438 val = self._fin.read(int(l))
369 star[arg] = val
439 star[arg] = val
370 data['*'] = star
440 data['*'] = star
371 else:
441 else:
372 val = self._fin.read(int(l))
442 val = self._fin.read(int(l))
373 data[arg] = val
443 data[arg] = val
374 return [data[k] for k in keys]
444 return [data[k] for k in keys]
375
445
376 def forwardpayload(self, fpout):
446 def forwardpayload(self, fpout):
377 # We initially send an empty response. This tells the client it is
447 # We initially send an empty response. This tells the client it is
378 # OK to start sending data. If a client sees any other response, it
448 # OK to start sending data. If a client sees any other response, it
379 # interprets it as an error.
449 # interprets it as an error.
380 _sshv1respondbytes(self._fout, b'')
450 _sshv1respondbytes(self._fout, b'')
381
451
382 # The file is in the form:
452 # The file is in the form:
383 #
453 #
384 # <chunk size>\n<chunk>
454 # <chunk size>\n<chunk>
385 # ...
455 # ...
386 # 0\n
456 # 0\n
387 count = int(self._fin.readline())
457 count = int(self._fin.readline())
388 while count:
458 while count:
389 fpout.write(self._fin.read(count))
459 fpout.write(self._fin.read(count))
390 count = int(self._fin.readline())
460 count = int(self._fin.readline())
391
461
392 @contextlib.contextmanager
462 @contextlib.contextmanager
393 def mayberedirectstdio(self):
463 def mayberedirectstdio(self):
394 yield None
464 yield None
395
465
396 def client(self):
466 def client(self):
397 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
467 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
398 return 'remote:ssh:' + client
468 return 'remote:ssh:' + client
399
469
400 def addcapabilities(self, repo, caps):
470 def addcapabilities(self, repo, caps):
401 return caps
471 return caps
402
472
403 def checkperm(self, perm):
473 def checkperm(self, perm):
404 pass
474 pass
405
475
406 class sshv2protocolhandler(sshv1protocolhandler):
476 class sshv2protocolhandler(sshv1protocolhandler):
407 """Protocol handler for version 2 of the SSH protocol."""
477 """Protocol handler for version 2 of the SSH protocol."""
408
478
409 @property
479 @property
410 def name(self):
480 def name(self):
411 return wireprototypes.SSHV2
481 return wireprototypes.SSHV2
412
482
413 def _runsshserver(ui, repo, fin, fout, ev):
483 def _runsshserver(ui, repo, fin, fout, ev):
414 # This function operates like a state machine of sorts. The following
484 # This function operates like a state machine of sorts. The following
415 # states are defined:
485 # states are defined:
416 #
486 #
417 # protov1-serving
487 # protov1-serving
418 # Server is in protocol version 1 serving mode. Commands arrive on
488 # Server is in protocol version 1 serving mode. Commands arrive on
419 # new lines. These commands are processed in this state, one command
489 # new lines. These commands are processed in this state, one command
420 # after the other.
490 # after the other.
421 #
491 #
422 # protov2-serving
492 # protov2-serving
423 # Server is in protocol version 2 serving mode.
493 # Server is in protocol version 2 serving mode.
424 #
494 #
425 # upgrade-initial
495 # upgrade-initial
426 # The server is going to process an upgrade request.
496 # The server is going to process an upgrade request.
427 #
497 #
428 # upgrade-v2-filter-legacy-handshake
498 # upgrade-v2-filter-legacy-handshake
429 # The protocol is being upgraded to version 2. The server is expecting
499 # The protocol is being upgraded to version 2. The server is expecting
430 # the legacy handshake from version 1.
500 # the legacy handshake from version 1.
431 #
501 #
432 # upgrade-v2-finish
502 # upgrade-v2-finish
433 # The upgrade to version 2 of the protocol is imminent.
503 # The upgrade to version 2 of the protocol is imminent.
434 #
504 #
435 # shutdown
505 # shutdown
436 # The server is shutting down, possibly in reaction to a client event.
506 # The server is shutting down, possibly in reaction to a client event.
437 #
507 #
438 # And here are their transitions:
508 # And here are their transitions:
439 #
509 #
440 # protov1-serving -> shutdown
510 # protov1-serving -> shutdown
441 # When server receives an empty request or encounters another
511 # When server receives an empty request or encounters another
442 # error.
512 # error.
443 #
513 #
444 # protov1-serving -> upgrade-initial
514 # protov1-serving -> upgrade-initial
445 # An upgrade request line was seen.
515 # An upgrade request line was seen.
446 #
516 #
447 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
517 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
448 # Upgrade to version 2 in progress. Server is expecting to
518 # Upgrade to version 2 in progress. Server is expecting to
449 # process a legacy handshake.
519 # process a legacy handshake.
450 #
520 #
451 # upgrade-v2-filter-legacy-handshake -> shutdown
521 # upgrade-v2-filter-legacy-handshake -> shutdown
452 # Client did not fulfill upgrade handshake requirements.
522 # Client did not fulfill upgrade handshake requirements.
453 #
523 #
454 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
524 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
455 # Client fulfilled version 2 upgrade requirements. Finishing that
525 # Client fulfilled version 2 upgrade requirements. Finishing that
456 # upgrade.
526 # upgrade.
457 #
527 #
458 # upgrade-v2-finish -> protov2-serving
528 # upgrade-v2-finish -> protov2-serving
459 # Protocol upgrade to version 2 complete. Server can now speak protocol
529 # Protocol upgrade to version 2 complete. Server can now speak protocol
460 # version 2.
530 # version 2.
461 #
531 #
462 # protov2-serving -> protov1-serving
532 # protov2-serving -> protov1-serving
463 # Ths happens by default since protocol version 2 is the same as
533 # Ths happens by default since protocol version 2 is the same as
464 # version 1 except for the handshake.
534 # version 1 except for the handshake.
465
535
466 state = 'protov1-serving'
536 state = 'protov1-serving'
467 proto = sshv1protocolhandler(ui, fin, fout)
537 proto = sshv1protocolhandler(ui, fin, fout)
468 protoswitched = False
538 protoswitched = False
469
539
470 while not ev.is_set():
540 while not ev.is_set():
471 if state == 'protov1-serving':
541 if state == 'protov1-serving':
472 # Commands are issued on new lines.
542 # Commands are issued on new lines.
473 request = fin.readline()[:-1]
543 request = fin.readline()[:-1]
474
544
475 # Empty lines signal to terminate the connection.
545 # Empty lines signal to terminate the connection.
476 if not request:
546 if not request:
477 state = 'shutdown'
547 state = 'shutdown'
478 continue
548 continue
479
549
480 # It looks like a protocol upgrade request. Transition state to
550 # It looks like a protocol upgrade request. Transition state to
481 # handle it.
551 # handle it.
482 if request.startswith(b'upgrade '):
552 if request.startswith(b'upgrade '):
483 if protoswitched:
553 if protoswitched:
484 _sshv1respondooberror(fout, ui.ferr,
554 _sshv1respondooberror(fout, ui.ferr,
485 b'cannot upgrade protocols multiple '
555 b'cannot upgrade protocols multiple '
486 b'times')
556 b'times')
487 state = 'shutdown'
557 state = 'shutdown'
488 continue
558 continue
489
559
490 state = 'upgrade-initial'
560 state = 'upgrade-initial'
491 continue
561 continue
492
562
493 available = wireproto.commands.commandavailable(request, proto)
563 available = wireproto.commands.commandavailable(request, proto)
494
564
495 # This command isn't available. Send an empty response and go
565 # This command isn't available. Send an empty response and go
496 # back to waiting for a new command.
566 # back to waiting for a new command.
497 if not available:
567 if not available:
498 _sshv1respondbytes(fout, b'')
568 _sshv1respondbytes(fout, b'')
499 continue
569 continue
500
570
501 rsp = wireproto.dispatch(repo, proto, request)
571 rsp = wireproto.dispatch(repo, proto, request)
502
572
503 if isinstance(rsp, bytes):
573 if isinstance(rsp, bytes):
504 _sshv1respondbytes(fout, rsp)
574 _sshv1respondbytes(fout, rsp)
505 elif isinstance(rsp, wireprototypes.bytesresponse):
575 elif isinstance(rsp, wireprototypes.bytesresponse):
506 _sshv1respondbytes(fout, rsp.data)
576 _sshv1respondbytes(fout, rsp.data)
507 elif isinstance(rsp, wireprototypes.streamres):
577 elif isinstance(rsp, wireprototypes.streamres):
508 _sshv1respondstream(fout, rsp)
578 _sshv1respondstream(fout, rsp)
509 elif isinstance(rsp, wireprototypes.streamreslegacy):
579 elif isinstance(rsp, wireprototypes.streamreslegacy):
510 _sshv1respondstream(fout, rsp)
580 _sshv1respondstream(fout, rsp)
511 elif isinstance(rsp, wireprototypes.pushres):
581 elif isinstance(rsp, wireprototypes.pushres):
512 _sshv1respondbytes(fout, b'')
582 _sshv1respondbytes(fout, b'')
513 _sshv1respondbytes(fout, b'%d' % rsp.res)
583 _sshv1respondbytes(fout, b'%d' % rsp.res)
514 elif isinstance(rsp, wireprototypes.pusherr):
584 elif isinstance(rsp, wireprototypes.pusherr):
515 _sshv1respondbytes(fout, rsp.res)
585 _sshv1respondbytes(fout, rsp.res)
516 elif isinstance(rsp, wireprototypes.ooberror):
586 elif isinstance(rsp, wireprototypes.ooberror):
517 _sshv1respondooberror(fout, ui.ferr, rsp.message)
587 _sshv1respondooberror(fout, ui.ferr, rsp.message)
518 else:
588 else:
519 raise error.ProgrammingError('unhandled response type from '
589 raise error.ProgrammingError('unhandled response type from '
520 'wire protocol command: %s' % rsp)
590 'wire protocol command: %s' % rsp)
521
591
522 # For now, protocol version 2 serving just goes back to version 1.
592 # For now, protocol version 2 serving just goes back to version 1.
523 elif state == 'protov2-serving':
593 elif state == 'protov2-serving':
524 state = 'protov1-serving'
594 state = 'protov1-serving'
525 continue
595 continue
526
596
527 elif state == 'upgrade-initial':
597 elif state == 'upgrade-initial':
528 # We should never transition into this state if we've switched
598 # We should never transition into this state if we've switched
529 # protocols.
599 # protocols.
530 assert not protoswitched
600 assert not protoswitched
531 assert proto.name == wireprototypes.SSHV1
601 assert proto.name == wireprototypes.SSHV1
532
602
533 # Expected: upgrade <token> <capabilities>
603 # Expected: upgrade <token> <capabilities>
534 # If we get something else, the request is malformed. It could be
604 # If we get something else, the request is malformed. It could be
535 # from a future client that has altered the upgrade line content.
605 # from a future client that has altered the upgrade line content.
536 # We treat this as an unknown command.
606 # We treat this as an unknown command.
537 try:
607 try:
538 token, caps = request.split(b' ')[1:]
608 token, caps = request.split(b' ')[1:]
539 except ValueError:
609 except ValueError:
540 _sshv1respondbytes(fout, b'')
610 _sshv1respondbytes(fout, b'')
541 state = 'protov1-serving'
611 state = 'protov1-serving'
542 continue
612 continue
543
613
544 # Send empty response if we don't support upgrading protocols.
614 # Send empty response if we don't support upgrading protocols.
545 if not ui.configbool('experimental', 'sshserver.support-v2'):
615 if not ui.configbool('experimental', 'sshserver.support-v2'):
546 _sshv1respondbytes(fout, b'')
616 _sshv1respondbytes(fout, b'')
547 state = 'protov1-serving'
617 state = 'protov1-serving'
548 continue
618 continue
549
619
550 try:
620 try:
551 caps = urlreq.parseqs(caps)
621 caps = urlreq.parseqs(caps)
552 except ValueError:
622 except ValueError:
553 _sshv1respondbytes(fout, b'')
623 _sshv1respondbytes(fout, b'')
554 state = 'protov1-serving'
624 state = 'protov1-serving'
555 continue
625 continue
556
626
557 # We don't see an upgrade request to protocol version 2. Ignore
627 # We don't see an upgrade request to protocol version 2. Ignore
558 # the upgrade request.
628 # the upgrade request.
559 wantedprotos = caps.get(b'proto', [b''])[0]
629 wantedprotos = caps.get(b'proto', [b''])[0]
560 if SSHV2 not in wantedprotos:
630 if SSHV2 not in wantedprotos:
561 _sshv1respondbytes(fout, b'')
631 _sshv1respondbytes(fout, b'')
562 state = 'protov1-serving'
632 state = 'protov1-serving'
563 continue
633 continue
564
634
565 # It looks like we can honor this upgrade request to protocol 2.
635 # It looks like we can honor this upgrade request to protocol 2.
566 # Filter the rest of the handshake protocol request lines.
636 # Filter the rest of the handshake protocol request lines.
567 state = 'upgrade-v2-filter-legacy-handshake'
637 state = 'upgrade-v2-filter-legacy-handshake'
568 continue
638 continue
569
639
570 elif state == 'upgrade-v2-filter-legacy-handshake':
640 elif state == 'upgrade-v2-filter-legacy-handshake':
571 # Client should have sent legacy handshake after an ``upgrade``
641 # Client should have sent legacy handshake after an ``upgrade``
572 # request. Expected lines:
642 # request. Expected lines:
573 #
643 #
574 # hello
644 # hello
575 # between
645 # between
576 # pairs 81
646 # pairs 81
577 # 0000...-0000...
647 # 0000...-0000...
578
648
579 ok = True
649 ok = True
580 for line in (b'hello', b'between', b'pairs 81'):
650 for line in (b'hello', b'between', b'pairs 81'):
581 request = fin.readline()[:-1]
651 request = fin.readline()[:-1]
582
652
583 if request != line:
653 if request != line:
584 _sshv1respondooberror(fout, ui.ferr,
654 _sshv1respondooberror(fout, ui.ferr,
585 b'malformed handshake protocol: '
655 b'malformed handshake protocol: '
586 b'missing %s' % line)
656 b'missing %s' % line)
587 ok = False
657 ok = False
588 state = 'shutdown'
658 state = 'shutdown'
589 break
659 break
590
660
591 if not ok:
661 if not ok:
592 continue
662 continue
593
663
594 request = fin.read(81)
664 request = fin.read(81)
595 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
665 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
596 _sshv1respondooberror(fout, ui.ferr,
666 _sshv1respondooberror(fout, ui.ferr,
597 b'malformed handshake protocol: '
667 b'malformed handshake protocol: '
598 b'missing between argument value')
668 b'missing between argument value')
599 state = 'shutdown'
669 state = 'shutdown'
600 continue
670 continue
601
671
602 state = 'upgrade-v2-finish'
672 state = 'upgrade-v2-finish'
603 continue
673 continue
604
674
605 elif state == 'upgrade-v2-finish':
675 elif state == 'upgrade-v2-finish':
606 # Send the upgrade response.
676 # Send the upgrade response.
607 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
677 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
608 servercaps = wireproto.capabilities(repo, proto)
678 servercaps = wireproto.capabilities(repo, proto)
609 rsp = b'capabilities: %s' % servercaps.data
679 rsp = b'capabilities: %s' % servercaps.data
610 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
680 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
611 fout.flush()
681 fout.flush()
612
682
613 proto = sshv2protocolhandler(ui, fin, fout)
683 proto = sshv2protocolhandler(ui, fin, fout)
614 protoswitched = True
684 protoswitched = True
615
685
616 state = 'protov2-serving'
686 state = 'protov2-serving'
617 continue
687 continue
618
688
619 elif state == 'shutdown':
689 elif state == 'shutdown':
620 break
690 break
621
691
622 else:
692 else:
623 raise error.ProgrammingError('unhandled ssh server state: %s' %
693 raise error.ProgrammingError('unhandled ssh server state: %s' %
624 state)
694 state)
625
695
626 class sshserver(object):
696 class sshserver(object):
627 def __init__(self, ui, repo, logfh=None):
697 def __init__(self, ui, repo, logfh=None):
628 self._ui = ui
698 self._ui = ui
629 self._repo = repo
699 self._repo = repo
630 self._fin = ui.fin
700 self._fin = ui.fin
631 self._fout = ui.fout
701 self._fout = ui.fout
632
702
633 # Log write I/O to stdout and stderr if configured.
703 # Log write I/O to stdout and stderr if configured.
634 if logfh:
704 if logfh:
635 self._fout = util.makeloggingfileobject(
705 self._fout = util.makeloggingfileobject(
636 logfh, self._fout, 'o', logdata=True)
706 logfh, self._fout, 'o', logdata=True)
637 ui.ferr = util.makeloggingfileobject(
707 ui.ferr = util.makeloggingfileobject(
638 logfh, ui.ferr, 'e', logdata=True)
708 logfh, ui.ferr, 'e', logdata=True)
639
709
640 hook.redirect(True)
710 hook.redirect(True)
641 ui.fout = repo.ui.fout = ui.ferr
711 ui.fout = repo.ui.fout = ui.ferr
642
712
643 # Prevent insertion/deletion of CRs
713 # Prevent insertion/deletion of CRs
644 util.setbinary(self._fin)
714 util.setbinary(self._fin)
645 util.setbinary(self._fout)
715 util.setbinary(self._fout)
646
716
647 def serve_forever(self):
717 def serve_forever(self):
648 self.serveuntil(threading.Event())
718 self.serveuntil(threading.Event())
649 sys.exit(0)
719 sys.exit(0)
650
720
651 def serveuntil(self, ev):
721 def serveuntil(self, ev):
652 """Serve until a threading.Event is set."""
722 """Serve until a threading.Event is set."""
653 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
723 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,157 +1,162 b''
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 import abc
8 import abc
9
9
10 # Names of the SSH protocol implementations.
10 # Names of the SSH protocol implementations.
11 SSHV1 = 'ssh-v1'
11 SSHV1 = 'ssh-v1'
12 # This is advertised over the wire. Incremental the counter at the end
12 # These are advertised over the wire. Increment the counters at the end
13 # to reflect BC breakages.
13 # to reflect BC breakages.
14 SSHV2 = 'exp-ssh-v2-0001'
14 SSHV2 = 'exp-ssh-v2-0001'
15 HTTPV2 = 'exp-http-v2-0001'
15
16
16 # All available wire protocol transports.
17 # All available wire protocol transports.
17 TRANSPORTS = {
18 TRANSPORTS = {
18 SSHV1: {
19 SSHV1: {
19 'transport': 'ssh',
20 'transport': 'ssh',
20 'version': 1,
21 'version': 1,
21 },
22 },
22 SSHV2: {
23 SSHV2: {
23 'transport': 'ssh',
24 'transport': 'ssh',
24 'version': 2,
25 'version': 2,
25 },
26 },
26 'http-v1': {
27 'http-v1': {
27 'transport': 'http',
28 'transport': 'http',
28 'version': 1,
29 'version': 1,
30 },
31 HTTPV2: {
32 'transport': 'http',
33 'version': 2,
29 }
34 }
30 }
35 }
31
36
32 class bytesresponse(object):
37 class bytesresponse(object):
33 """A wire protocol response consisting of raw bytes."""
38 """A wire protocol response consisting of raw bytes."""
34 def __init__(self, data):
39 def __init__(self, data):
35 self.data = data
40 self.data = data
36
41
37 class ooberror(object):
42 class ooberror(object):
38 """wireproto reply: failure of a batch of operation
43 """wireproto reply: failure of a batch of operation
39
44
40 Something failed during a batch call. The error message is stored in
45 Something failed during a batch call. The error message is stored in
41 `self.message`.
46 `self.message`.
42 """
47 """
43 def __init__(self, message):
48 def __init__(self, message):
44 self.message = message
49 self.message = message
45
50
46 class pushres(object):
51 class pushres(object):
47 """wireproto reply: success with simple integer return
52 """wireproto reply: success with simple integer return
48
53
49 The call was successful and returned an integer contained in `self.res`.
54 The call was successful and returned an integer contained in `self.res`.
50 """
55 """
51 def __init__(self, res, output):
56 def __init__(self, res, output):
52 self.res = res
57 self.res = res
53 self.output = output
58 self.output = output
54
59
55 class pusherr(object):
60 class pusherr(object):
56 """wireproto reply: failure
61 """wireproto reply: failure
57
62
58 The call failed. The `self.res` attribute contains the error message.
63 The call failed. The `self.res` attribute contains the error message.
59 """
64 """
60 def __init__(self, res, output):
65 def __init__(self, res, output):
61 self.res = res
66 self.res = res
62 self.output = output
67 self.output = output
63
68
64 class streamres(object):
69 class streamres(object):
65 """wireproto reply: binary stream
70 """wireproto reply: binary stream
66
71
67 The call was successful and the result is a stream.
72 The call was successful and the result is a stream.
68
73
69 Accepts a generator containing chunks of data to be sent to the client.
74 Accepts a generator containing chunks of data to be sent to the client.
70
75
71 ``prefer_uncompressed`` indicates that the data is expected to be
76 ``prefer_uncompressed`` indicates that the data is expected to be
72 uncompressable and that the stream should therefore use the ``none``
77 uncompressable and that the stream should therefore use the ``none``
73 engine.
78 engine.
74 """
79 """
75 def __init__(self, gen=None, prefer_uncompressed=False):
80 def __init__(self, gen=None, prefer_uncompressed=False):
76 self.gen = gen
81 self.gen = gen
77 self.prefer_uncompressed = prefer_uncompressed
82 self.prefer_uncompressed = prefer_uncompressed
78
83
79 class streamreslegacy(object):
84 class streamreslegacy(object):
80 """wireproto reply: uncompressed binary stream
85 """wireproto reply: uncompressed binary stream
81
86
82 The call was successful and the result is a stream.
87 The call was successful and the result is a stream.
83
88
84 Accepts a generator containing chunks of data to be sent to the client.
89 Accepts a generator containing chunks of data to be sent to the client.
85
90
86 Like ``streamres``, but sends an uncompressed data for "version 1" clients
91 Like ``streamres``, but sends an uncompressed data for "version 1" clients
87 using the application/mercurial-0.1 media type.
92 using the application/mercurial-0.1 media type.
88 """
93 """
89 def __init__(self, gen=None):
94 def __init__(self, gen=None):
90 self.gen = gen
95 self.gen = gen
91
96
92 class baseprotocolhandler(object):
97 class baseprotocolhandler(object):
93 """Abstract base class for wire protocol handlers.
98 """Abstract base class for wire protocol handlers.
94
99
95 A wire protocol handler serves as an interface between protocol command
100 A wire protocol handler serves as an interface between protocol command
96 handlers and the wire protocol transport layer. Protocol handlers provide
101 handlers and the wire protocol transport layer. Protocol handlers provide
97 methods to read command arguments, redirect stdio for the duration of
102 methods to read command arguments, redirect stdio for the duration of
98 the request, handle response types, etc.
103 the request, handle response types, etc.
99 """
104 """
100
105
101 __metaclass__ = abc.ABCMeta
106 __metaclass__ = abc.ABCMeta
102
107
103 @abc.abstractproperty
108 @abc.abstractproperty
104 def name(self):
109 def name(self):
105 """The name of the protocol implementation.
110 """The name of the protocol implementation.
106
111
107 Used for uniquely identifying the transport type.
112 Used for uniquely identifying the transport type.
108 """
113 """
109
114
110 @abc.abstractmethod
115 @abc.abstractmethod
111 def getargs(self, args):
116 def getargs(self, args):
112 """return the value for arguments in <args>
117 """return the value for arguments in <args>
113
118
114 returns a list of values (same order as <args>)"""
119 returns a list of values (same order as <args>)"""
115
120
116 @abc.abstractmethod
121 @abc.abstractmethod
117 def forwardpayload(self, fp):
122 def forwardpayload(self, fp):
118 """Read the raw payload and forward to a file.
123 """Read the raw payload and forward to a file.
119
124
120 The payload is read in full before the function returns.
125 The payload is read in full before the function returns.
121 """
126 """
122
127
123 @abc.abstractmethod
128 @abc.abstractmethod
124 def mayberedirectstdio(self):
129 def mayberedirectstdio(self):
125 """Context manager to possibly redirect stdio.
130 """Context manager to possibly redirect stdio.
126
131
127 The context manager yields a file-object like object that receives
132 The context manager yields a file-object like object that receives
128 stdout and stderr output when the context manager is active. Or it
133 stdout and stderr output when the context manager is active. Or it
129 yields ``None`` if no I/O redirection occurs.
134 yields ``None`` if no I/O redirection occurs.
130
135
131 The intent of this context manager is to capture stdio output
136 The intent of this context manager is to capture stdio output
132 so it may be sent in the response. Some transports support streaming
137 so it may be sent in the response. Some transports support streaming
133 stdio to the client in real time. For these transports, stdio output
138 stdio to the client in real time. For these transports, stdio output
134 won't be captured.
139 won't be captured.
135 """
140 """
136
141
137 @abc.abstractmethod
142 @abc.abstractmethod
138 def client(self):
143 def client(self):
139 """Returns a string representation of this client (as bytes)."""
144 """Returns a string representation of this client (as bytes)."""
140
145
141 @abc.abstractmethod
146 @abc.abstractmethod
142 def addcapabilities(self, repo, caps):
147 def addcapabilities(self, repo, caps):
143 """Adds advertised capabilities specific to this protocol.
148 """Adds advertised capabilities specific to this protocol.
144
149
145 Receives the list of capabilities collected so far.
150 Receives the list of capabilities collected so far.
146
151
147 Returns a list of capabilities. The passed in argument can be returned.
152 Returns a list of capabilities. The passed in argument can be returned.
148 """
153 """
149
154
150 @abc.abstractmethod
155 @abc.abstractmethod
151 def checkperm(self, perm):
156 def checkperm(self, perm):
152 """Validate that the client has permissions to perform a request.
157 """Validate that the client has permissions to perform a request.
153
158
154 The argument is the permission required to proceed. If the client
159 The argument is the permission required to proceed. If the client
155 doesn't have that permission, the exception should raise or abort
160 doesn't have that permission, the exception should raise or abort
156 in a protocol specific manner.
161 in a protocol specific manner.
157 """
162 """
General Comments 0
You need to be logged in to leave comments. Login now