##// END OF EJS Templates
configitems: move blackbox's config items to the new configitems.toml...
Raphaël Gomès -
r51658:7f8f6fe1 default
parent child Browse files
Show More
@@ -1,242 +1,207 b''
1 # blackbox.py - log repository events to a file for post-mortem debugging
1 # blackbox.py - log repository events to a file for post-mortem debugging
2 #
2 #
3 # Copyright 2010 Nicolas Dumazet
3 # Copyright 2010 Nicolas Dumazet
4 # Copyright 2013 Facebook, Inc.
4 # Copyright 2013 Facebook, Inc.
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 """log repository events to a blackbox for debugging
9 """log repository events to a blackbox for debugging
10
10
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 The events that get logged can be configured via the blackbox.track and
12 The events that get logged can be configured via the blackbox.track and
13 blackbox.ignore config keys.
13 blackbox.ignore config keys.
14
14
15 Examples::
15 Examples::
16
16
17 [blackbox]
17 [blackbox]
18 track = *
18 track = *
19 ignore = pythonhook
19 ignore = pythonhook
20 # dirty is *EXPENSIVE* (slow);
20 # dirty is *EXPENSIVE* (slow);
21 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
21 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
22 dirty = True
22 dirty = True
23 # record the source of log messages
23 # record the source of log messages
24 logsource = True
24 logsource = True
25
25
26 [blackbox]
26 [blackbox]
27 track = command, commandfinish, commandexception, exthook, pythonhook
27 track = command, commandfinish, commandexception, exthook, pythonhook
28
28
29 [blackbox]
29 [blackbox]
30 track = incoming
30 track = incoming
31
31
32 [blackbox]
32 [blackbox]
33 # limit the size of a log file
33 # limit the size of a log file
34 maxsize = 1.5 MB
34 maxsize = 1.5 MB
35 # rotate up to N log files when the current one gets too big
35 # rotate up to N log files when the current one gets too big
36 maxfiles = 3
36 maxfiles = 3
37
37
38 [blackbox]
38 [blackbox]
39 # Include microseconds in log entries with %f (see Python function
39 # Include microseconds in log entries with %f (see Python function
40 # datetime.datetime.strftime)
40 # datetime.datetime.strftime)
41 date-format = %Y-%m-%d @ %H:%M:%S.%f
41 date-format = %Y-%m-%d @ %H:%M:%S.%f
42
42
43 """
43 """
44
44
45
45
46 import re
46 import re
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import hex
49 from mercurial.node import hex
50
50
51 from mercurial import (
51 from mercurial import (
52 encoding,
52 encoding,
53 loggingutil,
53 loggingutil,
54 registrar,
54 registrar,
55 )
55 )
56 from mercurial.utils import (
56 from mercurial.utils import (
57 dateutil,
57 dateutil,
58 procutil,
58 procutil,
59 )
59 )
60
60
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 # be specifying the version(s) of Mercurial they are tested with, or
63 # be specifying the version(s) of Mercurial they are tested with, or
64 # leave the attribute unspecified.
64 # leave the attribute unspecified.
65 testedwith = b'ships-with-hg-core'
65 testedwith = b'ships-with-hg-core'
66
66
67 cmdtable = {}
67 cmdtable = {}
68 command = registrar.command(cmdtable)
68 command = registrar.command(cmdtable)
69
69
70 configtable = {}
71 configitem = registrar.configitem(configtable)
72
73 configitem(
74 b'blackbox',
75 b'dirty',
76 default=False,
77 )
78 configitem(
79 b'blackbox',
80 b'maxsize',
81 default=b'1 MB',
82 )
83 configitem(
84 b'blackbox',
85 b'logsource',
86 default=False,
87 )
88 configitem(
89 b'blackbox',
90 b'maxfiles',
91 default=7,
92 )
93 configitem(
94 b'blackbox',
95 b'track',
96 default=lambda: [b'*'],
97 )
98 configitem(
99 b'blackbox',
100 b'ignore',
101 default=lambda: [b'chgserver', b'cmdserver', b'extension'],
102 )
103 configitem(b'blackbox', b'date-format', default=b'')
104
105 _lastlogger = loggingutil.proxylogger()
70 _lastlogger = loggingutil.proxylogger()
106
71
107
72
108 class blackboxlogger:
73 class blackboxlogger:
109 def __init__(self, ui, repo):
74 def __init__(self, ui, repo):
110 self._repo = repo
75 self._repo = repo
111 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
76 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
112 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
77 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
113 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
78 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
114 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
79 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
115 self._inlog = False
80 self._inlog = False
116
81
117 def tracked(self, event):
82 def tracked(self, event):
118 return (
83 return (
119 b'*' in self._trackedevents and event not in self._ignoredevents
84 b'*' in self._trackedevents and event not in self._ignoredevents
120 ) or event in self._trackedevents
85 ) or event in self._trackedevents
121
86
122 def log(self, ui, event, msg, opts):
87 def log(self, ui, event, msg, opts):
123 # self._log() -> ctx.dirty() may create new subrepo instance, which
88 # self._log() -> ctx.dirty() may create new subrepo instance, which
124 # ui is derived from baseui. So the recursion guard in ui.log()
89 # ui is derived from baseui. So the recursion guard in ui.log()
125 # doesn't work as it's local to the ui instance.
90 # doesn't work as it's local to the ui instance.
126 if self._inlog:
91 if self._inlog:
127 return
92 return
128 self._inlog = True
93 self._inlog = True
129 try:
94 try:
130 self._log(ui, event, msg, opts)
95 self._log(ui, event, msg, opts)
131 finally:
96 finally:
132 self._inlog = False
97 self._inlog = False
133
98
134 def _log(self, ui, event, msg, opts):
99 def _log(self, ui, event, msg, opts):
135 default = ui.configdate(b'devel', b'default-date')
100 default = ui.configdate(b'devel', b'default-date')
136 dateformat = ui.config(b'blackbox', b'date-format')
101 dateformat = ui.config(b'blackbox', b'date-format')
137 if dateformat:
102 if dateformat:
138 date = dateutil.datestr(default, dateformat)
103 date = dateutil.datestr(default, dateformat)
139 else:
104 else:
140 # We want to display milliseconds (more precision seems
105 # We want to display milliseconds (more precision seems
141 # unnecessary). Since %.3f is not supported, use %f and truncate
106 # unnecessary). Since %.3f is not supported, use %f and truncate
142 # microseconds.
107 # microseconds.
143 date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3]
108 date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3]
144 user = procutil.getuser()
109 user = procutil.getuser()
145 pid = b'%d' % procutil.getpid()
110 pid = b'%d' % procutil.getpid()
146 changed = b''
111 changed = b''
147 ctx = self._repo[None]
112 ctx = self._repo[None]
148 parents = ctx.parents()
113 parents = ctx.parents()
149 rev = b'+'.join([hex(p.node()) for p in parents])
114 rev = b'+'.join([hex(p.node()) for p in parents])
150 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
115 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
151 missing=True, merge=False, branch=False
116 missing=True, merge=False, branch=False
152 ):
117 ):
153 changed = b'+'
118 changed = b'+'
154 if ui.configbool(b'blackbox', b'logsource'):
119 if ui.configbool(b'blackbox', b'logsource'):
155 src = b' [%s]' % event
120 src = b' [%s]' % event
156 else:
121 else:
157 src = b''
122 src = b''
158 try:
123 try:
159 fmt = b'%s %s @%s%s (%s)%s> %s'
124 fmt = b'%s %s @%s%s (%s)%s> %s'
160 args = (date, user, rev, changed, pid, src, msg)
125 args = (date, user, rev, changed, pid, src, msg)
161 with loggingutil.openlogfile(
126 with loggingutil.openlogfile(
162 ui,
127 ui,
163 self._repo.vfs,
128 self._repo.vfs,
164 name=b'blackbox.log',
129 name=b'blackbox.log',
165 maxfiles=self._maxfiles,
130 maxfiles=self._maxfiles,
166 maxsize=self._maxsize,
131 maxsize=self._maxsize,
167 ) as fp:
132 ) as fp:
168 fp.write(fmt % args)
133 fp.write(fmt % args)
169 except (IOError, OSError) as err:
134 except (IOError, OSError) as err:
170 # deactivate this to avoid failed logging again
135 # deactivate this to avoid failed logging again
171 self._trackedevents.clear()
136 self._trackedevents.clear()
172 ui.debug(
137 ui.debug(
173 b'warning: cannot write to blackbox.log: %s\n'
138 b'warning: cannot write to blackbox.log: %s\n'
174 % encoding.strtolocal(err.strerror)
139 % encoding.strtolocal(err.strerror)
175 )
140 )
176 return
141 return
177 _lastlogger.logger = self
142 _lastlogger.logger = self
178
143
179
144
180 def uipopulate(ui):
145 def uipopulate(ui):
181 ui.setlogger(b'blackbox', _lastlogger)
146 ui.setlogger(b'blackbox', _lastlogger)
182
147
183
148
184 def reposetup(ui, repo):
149 def reposetup(ui, repo):
185 # During 'hg pull' a httppeer repo is created to represent the remote repo.
150 # During 'hg pull' a httppeer repo is created to represent the remote repo.
186 # It doesn't have a .hg directory to put a blackbox in, so we don't do
151 # It doesn't have a .hg directory to put a blackbox in, so we don't do
187 # the blackbox setup for it.
152 # the blackbox setup for it.
188 if not repo.local():
153 if not repo.local():
189 return
154 return
190
155
191 # Since blackbox.log is stored in the repo directory, the logger should be
156 # Since blackbox.log is stored in the repo directory, the logger should be
192 # instantiated per repository.
157 # instantiated per repository.
193 logger = blackboxlogger(ui, repo)
158 logger = blackboxlogger(ui, repo)
194 ui.setlogger(b'blackbox', logger)
159 ui.setlogger(b'blackbox', logger)
195
160
196 # Set _lastlogger even if ui.log is not called. This gives blackbox a
161 # Set _lastlogger even if ui.log is not called. This gives blackbox a
197 # fallback place to log
162 # fallback place to log
198 if _lastlogger.logger is None:
163 if _lastlogger.logger is None:
199 _lastlogger.logger = logger
164 _lastlogger.logger = logger
200
165
201 repo._wlockfreeprefix.add(b'blackbox.log')
166 repo._wlockfreeprefix.add(b'blackbox.log')
202
167
203
168
204 @command(
169 @command(
205 b'blackbox',
170 b'blackbox',
206 [
171 [
207 (b'l', b'limit', 10, _(b'the number of events to show')),
172 (b'l', b'limit', 10, _(b'the number of events to show')),
208 ],
173 ],
209 _(b'hg blackbox [OPTION]...'),
174 _(b'hg blackbox [OPTION]...'),
210 helpcategory=command.CATEGORY_MAINTENANCE,
175 helpcategory=command.CATEGORY_MAINTENANCE,
211 helpbasic=True,
176 helpbasic=True,
212 )
177 )
213 def blackbox(ui, repo, *revs, **opts):
178 def blackbox(ui, repo, *revs, **opts):
214 """view the recent repository events"""
179 """view the recent repository events"""
215
180
216 if not repo.vfs.exists(b'blackbox.log'):
181 if not repo.vfs.exists(b'blackbox.log'):
217 return
182 return
218
183
219 limit = opts.get('limit')
184 limit = opts.get('limit')
220 assert limit is not None # help pytype
185 assert limit is not None # help pytype
221
186
222 fp = repo.vfs(b'blackbox.log', b'r')
187 fp = repo.vfs(b'blackbox.log', b'r')
223 lines = fp.read().split(b'\n')
188 lines = fp.read().split(b'\n')
224
189
225 count = 0
190 count = 0
226 output = []
191 output = []
227 for line in reversed(lines):
192 for line in reversed(lines):
228 if count >= limit:
193 if count >= limit:
229 break
194 break
230
195
231 # count the commands by matching lines like:
196 # count the commands by matching lines like:
232 # 2013/01/23 19:13:36 root>
197 # 2013/01/23 19:13:36 root>
233 # 2013/01/23 19:13:36 root (1234)>
198 # 2013/01/23 19:13:36 root (1234)>
234 # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)>
199 # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)>
235 # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)>
200 # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)>
236 if re.match(
201 if re.match(
237 br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line
202 br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line
238 ):
203 ):
239 count += 1
204 count += 1
240 output.append(line)
205 output.append(line)
241
206
242 ui.status(b'\n'.join(reversed(output)))
207 ui.status(b'\n'.join(reversed(output)))
@@ -1,212 +1,214 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
8
9 import functools
9 import functools
10 import re
10 import re
11
11
12 from .utils import resourceutil
12 from .utils import resourceutil
13
13
14 from . import (
14 from . import (
15 encoding,
15 encoding,
16 error,
16 error,
17 )
17 )
18
18
19 try:
19 try:
20 import tomllib # pytype: disable=import-error
20 import tomllib # pytype: disable=import-error
21
21
22 tomllib.load # trigger lazy import
22 tomllib.load # trigger lazy import
23 except ModuleNotFoundError:
23 except ModuleNotFoundError:
24 # Python <3.11 compat
24 # Python <3.11 compat
25 from .thirdparty import tomli as tomllib
25 from .thirdparty import tomli as tomllib
26
26
27
27
28 def loadconfigtable(ui, extname, configtable):
28 def loadconfigtable(ui, extname, configtable):
29 """update config item known to the ui with the extension ones"""
29 """update config item known to the ui with the extension ones"""
30 for section, items in sorted(configtable.items()):
30 for section, items in sorted(configtable.items()):
31 knownitems = ui._knownconfig.setdefault(section, itemregister())
31 knownitems = ui._knownconfig.setdefault(section, itemregister())
32 knownkeys = set(knownitems)
32 knownkeys = set(knownitems)
33 newkeys = set(items)
33 newkeys = set(items)
34 for key in sorted(knownkeys & newkeys):
34 for key in sorted(knownkeys & newkeys):
35 msg = b"extension '%s' overwrites config item '%s.%s'"
35 msg = b"extension '%s' overwrites config item '%s.%s'"
36 msg %= (extname, section, key)
36 msg %= (extname, section, key)
37 ui.develwarn(msg, config=b'warn-config')
37 ui.develwarn(msg, config=b'warn-config')
38
38
39 knownitems.update(items)
39 knownitems.update(items)
40
40
41
41
42 class configitem:
42 class configitem:
43 """represent a known config item
43 """represent a known config item
44
44
45 :section: the official config section where to find this item,
45 :section: the official config section where to find this item,
46 :name: the official name within the section,
46 :name: the official name within the section,
47 :default: default value for this item,
47 :default: default value for this item,
48 :alias: optional list of tuples as alternatives,
48 :alias: optional list of tuples as alternatives,
49 :generic: this is a generic definition, match name using regular expression.
49 :generic: this is a generic definition, match name using regular expression.
50 """
50 """
51
51
52 def __init__(
52 def __init__(
53 self,
53 self,
54 section,
54 section,
55 name,
55 name,
56 default=None,
56 default=None,
57 alias=(),
57 alias=(),
58 generic=False,
58 generic=False,
59 priority=0,
59 priority=0,
60 experimental=False,
60 experimental=False,
61 documentation="",
61 documentation="",
62 in_core_extension=None,
62 ):
63 ):
63 self.section = section
64 self.section = section
64 self.name = name
65 self.name = name
65 self.default = default
66 self.default = default
66 self.documentation = documentation
67 self.documentation = documentation
67 self.alias = list(alias)
68 self.alias = list(alias)
68 self.generic = generic
69 self.generic = generic
69 self.priority = priority
70 self.priority = priority
70 self.experimental = experimental
71 self.experimental = experimental
71 self._re = None
72 self._re = None
73 self.in_core_extension = in_core_extension
72 if generic:
74 if generic:
73 self._re = re.compile(self.name)
75 self._re = re.compile(self.name)
74
76
75
77
76 class itemregister(dict):
78 class itemregister(dict):
77 """A specialized dictionary that can handle wild-card selection"""
79 """A specialized dictionary that can handle wild-card selection"""
78
80
79 def __init__(self):
81 def __init__(self):
80 super(itemregister, self).__init__()
82 super(itemregister, self).__init__()
81 self._generics = set()
83 self._generics = set()
82
84
83 def update(self, other):
85 def update(self, other):
84 super(itemregister, self).update(other)
86 super(itemregister, self).update(other)
85 self._generics.update(other._generics)
87 self._generics.update(other._generics)
86
88
87 def __setitem__(self, key, item):
89 def __setitem__(self, key, item):
88 super(itemregister, self).__setitem__(key, item)
90 super(itemregister, self).__setitem__(key, item)
89 if item.generic:
91 if item.generic:
90 self._generics.add(item)
92 self._generics.add(item)
91
93
92 def get(self, key):
94 def get(self, key):
93 baseitem = super(itemregister, self).get(key)
95 baseitem = super(itemregister, self).get(key)
94 if baseitem is not None and not baseitem.generic:
96 if baseitem is not None and not baseitem.generic:
95 return baseitem
97 return baseitem
96
98
97 # search for a matching generic item
99 # search for a matching generic item
98 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
100 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
99 for item in generics:
101 for item in generics:
100 # we use 'match' instead of 'search' to make the matching simpler
102 # we use 'match' instead of 'search' to make the matching simpler
101 # for people unfamiliar with regular expression. Having the match
103 # for people unfamiliar with regular expression. Having the match
102 # rooted to the start of the string will produce less surprising
104 # rooted to the start of the string will produce less surprising
103 # result for user writing simple regex for sub-attribute.
105 # result for user writing simple regex for sub-attribute.
104 #
106 #
105 # For example using "color\..*" match produces an unsurprising
107 # For example using "color\..*" match produces an unsurprising
106 # result, while using search could suddenly match apparently
108 # result, while using search could suddenly match apparently
107 # unrelated configuration that happens to contains "color."
109 # unrelated configuration that happens to contains "color."
108 # anywhere. This is a tradeoff where we favor requiring ".*" on
110 # anywhere. This is a tradeoff where we favor requiring ".*" on
109 # some match to avoid the need to prefix most pattern with "^".
111 # some match to avoid the need to prefix most pattern with "^".
110 # The "^" seems more error prone.
112 # The "^" seems more error prone.
111 if item._re.match(key):
113 if item._re.match(key):
112 return item
114 return item
113
115
114 return None
116 return None
115
117
116
118
117 def sanitize_item(item):
119 def sanitize_item(item):
118 """Apply the transformations that are encoded on top of the pure data"""
120 """Apply the transformations that are encoded on top of the pure data"""
119
121
120 # Set the special defaults
122 # Set the special defaults
121 default_type_key = "default-type"
123 default_type_key = "default-type"
122 default_type = item.pop(default_type_key, None)
124 default_type = item.pop(default_type_key, None)
123 if default_type == "dynamic":
125 if default_type == "dynamic":
124 item["default"] = dynamicdefault
126 item["default"] = dynamicdefault
125 elif default_type == "list_type":
127 elif default_type == "list_type":
126 item["default"] = list
128 item["default"] = list
127 elif default_type == "lambda":
129 elif default_type == "lambda":
128 assert isinstance(item["default"], list)
130 assert isinstance(item["default"], list)
129 default = [e.encode() for e in item["default"]]
131 default = [e.encode() for e in item["default"]]
130 item["default"] = lambda: default
132 item["default"] = lambda: default
131 elif default_type == "lazy_module":
133 elif default_type == "lazy_module":
132 item["default"] = lambda: encoding.encoding
134 item["default"] = lambda: encoding.encoding
133 else:
135 else:
134 if default_type is not None:
136 if default_type is not None:
135 msg = "invalid default config type %r for '%s.%s'"
137 msg = "invalid default config type %r for '%s.%s'"
136 msg %= (default_type, item["section"], item["name"])
138 msg %= (default_type, item["section"], item["name"])
137 raise error.ProgrammingError(msg)
139 raise error.ProgrammingError(msg)
138
140
139 # config expects bytes
141 # config expects bytes
140 alias = item.get("alias")
142 alias = item.get("alias")
141 if alias:
143 if alias:
142 item["alias"] = [(k.encode(), v.encode()) for (k, v) in alias]
144 item["alias"] = [(k.encode(), v.encode()) for (k, v) in alias]
143 if isinstance(item.get("default"), str):
145 if isinstance(item.get("default"), str):
144 item["default"] = item["default"].encode()
146 item["default"] = item["default"].encode()
145 item["section"] = item["section"].encode()
147 item["section"] = item["section"].encode()
146 item["name"] = item["name"].encode()
148 item["name"] = item["name"].encode()
147
149
148
150
149 def read_configitems_file():
151 def read_configitems_file():
150 """Returns the deserialized TOML structure from the configitems file"""
152 """Returns the deserialized TOML structure from the configitems file"""
151 with resourceutil.open_resource(b"mercurial", b"configitems.toml") as fp:
153 with resourceutil.open_resource(b"mercurial", b"configitems.toml") as fp:
152 return tomllib.load(fp)
154 return tomllib.load(fp)
153
155
154
156
155 def configitems_from_toml(items):
157 def configitems_from_toml(items):
156 """Register the configitems from the *deserialized* toml file"""
158 """Register the configitems from the *deserialized* toml file"""
157 for item in items["items"]:
159 for item in items["items"]:
158 sanitize_item(item)
160 sanitize_item(item)
159 coreconfigitem(**item)
161 coreconfigitem(**item)
160
162
161 templates = items["templates"]
163 templates = items["templates"]
162
164
163 for application in items["template-applications"]:
165 for application in items["template-applications"]:
164 template_items = templates[application["template"]]
166 template_items = templates[application["template"]]
165
167
166 for template_item in template_items:
168 for template_item in template_items:
167 item = template_item.copy()
169 item = template_item.copy()
168 prefix = application.get("prefix", "")
170 prefix = application.get("prefix", "")
169 item["section"] = application["section"]
171 item["section"] = application["section"]
170 if prefix:
172 if prefix:
171 item["name"] = f'{prefix}.{item["suffix"]}'
173 item["name"] = f'{prefix}.{item["suffix"]}'
172 else:
174 else:
173 item["name"] = item["suffix"]
175 item["name"] = item["suffix"]
174
176
175 sanitize_item(item)
177 sanitize_item(item)
176 item.pop("suffix", None)
178 item.pop("suffix", None)
177 coreconfigitem(**item)
179 coreconfigitem(**item)
178
180
179
181
180 def import_configitems_from_file():
182 def import_configitems_from_file():
181 as_toml = read_configitems_file()
183 as_toml = read_configitems_file()
182 configitems_from_toml(as_toml)
184 configitems_from_toml(as_toml)
183
185
184
186
185 coreitems = {}
187 coreitems = {}
186
188
187
189
188 def _register(configtable, *args, **kwargs):
190 def _register(configtable, *args, **kwargs):
189 item = configitem(*args, **kwargs)
191 item = configitem(*args, **kwargs)
190 section = configtable.setdefault(item.section, itemregister())
192 section = configtable.setdefault(item.section, itemregister())
191 if item.name in section:
193 if item.name in section:
192 msg = b"duplicated config item registration for '%s.%s'"
194 msg = b"duplicated config item registration for '%s.%s'"
193 raise error.ProgrammingError(msg % (item.section, item.name))
195 raise error.ProgrammingError(msg % (item.section, item.name))
194 section[item.name] = item
196 section[item.name] = item
195
197
196
198
197 # special value for case where the default is derived from other values
199 # special value for case where the default is derived from other values
198 dynamicdefault = object()
200 dynamicdefault = object()
199
201
200 # Registering actual config items
202 # Registering actual config items
201
203
202
204
203 def getitemregister(configtable):
205 def getitemregister(configtable):
204 f = functools.partial(_register, configtable)
206 f = functools.partial(_register, configtable)
205 # export pseudo enum as configitem.*
207 # export pseudo enum as configitem.*
206 f.dynamicdefault = dynamicdefault
208 f.dynamicdefault = dynamicdefault
207 return f
209 return f
208
210
209
211
210 coreconfigitem = getitemregister(coreitems)
212 coreconfigitem = getitemregister(coreitems)
211
213
212 import_configitems_from_file()
214 import_configitems_from_file()
@@ -1,2759 +1,2807 b''
1 # configitems.toml - centralized declaration of configuration options
1 # configitems.toml - centralized declaration of configuration options
2 #
2 #
3 # This file contains declarations of the core Mercurial configuration options.
3 # This file contains declarations of the core Mercurial configuration options.
4 #
4 #
5 # # Structure
5 # # Structure
6 #
6 #
7 # items: array of config items
7 # items: array of config items
8 # templates: mapping of template name to template declaration
8 # templates: mapping of template name to template declaration
9 # template-applications: array of template applications
9 # template-applications: array of template applications
10 #
10 #
11 # # Elements
11 # # Elements
12 #
12 #
13 # ## Item
13 # ## Item
14 #
14 #
15 # Declares a core Mercurial option.
15 # Declares a core Mercurial option.
16 #
16 #
17 # - section: string (required)
17 # - section: string (required)
18 # - name: string (required)
18 # - name: string (required)
19 # - default-type: boolean, changes how `default` is read
19 # - default-type: boolean, changes how `default` is read
20 # - default: any
20 # - default: any
21 # - generic: boolean
21 # - generic: boolean
22 # - priority: integer, only if `generic` is true
22 # - priority: integer, only if `generic` is true
23 # - alias: list of 2-tuples of strings
23 # - alias: list of 2-tuples of strings
24 # - experimental: boolean
24 # - experimental: boolean
25 # - documentation: string
25 # - documentation: string
26 # - in_core_extension: string
26 #
27 #
27 # ## Template
28 # ## Template
28 #
29 #
29 # Declares a group of options to be re-used for multiple sections.
30 # Declares a group of options to be re-used for multiple sections.
30 #
31 #
31 # - all the same fields as `Item`, except `section` and `name`
32 # - all the same fields as `Item`, except `section` and `name`
32 # - `suffix` (string, required)
33 # - `suffix` (string, required)
33 #
34 #
34 # ## Template applications
35 # ## Template applications
35 #
36 #
36 # Uses a `Template` to instanciate its options in a given section.
37 # Uses a `Template` to instanciate its options in a given section.
37 #
38 #
38 # - template: string (required, must match a `Template` name)
39 # - template: string (required, must match a `Template` name)
39 # - section: string (required)
40 # - section: string (required)
40
41
41 [[items]]
42 [[items]]
42 section = "alias"
43 section = "alias"
43 name = ".*"
44 name = ".*"
44 default-type = "dynamic"
45 default-type = "dynamic"
45 generic = true
46 generic = true
46
47
47 [[items]]
48 [[items]]
48 section = "auth"
49 section = "auth"
49 name = "cookiefile"
50 name = "cookiefile"
50
51
51 # bookmarks.pushing: internal hack for discovery
52 # bookmarks.pushing: internal hack for discovery
52 [[items]]
53 [[items]]
53 section = "bookmarks"
54 section = "bookmarks"
54 name = "pushing"
55 name = "pushing"
55 default-type = "list_type"
56 default-type = "list_type"
56
57
57 # bundle.mainreporoot: internal hack for bundlerepo
58 # bundle.mainreporoot: internal hack for bundlerepo
58 [[items]]
59 [[items]]
59 section = "bundle"
60 section = "bundle"
60 name = "mainreporoot"
61 name = "mainreporoot"
61 default = ""
62 default = ""
62
63
63 [[items]]
64 [[items]]
64 section = "censor"
65 section = "censor"
65 name = "policy"
66 name = "policy"
66 default = "abort"
67 default = "abort"
67 experimental = true
68 experimental = true
68
69
69 [[items]]
70 [[items]]
70 section = "chgserver"
71 section = "chgserver"
71 name = "idletimeout"
72 name = "idletimeout"
72 default = 3600
73 default = 3600
73
74
74 [[items]]
75 [[items]]
75 section = "chgserver"
76 section = "chgserver"
76 name = "skiphash"
77 name = "skiphash"
77 default = false
78 default = false
78
79
79 [[items]]
80 [[items]]
80 section = "cmdserver"
81 section = "cmdserver"
81 name = "log"
82 name = "log"
82
83
83 [[items]]
84 [[items]]
84 section = "cmdserver"
85 section = "cmdserver"
85 name = "max-log-files"
86 name = "max-log-files"
86 default = 7
87 default = 7
87
88
88 [[items]]
89 [[items]]
89 section = "cmdserver"
90 section = "cmdserver"
90 name = "max-log-size"
91 name = "max-log-size"
91 default = "1 MB"
92 default = "1 MB"
92
93
93 [[items]]
94 [[items]]
94 section = "cmdserver"
95 section = "cmdserver"
95 name = "max-repo-cache"
96 name = "max-repo-cache"
96 default = 0
97 default = 0
97 experimental = true
98 experimental = true
98
99
99 [[items]]
100 [[items]]
100 section = "cmdserver"
101 section = "cmdserver"
101 name = "message-encodings"
102 name = "message-encodings"
102 default-type = "list_type"
103 default-type = "list_type"
103
104
104 [[items]]
105 [[items]]
105 section = "cmdserver"
106 section = "cmdserver"
106 name = "shutdown-on-interrupt"
107 name = "shutdown-on-interrupt"
107 default = true
108 default = true
108
109
109 [[items]]
110 [[items]]
110 section = "cmdserver"
111 section = "cmdserver"
111 name = "track-log"
112 name = "track-log"
112 default-type = "lambda"
113 default-type = "lambda"
113 default = [ "chgserver", "cmdserver", "repocache",]
114 default = [ "chgserver", "cmdserver", "repocache",]
114
115
115 [[items]]
116 [[items]]
116 section = "color"
117 section = "color"
117 name = ".*"
118 name = ".*"
118 generic = true
119 generic = true
119
120
120 [[items]]
121 [[items]]
121 section = "color"
122 section = "color"
122 name = "mode"
123 name = "mode"
123 default = "auto"
124 default = "auto"
124
125
125 [[items]]
126 [[items]]
126 section = "color"
127 section = "color"
127 name = "pagermode"
128 name = "pagermode"
128 default-type = "dynamic"
129 default-type = "dynamic"
129
130
130 [[items]]
131 [[items]]
131 section = "command-templates"
132 section = "command-templates"
132 name = "graphnode"
133 name = "graphnode"
133 alias = [["ui", "graphnodetemplate"]]
134 alias = [["ui", "graphnodetemplate"]]
134
135
135 [[items]]
136 [[items]]
136 section = "command-templates"
137 section = "command-templates"
137 name = "log"
138 name = "log"
138 alias = [["ui", "logtemplate"]]
139 alias = [["ui", "logtemplate"]]
139
140
140 [[items]]
141 [[items]]
141 section = "command-templates"
142 section = "command-templates"
142 name = "mergemarker"
143 name = "mergemarker"
143 default = '{node|short} {ifeq(tags, "tip", "", ifeq(tags, "", "", "{tags} "))}{if(bookmarks, "{bookmarks} ")}{ifeq(branch, "default", "", "{branch} ")}- {author|user}: {desc|firstline}'
144 default = '{node|short} {ifeq(tags, "tip", "", ifeq(tags, "", "", "{tags} "))}{if(bookmarks, "{bookmarks} ")}{ifeq(branch, "default", "", "{branch} ")}- {author|user}: {desc|firstline}'
144 alias = [["ui", "mergemarkertemplate"]]
145 alias = [["ui", "mergemarkertemplate"]]
145
146
146 [[items]]
147 [[items]]
147 section = "command-templates"
148 section = "command-templates"
148 name = "oneline-summary"
149 name = "oneline-summary"
149
150
150 [[items]]
151 [[items]]
151 section = "command-templates"
152 section = "command-templates"
152 name = "oneline-summary.*"
153 name = "oneline-summary.*"
153 default-type = "dynamic"
154 default-type = "dynamic"
154 generic = true
155 generic = true
155
156
156 [[items]]
157 [[items]]
157 section = "command-templates"
158 section = "command-templates"
158 name = "pre-merge-tool-output"
159 name = "pre-merge-tool-output"
159 alias = [["ui", "pre-merge-tool-output-template"]]
160 alias = [["ui", "pre-merge-tool-output-template"]]
160
161
161 [[items]]
162 [[items]]
162 section = "commands"
163 section = "commands"
163 name = "commit.post-status"
164 name = "commit.post-status"
164 default = false
165 default = false
165
166
166 [[items]]
167 [[items]]
167 section = "commands"
168 section = "commands"
168 name = "grep.all-files"
169 name = "grep.all-files"
169 default = false
170 default = false
170 experimental = true
171 experimental = true
171
172
172 [[items]]
173 [[items]]
173 section = "commands"
174 section = "commands"
174 name = "merge.require-rev"
175 name = "merge.require-rev"
175 default = false
176 default = false
176
177
177 [[items]]
178 [[items]]
178 section = "commands"
179 section = "commands"
179 name = "push.require-revs"
180 name = "push.require-revs"
180 default = false
181 default = false
181
182
182 # Rebase related configuration moved to core because other extension are doing
183 # Rebase related configuration moved to core because other extension are doing
183 # strange things. For example, shelve import the extensions to reuse some bit
184 # strange things. For example, shelve import the extensions to reuse some bit
184 # without formally loading it.
185 # without formally loading it.
185 [[items]]
186 [[items]]
186 section = "commands"
187 section = "commands"
187 name = "rebase.requiredest"
188 name = "rebase.requiredest"
188 default = false
189 default = false
189
190
190 [[items]]
191 [[items]]
191 section = "commands"
192 section = "commands"
192 name = "resolve.confirm"
193 name = "resolve.confirm"
193 default = false
194 default = false
194
195
195 [[items]]
196 [[items]]
196 section = "commands"
197 section = "commands"
197 name = "resolve.explicit-re-merge"
198 name = "resolve.explicit-re-merge"
198 default = false
199 default = false
199
200
200 [[items]]
201 [[items]]
201 section = "commands"
202 section = "commands"
202 name = "resolve.mark-check"
203 name = "resolve.mark-check"
203 default = "none"
204 default = "none"
204
205
205 [[items]]
206 [[items]]
206 section = "commands"
207 section = "commands"
207 name = "show.aliasprefix"
208 name = "show.aliasprefix"
208 default-type = "list_type"
209 default-type = "list_type"
209
210
210 [[items]]
211 [[items]]
211 section = "commands"
212 section = "commands"
212 name = "status.relative"
213 name = "status.relative"
213 default = false
214 default = false
214
215
215 [[items]]
216 [[items]]
216 section = "commands"
217 section = "commands"
217 name = "status.skipstates"
218 name = "status.skipstates"
218 default = []
219 default = []
219 experimental = true
220 experimental = true
220
221
221 [[items]]
222 [[items]]
222 section = "commands"
223 section = "commands"
223 name = "status.terse"
224 name = "status.terse"
224 default = ""
225 default = ""
225
226
226 [[items]]
227 [[items]]
227 section = "commands"
228 section = "commands"
228 name = "status.verbose"
229 name = "status.verbose"
229 default = false
230 default = false
230
231
231 [[items]]
232 [[items]]
232 section = "commands"
233 section = "commands"
233 name = "update.check"
234 name = "update.check"
234
235
235 [[items]]
236 [[items]]
236 section = "commands"
237 section = "commands"
237 name = "update.requiredest"
238 name = "update.requiredest"
238 default = false
239 default = false
239
240
240 [[items]]
241 [[items]]
241 section = "committemplate"
242 section = "committemplate"
242 name = ".*"
243 name = ".*"
243 generic = true
244 generic = true
244
245
245 [[items]]
246 [[items]]
246 section = "convert"
247 section = "convert"
247 name = "bzr.saverev"
248 name = "bzr.saverev"
248 default = true
249 default = true
249
250
250 [[items]]
251 [[items]]
251 section = "convert"
252 section = "convert"
252 name = "cvsps.cache"
253 name = "cvsps.cache"
253 default = true
254 default = true
254
255
255 [[items]]
256 [[items]]
256 section = "convert"
257 section = "convert"
257 name = "cvsps.fuzz"
258 name = "cvsps.fuzz"
258 default = 60
259 default = 60
259
260
260 [[items]]
261 [[items]]
261 section = "convert"
262 section = "convert"
262 name = "cvsps.logencoding"
263 name = "cvsps.logencoding"
263
264
264 [[items]]
265 [[items]]
265 section = "convert"
266 section = "convert"
266 name = "cvsps.mergefrom"
267 name = "cvsps.mergefrom"
267
268
268 [[items]]
269 [[items]]
269 section = "convert"
270 section = "convert"
270 name = "cvsps.mergeto"
271 name = "cvsps.mergeto"
271
272
272 [[items]]
273 [[items]]
273 section = "convert"
274 section = "convert"
274 name = "git.committeractions"
275 name = "git.committeractions"
275 default-type = "lambda"
276 default-type = "lambda"
276 default = [ "messagedifferent",]
277 default = [ "messagedifferent",]
277
278
278 [[items]]
279 [[items]]
279 section = "convert"
280 section = "convert"
280 name = "git.extrakeys"
281 name = "git.extrakeys"
281 default-type = "list_type"
282 default-type = "list_type"
282
283
283 [[items]]
284 [[items]]
284 section = "convert"
285 section = "convert"
285 name = "git.findcopiesharder"
286 name = "git.findcopiesharder"
286 default = false
287 default = false
287
288
288 [[items]]
289 [[items]]
289 section = "convert"
290 section = "convert"
290 name = "git.remoteprefix"
291 name = "git.remoteprefix"
291 default = "remote"
292 default = "remote"
292
293
293 [[items]]
294 [[items]]
294 section = "convert"
295 section = "convert"
295 name = "git.renamelimit"
296 name = "git.renamelimit"
296 default = 400
297 default = 400
297
298
298 [[items]]
299 [[items]]
299 section = "convert"
300 section = "convert"
300 name = "git.saverev"
301 name = "git.saverev"
301 default = true
302 default = true
302
303
303 [[items]]
304 [[items]]
304 section = "convert"
305 section = "convert"
305 name = "git.similarity"
306 name = "git.similarity"
306 default = 50
307 default = 50
307
308
308 [[items]]
309 [[items]]
309 section = "convert"
310 section = "convert"
310 name = "git.skipsubmodules"
311 name = "git.skipsubmodules"
311 default = false
312 default = false
312
313
313 [[items]]
314 [[items]]
314 section = "convert"
315 section = "convert"
315 name = "hg.clonebranches"
316 name = "hg.clonebranches"
316 default = false
317 default = false
317
318
318 [[items]]
319 [[items]]
319 section = "convert"
320 section = "convert"
320 name = "hg.ignoreerrors"
321 name = "hg.ignoreerrors"
321 default = false
322 default = false
322
323
323 [[items]]
324 [[items]]
324 section = "convert"
325 section = "convert"
325 name = "hg.preserve-hash"
326 name = "hg.preserve-hash"
326 default = false
327 default = false
327
328
328 [[items]]
329 [[items]]
329 section = "convert"
330 section = "convert"
330 name = "hg.revs"
331 name = "hg.revs"
331
332
332 [[items]]
333 [[items]]
333 section = "convert"
334 section = "convert"
334 name = "hg.saverev"
335 name = "hg.saverev"
335 default = false
336 default = false
336
337
337 [[items]]
338 [[items]]
338 section = "convert"
339 section = "convert"
339 name = "hg.sourcename"
340 name = "hg.sourcename"
340
341
341 [[items]]
342 [[items]]
342 section = "convert"
343 section = "convert"
343 name = "hg.startrev"
344 name = "hg.startrev"
344
345
345 [[items]]
346 [[items]]
346 section = "convert"
347 section = "convert"
347 name = "hg.tagsbranch"
348 name = "hg.tagsbranch"
348 default = "default"
349 default = "default"
349
350
350 [[items]]
351 [[items]]
351 section = "convert"
352 section = "convert"
352 name = "hg.usebranchnames"
353 name = "hg.usebranchnames"
353 default = true
354 default = true
354
355
355 [[items]]
356 [[items]]
356 section = "convert"
357 section = "convert"
357 name = "ignoreancestorcheck"
358 name = "ignoreancestorcheck"
358 default = false
359 default = false
359 experimental = true
360 experimental = true
360
361
361 [[items]]
362 [[items]]
362 section = "convert"
363 section = "convert"
363 name = "localtimezone"
364 name = "localtimezone"
364 default = false
365 default = false
365
366
366 [[items]]
367 [[items]]
367 section = "convert"
368 section = "convert"
368 name = "p4.encoding"
369 name = "p4.encoding"
369 default-type = "dynamic"
370 default-type = "dynamic"
370
371
371 [[items]]
372 [[items]]
372 section = "convert"
373 section = "convert"
373 name = "p4.startrev"
374 name = "p4.startrev"
374 default = 0
375 default = 0
375
376
376 [[items]]
377 [[items]]
377 section = "convert"
378 section = "convert"
378 name = "skiptags"
379 name = "skiptags"
379 default = false
380 default = false
380
381
381 [[items]]
382 [[items]]
382 section = "convert"
383 section = "convert"
383 name = "svn.branches"
384 name = "svn.branches"
384
385
385 [[items]]
386 [[items]]
386 section = "convert"
387 section = "convert"
387 name = "svn.dangerous-set-commit-dates"
388 name = "svn.dangerous-set-commit-dates"
388 default = false
389 default = false
389
390
390 [[items]]
391 [[items]]
391 section = "convert"
392 section = "convert"
392 name = "svn.debugsvnlog"
393 name = "svn.debugsvnlog"
393 default = true
394 default = true
394
395
395 [[items]]
396 [[items]]
396 section = "convert"
397 section = "convert"
397 name = "svn.startrev"
398 name = "svn.startrev"
398 default = 0
399 default = 0
399
400
400 [[items]]
401 [[items]]
401 section = "convert"
402 section = "convert"
402 name = "svn.tags"
403 name = "svn.tags"
403
404
404 [[items]]
405 [[items]]
405 section = "convert"
406 section = "convert"
406 name = "svn.trunk"
407 name = "svn.trunk"
407
408
408 [[items]]
409 [[items]]
409 section = "debug"
410 section = "debug"
410 name = "bundling-stats"
411 name = "bundling-stats"
411 default = false
412 default = false
412 documentation = "Display extra information about the bundling process."
413 documentation = "Display extra information about the bundling process."
413
414
414 [[items]]
415 [[items]]
415 section = "debug"
416 section = "debug"
416 name = "dirstate.delaywrite"
417 name = "dirstate.delaywrite"
417 default = 0
418 default = 0
418
419
419 [[items]]
420 [[items]]
420 section = "debug"
421 section = "debug"
421 name = "revlog.debug-delta"
422 name = "revlog.debug-delta"
422 default = false
423 default = false
423
424
424 [[items]]
425 [[items]]
425 section = "debug"
426 section = "debug"
426 name = "revlog.verifyposition.changelog"
427 name = "revlog.verifyposition.changelog"
427 default = ""
428 default = ""
428
429
429 [[items]]
430 [[items]]
430 section = "debug"
431 section = "debug"
431 name = "unbundling-stats"
432 name = "unbundling-stats"
432 default = false
433 default = false
433 documentation = "Display extra information about the unbundling process."
434 documentation = "Display extra information about the unbundling process."
434
435
435 [[items]]
436 [[items]]
436 section = "defaults"
437 section = "defaults"
437 name = ".*"
438 name = ".*"
438 generic = true
439 generic = true
439
440
440 [[items]]
441 [[items]]
441 section = "devel"
442 section = "devel"
442 name = "all-warnings"
443 name = "all-warnings"
443 default = false
444 default = false
444
445
445 [[items]]
446 [[items]]
446 section = "devel"
447 section = "devel"
447 name = "bundle.delta"
448 name = "bundle.delta"
448 default = ""
449 default = ""
449
450
450 [[items]]
451 [[items]]
451 section = "devel"
452 section = "devel"
452 name = "bundle2.debug"
453 name = "bundle2.debug"
453 default = false
454 default = false
454
455
455 [[items]]
456 [[items]]
456 section = "devel"
457 section = "devel"
457 name = "cache-vfs"
458 name = "cache-vfs"
458
459
459 [[items]]
460 [[items]]
460 section = "devel"
461 section = "devel"
461 name = "check-locks"
462 name = "check-locks"
462 default = false
463 default = false
463
464
464 [[items]]
465 [[items]]
465 section = "devel"
466 section = "devel"
466 name = "check-relroot"
467 name = "check-relroot"
467 default = false
468 default = false
468
469
469 [[items]]
470 [[items]]
470 section = "devel"
471 section = "devel"
471 name = "copy-tracing.multi-thread"
472 name = "copy-tracing.multi-thread"
472 default = true
473 default = true
473
474
474 # Track copy information for all files, not just "added" ones (very slow)
475 # Track copy information for all files, not just "added" ones (very slow)
475 [[items]]
476 [[items]]
476 section = "devel"
477 section = "devel"
477 name = "copy-tracing.trace-all-files"
478 name = "copy-tracing.trace-all-files"
478 default = false
479 default = false
479
480
480 [[items]]
481 [[items]]
481 section = "devel"
482 section = "devel"
482 name = "debug.abort-update"
483 name = "debug.abort-update"
483 default = false
484 default = false
484 documentation = """If true, then any merge with the working copy, \
485 documentation = """If true, then any merge with the working copy, \
485 e.g. [hg update], will be aborted after figuring out what needs to be done, \
486 e.g. [hg update], will be aborted after figuring out what needs to be done, \
486 but before spawning the parallel worker."""
487 but before spawning the parallel worker."""
487
488
488 [[items]]
489 [[items]]
489 section = "devel"
490 section = "devel"
490 name = "debug.copies"
491 name = "debug.copies"
491 default = false
492 default = false
492
493
493 [[items]]
494 [[items]]
494 section = "devel"
495 section = "devel"
495 name = "debug.extensions"
496 name = "debug.extensions"
496 default = false
497 default = false
497
498
498 [[items]]
499 [[items]]
499 section = "devel"
500 section = "devel"
500 name = "debug.peer-request"
501 name = "debug.peer-request"
501 default = false
502 default = false
502
503
503 [[items]]
504 [[items]]
504 section = "devel"
505 section = "devel"
505 name = "debug.repo-filters"
506 name = "debug.repo-filters"
506 default = false
507 default = false
507
508
508 [[items]]
509 [[items]]
509 section = "devel"
510 section = "devel"
510 name = "default-date"
511 name = "default-date"
511
512
512 [[items]]
513 [[items]]
513 section = "devel"
514 section = "devel"
514 name = "deprec-warn"
515 name = "deprec-warn"
515 default = false
516 default = false
516
517
517 # possible values:
518 # possible values:
518 # - auto (the default)
519 # - auto (the default)
519 # - force-append
520 # - force-append
520 # - force-new
521 # - force-new
521 [[items]]
522 [[items]]
522 section = "devel"
523 section = "devel"
523 name = "dirstate.v2.data_update_mode"
524 name = "dirstate.v2.data_update_mode"
524 default = "auto"
525 default = "auto"
525
526
526 [[items]]
527 [[items]]
527 section = "devel"
528 section = "devel"
528 name = "disableloaddefaultcerts"
529 name = "disableloaddefaultcerts"
529 default = false
530 default = false
530
531
531 [[items]]
532 [[items]]
532 section = "devel"
533 section = "devel"
533 name = "discovery.exchange-heads"
534 name = "discovery.exchange-heads"
534 default = true
535 default = true
535 documentation = """If false, the discovery will not start with remote \
536 documentation = """If false, the discovery will not start with remote \
536 head fetching and local head querying."""
537 head fetching and local head querying."""
537
538
538 [[items]]
539 [[items]]
539 section = "devel"
540 section = "devel"
540 name = "discovery.grow-sample"
541 name = "discovery.grow-sample"
541 default = true
542 default = true
542 documentation = """If false, the sample size used in set discovery \
543 documentation = """If false, the sample size used in set discovery \
543 will not be increased through the process."""
544 will not be increased through the process."""
544
545
545 [[items]]
546 [[items]]
546 section = "devel"
547 section = "devel"
547 name = "discovery.grow-sample.dynamic"
548 name = "discovery.grow-sample.dynamic"
548 default = true
549 default = true
549 documentation = """If true, the default, the sample size is adapted to the shape \
550 documentation = """If true, the default, the sample size is adapted to the shape \
550 of the undecided set. It is set to the max of:
551 of the undecided set. It is set to the max of:
551 `<target-size>, len(roots(undecided)), len(heads(undecided))`"""
552 `<target-size>, len(roots(undecided)), len(heads(undecided))`"""
552
553
553 [[items]]
554 [[items]]
554 section = "devel"
555 section = "devel"
555 name = "discovery.grow-sample.rate"
556 name = "discovery.grow-sample.rate"
556 default = 1.05
557 default = 1.05
557 documentation = "Controls the rate at which the sample grows."
558 documentation = "Controls the rate at which the sample grows."
558
559
559 [[items]]
560 [[items]]
560 section = "devel"
561 section = "devel"
561 name = "discovery.randomize"
562 name = "discovery.randomize"
562 default = true
563 default = true
563 documentation = """If false, random samplings during discovery are deterministic. \
564 documentation = """If false, random samplings during discovery are deterministic. \
564 It is meant for integration tests."""
565 It is meant for integration tests."""
565
566
566 [[items]]
567 [[items]]
567 section = "devel"
568 section = "devel"
568 name = "discovery.sample-size"
569 name = "discovery.sample-size"
569 default = 200
570 default = 200
570 documentation = "Controls the initial size of the discovery sample."
571 documentation = "Controls the initial size of the discovery sample."
571
572
572 [[items]]
573 [[items]]
573 section = "devel"
574 section = "devel"
574 name = "discovery.sample-size.initial"
575 name = "discovery.sample-size.initial"
575 default = 100
576 default = 100
576 documentation = "Controls the initial size of the discovery for initial change."
577 documentation = "Controls the initial size of the discovery for initial change."
577
578
578 [[items]]
579 [[items]]
579 section = "devel"
580 section = "devel"
580 name = "legacy.exchange"
581 name = "legacy.exchange"
581 default-type = "list_type"
582 default-type = "list_type"
582
583
583 [[items]]
584 [[items]]
584 section = "devel"
585 section = "devel"
585 name = "persistent-nodemap"
586 name = "persistent-nodemap"
586 default = false
587 default = false
587 documentation = """When true, revlogs use a special reference version of the \
588 documentation = """When true, revlogs use a special reference version of the \
588 nodemap, that is not performant but is "known" to behave properly."""
589 nodemap, that is not performant but is "known" to behave properly."""
589
590
590 [[items]]
591 [[items]]
591 section = "devel"
592 section = "devel"
592 name = "server-insecure-exact-protocol"
593 name = "server-insecure-exact-protocol"
593 default = ""
594 default = ""
594
595
595 [[items]]
596 [[items]]
596 section = "devel"
597 section = "devel"
597 name = "servercafile"
598 name = "servercafile"
598 default = ""
599 default = ""
599
600
600 [[items]]
601 [[items]]
601 section = "devel"
602 section = "devel"
602 name = "serverexactprotocol"
603 name = "serverexactprotocol"
603 default = ""
604 default = ""
604
605
605 [[items]]
606 [[items]]
606 section = "devel"
607 section = "devel"
607 name = "serverrequirecert"
608 name = "serverrequirecert"
608 default = false
609 default = false
609
610
610 [[items]]
611 [[items]]
611 section = "devel"
612 section = "devel"
612 name = "strip-obsmarkers"
613 name = "strip-obsmarkers"
613 default = true
614 default = true
614
615
615 [[items]]
616 [[items]]
616 section = 'devel'
617 section = 'devel'
617 name = 'sync.status.pre-dirstate-write-file'
618 name = 'sync.status.pre-dirstate-write-file'
618 documentation = """
619 documentation = """
619 Makes the status algorithm wait for the existence of this file \
620 Makes the status algorithm wait for the existence of this file \
620 (or until a timeout of `devel.sync.status.pre-dirstate-write-file-timeout` \
621 (or until a timeout of `devel.sync.status.pre-dirstate-write-file-timeout` \
621 seconds) before taking the lock and writing the dirstate. \
622 seconds) before taking the lock and writing the dirstate. \
622 Status signals that it's ready to wait by creating a file \
623 Status signals that it's ready to wait by creating a file \
623 with the same name + `.waiting`. \
624 with the same name + `.waiting`. \
624 Useful when testing race conditions."""
625 Useful when testing race conditions."""
625
626
626 [[items]]
627 [[items]]
627 section = 'devel'
628 section = 'devel'
628 name = 'sync.status.pre-dirstate-write-file-timeout'
629 name = 'sync.status.pre-dirstate-write-file-timeout'
629 default=2
630 default=2
630
631
631 [[items]]
632 [[items]]
632 section = 'devel'
633 section = 'devel'
633 name = 'sync.dirstate.post-docket-read-file'
634 name = 'sync.dirstate.post-docket-read-file'
634
635
635 [[items]]
636 [[items]]
636 section = 'devel'
637 section = 'devel'
637 name = 'sync.dirstate.post-docket-read-file-timeout'
638 name = 'sync.dirstate.post-docket-read-file-timeout'
638 default=2
639 default=2
639
640
640 [[items]]
641 [[items]]
641 section = 'devel'
642 section = 'devel'
642 name = 'sync.dirstate.pre-read-file'
643 name = 'sync.dirstate.pre-read-file'
643
644
644 [[items]]
645 [[items]]
645 section = 'devel'
646 section = 'devel'
646 name = 'sync.dirstate.pre-read-file-timeout'
647 name = 'sync.dirstate.pre-read-file-timeout'
647 default=2
648 default=2
648
649
649 [[items]]
650 [[items]]
650 section = "devel"
651 section = "devel"
651 name = "user.obsmarker"
652 name = "user.obsmarker"
652
653
653 [[items]]
654 [[items]]
654 section = "devel"
655 section = "devel"
655 name = "warn-config"
656 name = "warn-config"
656
657
657 [[items]]
658 [[items]]
658 section = "devel"
659 section = "devel"
659 name = "warn-config-default"
660 name = "warn-config-default"
660
661
661 [[items]]
662 [[items]]
662 section = "devel"
663 section = "devel"
663 name = "warn-config-unknown"
664 name = "warn-config-unknown"
664
665
665 [[items]]
666 [[items]]
666 section = "devel"
667 section = "devel"
667 name = "warn-empty-changegroup"
668 name = "warn-empty-changegroup"
668 default = false
669 default = false
669
670
670 [[items]]
671 [[items]]
671 section = "diff"
672 section = "diff"
672 name = "merge"
673 name = "merge"
673 default = false
674 default = false
674 experimental = true
675 experimental = true
675
676
676 [[items]]
677 [[items]]
677 section = "email"
678 section = "email"
678 name = "bcc"
679 name = "bcc"
679
680
680 [[items]]
681 [[items]]
681 section = "email"
682 section = "email"
682 name = "cc"
683 name = "cc"
683
684
684 [[items]]
685 [[items]]
685 section = "email"
686 section = "email"
686 name = "charsets"
687 name = "charsets"
687 default-type = "list_type"
688 default-type = "list_type"
688
689
689 [[items]]
690 [[items]]
690 section = "email"
691 section = "email"
691 name = "from"
692 name = "from"
692
693
693 [[items]]
694 [[items]]
694 section = "email"
695 section = "email"
695 name = "method"
696 name = "method"
696 default = "smtp"
697 default = "smtp"
697
698
698 [[items]]
699 [[items]]
699 section = "email"
700 section = "email"
700 name = "reply-to"
701 name = "reply-to"
701
702
702 [[items]]
703 [[items]]
703 section = "email"
704 section = "email"
704 name = "to"
705 name = "to"
705
706
706 [[items]]
707 [[items]]
707 section = "experimental"
708 section = "experimental"
708 name = "archivemetatemplate"
709 name = "archivemetatemplate"
709 default-type = "dynamic"
710 default-type = "dynamic"
710
711
711 [[items]]
712 [[items]]
712 section = "experimental"
713 section = "experimental"
713 name = "auto-publish"
714 name = "auto-publish"
714 default = "publish"
715 default = "publish"
715
716
716 [[items]]
717 [[items]]
717 section = "experimental"
718 section = "experimental"
718 name = "bundle-phases"
719 name = "bundle-phases"
719 default = false
720 default = false
720
721
721 [[items]]
722 [[items]]
722 section = "experimental"
723 section = "experimental"
723 name = "bundle2-advertise"
724 name = "bundle2-advertise"
724 default = true
725 default = true
725
726
726 [[items]]
727 [[items]]
727 section = "experimental"
728 section = "experimental"
728 name = "bundle2-output-capture"
729 name = "bundle2-output-capture"
729 default = false
730 default = false
730
731
731 [[items]]
732 [[items]]
732 section = "experimental"
733 section = "experimental"
733 name = "bundle2.pushback"
734 name = "bundle2.pushback"
734 default = false
735 default = false
735
736
736 [[items]]
737 [[items]]
737 section = "experimental"
738 section = "experimental"
738 name = "bundle2lazylocking"
739 name = "bundle2lazylocking"
739 default = false
740 default = false
740
741
741 [[items]]
742 [[items]]
742 section = "experimental"
743 section = "experimental"
743 name = "bundlecomplevel"
744 name = "bundlecomplevel"
744
745
745 [[items]]
746 [[items]]
746 section = "experimental"
747 section = "experimental"
747 name = "bundlecomplevel.bzip2"
748 name = "bundlecomplevel.bzip2"
748
749
749 [[items]]
750 [[items]]
750 section = "experimental"
751 section = "experimental"
751 name = "bundlecomplevel.gzip"
752 name = "bundlecomplevel.gzip"
752
753
753 [[items]]
754 [[items]]
754 section = "experimental"
755 section = "experimental"
755 name = "bundlecomplevel.none"
756 name = "bundlecomplevel.none"
756
757
757 [[items]]
758 [[items]]
758 section = "experimental"
759 section = "experimental"
759 name = "bundlecomplevel.zstd"
760 name = "bundlecomplevel.zstd"
760
761
761 [[items]]
762 [[items]]
762 section = "experimental"
763 section = "experimental"
763 name = "bundlecompthreads"
764 name = "bundlecompthreads"
764
765
765 [[items]]
766 [[items]]
766 section = "experimental"
767 section = "experimental"
767 name = "bundlecompthreads.bzip2"
768 name = "bundlecompthreads.bzip2"
768
769
769 [[items]]
770 [[items]]
770 section = "experimental"
771 section = "experimental"
771 name = "bundlecompthreads.gzip"
772 name = "bundlecompthreads.gzip"
772
773
773 [[items]]
774 [[items]]
774 section = "experimental"
775 section = "experimental"
775 name = "bundlecompthreads.none"
776 name = "bundlecompthreads.none"
776
777
777 [[items]]
778 [[items]]
778 section = "experimental"
779 section = "experimental"
779 name = "bundlecompthreads.zstd"
780 name = "bundlecompthreads.zstd"
780
781
781 [[items]]
782 [[items]]
782 section = "experimental"
783 section = "experimental"
783 name = "changegroup3"
784 name = "changegroup3"
784 default = true
785 default = true
785
786
786 [[items]]
787 [[items]]
787 section = "experimental"
788 section = "experimental"
788 name = "changegroup4"
789 name = "changegroup4"
789 default = false
790 default = false
790
791
791 # might remove rank configuration once the computation has no impact
792 # might remove rank configuration once the computation has no impact
792 [[items]]
793 [[items]]
793 section = "experimental"
794 section = "experimental"
794 name = "changelog-v2.compute-rank"
795 name = "changelog-v2.compute-rank"
795 default = true
796 default = true
796
797
797 [[items]]
798 [[items]]
798 section = "experimental"
799 section = "experimental"
799 name = "cleanup-as-archived"
800 name = "cleanup-as-archived"
800 default = false
801 default = false
801
802
802 [[items]]
803 [[items]]
803 section = "experimental"
804 section = "experimental"
804 name = "clientcompressionengines"
805 name = "clientcompressionengines"
805 default-type = "list_type"
806 default-type = "list_type"
806
807
807 [[items]]
808 [[items]]
808 section = "experimental"
809 section = "experimental"
809 name = "copies.read-from"
810 name = "copies.read-from"
810 default = "filelog-only"
811 default = "filelog-only"
811
812
812 [[items]]
813 [[items]]
813 section = "experimental"
814 section = "experimental"
814 name = "copies.write-to"
815 name = "copies.write-to"
815 default = "filelog-only"
816 default = "filelog-only"
816
817
817 [[items]]
818 [[items]]
818 section = "experimental"
819 section = "experimental"
819 name = "copytrace"
820 name = "copytrace"
820 default = "on"
821 default = "on"
821
822
822 [[items]]
823 [[items]]
823 section = "experimental"
824 section = "experimental"
824 name = "copytrace.movecandidateslimit"
825 name = "copytrace.movecandidateslimit"
825 default = 100
826 default = 100
826
827
827 [[items]]
828 [[items]]
828 section = "experimental"
829 section = "experimental"
829 name = "copytrace.sourcecommitlimit"
830 name = "copytrace.sourcecommitlimit"
830 default = 100
831 default = 100
831
832
832 [[items]]
833 [[items]]
833 section = "experimental"
834 section = "experimental"
834 name = "crecordtest"
835 name = "crecordtest"
835
836
836 [[items]]
837 [[items]]
837 section = "experimental"
838 section = "experimental"
838 name = "directaccess"
839 name = "directaccess"
839 default = false
840 default = false
840
841
841 [[items]]
842 [[items]]
842 section = "experimental"
843 section = "experimental"
843 name = "directaccess.revnums"
844 name = "directaccess.revnums"
844 default = false
845 default = false
845
846
846 [[items]]
847 [[items]]
847 section = "experimental"
848 section = "experimental"
848 name = "editortmpinhg"
849 name = "editortmpinhg"
849 default = false
850 default = false
850
851
851 [[items]]
852 [[items]]
852 section = "experimental"
853 section = "experimental"
853 name = "evolution"
854 name = "evolution"
854 default-type = "list_type"
855 default-type = "list_type"
855
856
856 [[items]]
857 [[items]]
857 section = "experimental"
858 section = "experimental"
858 name = "evolution.allowdivergence"
859 name = "evolution.allowdivergence"
859 default = false
860 default = false
860 alias = [["experimental", "allowdivergence"]]
861 alias = [["experimental", "allowdivergence"]]
861
862
862 [[items]]
863 [[items]]
863 section = "experimental"
864 section = "experimental"
864 name = "evolution.allowunstable"
865 name = "evolution.allowunstable"
865
866
866 [[items]]
867 [[items]]
867 section = "experimental"
868 section = "experimental"
868 name = "evolution.bundle-obsmarker"
869 name = "evolution.bundle-obsmarker"
869 default = false
870 default = false
870
871
871 [[items]]
872 [[items]]
872 section = "experimental"
873 section = "experimental"
873 name = "evolution.bundle-obsmarker:mandatory"
874 name = "evolution.bundle-obsmarker:mandatory"
874 default = true
875 default = true
875
876
876 [[items]]
877 [[items]]
877 section = "experimental"
878 section = "experimental"
878 name = "evolution.createmarkers"
879 name = "evolution.createmarkers"
879
880
880 [[items]]
881 [[items]]
881 section = "experimental"
882 section = "experimental"
882 name = "evolution.effect-flags"
883 name = "evolution.effect-flags"
883 default = true
884 default = true
884 alias = [["experimental", "effect-flags"]]
885 alias = [["experimental", "effect-flags"]]
885
886
886 [[items]]
887 [[items]]
887 section = "experimental"
888 section = "experimental"
888 name = "evolution.exchange"
889 name = "evolution.exchange"
889
890
890 [[items]]
891 [[items]]
891 section = "experimental"
892 section = "experimental"
892 name = "evolution.report-instabilities"
893 name = "evolution.report-instabilities"
893 default = true
894 default = true
894
895
895 [[items]]
896 [[items]]
896 section = "experimental"
897 section = "experimental"
897 name = "evolution.track-operation"
898 name = "evolution.track-operation"
898 default = true
899 default = true
899
900
900 [[items]]
901 [[items]]
901 section = "experimental"
902 section = "experimental"
902 name = "exportableenviron"
903 name = "exportableenviron"
903 default-type = "list_type"
904 default-type = "list_type"
904
905
905 [[items]]
906 [[items]]
906 section = "experimental"
907 section = "experimental"
907 name = "extendedheader.index"
908 name = "extendedheader.index"
908
909
909 [[items]]
910 [[items]]
910 section = "experimental"
911 section = "experimental"
911 name = "extendedheader.similarity"
912 name = "extendedheader.similarity"
912 default = false
913 default = false
913
914
914 [[items]]
915 [[items]]
915 section = "experimental"
916 section = "experimental"
916 name = "extra-filter-revs"
917 name = "extra-filter-revs"
917 documentation = """Repo-level config to prevent a revset from being visible.
918 documentation = """Repo-level config to prevent a revset from being visible.
918 The target use case is to use `share` to expose different subsets of the same \
919 The target use case is to use `share` to expose different subsets of the same \
919 repository, especially server side. See also `server.view`."""
920 repository, especially server side. See also `server.view`."""
920
921
921 [[items]]
922 [[items]]
922 section = "experimental"
923 section = "experimental"
923 name = "graphshorten"
924 name = "graphshorten"
924 default = false
925 default = false
925
926
926 [[items]]
927 [[items]]
927 section = "experimental"
928 section = "experimental"
928 name = "graphstyle.grandparent"
929 name = "graphstyle.grandparent"
929 default-type = "dynamic"
930 default-type = "dynamic"
930
931
931 [[items]]
932 [[items]]
932 section = "experimental"
933 section = "experimental"
933 name = "graphstyle.missing"
934 name = "graphstyle.missing"
934 default-type = "dynamic"
935 default-type = "dynamic"
935
936
936 [[items]]
937 [[items]]
937 section = "experimental"
938 section = "experimental"
938 name = "graphstyle.parent"
939 name = "graphstyle.parent"
939 default-type = "dynamic"
940 default-type = "dynamic"
940
941
941 [[items]]
942 [[items]]
942 section = "experimental"
943 section = "experimental"
943 name = "hook-track-tags"
944 name = "hook-track-tags"
944 default = false
945 default = false
945
946
946 [[items]]
947 [[items]]
947 section = "experimental"
948 section = "experimental"
948 name = "httppostargs"
949 name = "httppostargs"
949 default = false
950 default = false
950
951
951 [[items]]
952 [[items]]
952 section = "experimental"
953 section = "experimental"
953 name = "log.topo"
954 name = "log.topo"
954 default = false
955 default = false
955
956
956 [[items]]
957 [[items]]
957 section = "experimental"
958 section = "experimental"
958 name = "maxdeltachainspan"
959 name = "maxdeltachainspan"
959 default = -1
960 default = -1
960
961
961 [[items]]
962 [[items]]
962 section = "experimental"
963 section = "experimental"
963 name = "merge-track-salvaged"
964 name = "merge-track-salvaged"
964 default = false
965 default = false
965 documentation = """Tracks files which were undeleted (merge might delete them \
966 documentation = """Tracks files which were undeleted (merge might delete them \
966 but we explicitly kept/undeleted them) and creates new filenodes for them."""
967 but we explicitly kept/undeleted them) and creates new filenodes for them."""
967
968
968 [[items]]
969 [[items]]
969 section = "experimental"
970 section = "experimental"
970 name = "merge.checkpathconflicts"
971 name = "merge.checkpathconflicts"
971 default = false
972 default = false
972
973
973 [[items]]
974 [[items]]
974 section = "experimental"
975 section = "experimental"
975 name = "mmapindexthreshold"
976 name = "mmapindexthreshold"
976
977
977 [[items]]
978 [[items]]
978 section = "experimental"
979 section = "experimental"
979 name = "narrow"
980 name = "narrow"
980 default = false
981 default = false
981
982
982 [[items]]
983 [[items]]
983 section = "experimental"
984 section = "experimental"
984 name = "nointerrupt"
985 name = "nointerrupt"
985 default = false
986 default = false
986
987
987 [[items]]
988 [[items]]
988 section = "experimental"
989 section = "experimental"
989 name = "nointerrupt-interactiveonly"
990 name = "nointerrupt-interactiveonly"
990 default = true
991 default = true
991
992
992 [[items]]
993 [[items]]
993 section = "experimental"
994 section = "experimental"
994 name = "nonnormalparanoidcheck"
995 name = "nonnormalparanoidcheck"
995 default = false
996 default = false
996
997
997 [[items]]
998 [[items]]
998 section = "experimental"
999 section = "experimental"
999 name = "obsmarkers-exchange-debug"
1000 name = "obsmarkers-exchange-debug"
1000 default = false
1001 default = false
1001
1002
1002 [[items]]
1003 [[items]]
1003 section = "experimental"
1004 section = "experimental"
1004 name = "rebaseskipobsolete"
1005 name = "rebaseskipobsolete"
1005 default = true
1006 default = true
1006
1007
1007 [[items]]
1008 [[items]]
1008 section = "experimental"
1009 section = "experimental"
1009 name = "remotenames"
1010 name = "remotenames"
1010 default = false
1011 default = false
1011
1012
1012 [[items]]
1013 [[items]]
1013 section = "experimental"
1014 section = "experimental"
1014 name = "removeemptydirs"
1015 name = "removeemptydirs"
1015 default = true
1016 default = true
1016
1017
1017 [[items]]
1018 [[items]]
1018 section = "experimental"
1019 section = "experimental"
1019 name = "revert.interactive.select-to-keep"
1020 name = "revert.interactive.select-to-keep"
1020 default = false
1021 default = false
1021
1022
1022 [[items]]
1023 [[items]]
1023 section = "experimental"
1024 section = "experimental"
1024 name = "revisions.disambiguatewithin"
1025 name = "revisions.disambiguatewithin"
1025
1026
1026 [[items]]
1027 [[items]]
1027 section = "experimental"
1028 section = "experimental"
1028 name = "revisions.prefixhexnode"
1029 name = "revisions.prefixhexnode"
1029 default = false
1030 default = false
1030
1031
1031 # "out of experimental" todo list.
1032 # "out of experimental" todo list.
1032 #
1033 #
1033 # * include management of a persistent nodemap in the main docket
1034 # * include management of a persistent nodemap in the main docket
1034 # * enforce a "no-truncate" policy for mmap safety
1035 # * enforce a "no-truncate" policy for mmap safety
1035 # - for censoring operation
1036 # - for censoring operation
1036 # - for stripping operation
1037 # - for stripping operation
1037 # - for rollback operation
1038 # - for rollback operation
1038 # * proper streaming (race free) of the docket file
1039 # * proper streaming (race free) of the docket file
1039 # * track garbage data to evemtually allow rewriting -existing- sidedata.
1040 # * track garbage data to evemtually allow rewriting -existing- sidedata.
1040 # * Exchange-wise, we will also need to do something more efficient than
1041 # * Exchange-wise, we will also need to do something more efficient than
1041 # keeping references to the affected revlogs, especially memory-wise when
1042 # keeping references to the affected revlogs, especially memory-wise when
1042 # rewriting sidedata.
1043 # rewriting sidedata.
1043 # * introduce a proper solution to reduce the number of filelog related files.
1044 # * introduce a proper solution to reduce the number of filelog related files.
1044 # * use caching for reading sidedata (similar to what we do for data).
1045 # * use caching for reading sidedata (similar to what we do for data).
1045 # * no longer set offset=0 if sidedata_size=0 (simplify cutoff computation).
1046 # * no longer set offset=0 if sidedata_size=0 (simplify cutoff computation).
1046 # * Improvement to consider
1047 # * Improvement to consider
1047 # - avoid compression header in chunk using the default compression?
1048 # - avoid compression header in chunk using the default compression?
1048 # - forbid "inline" compression mode entirely?
1049 # - forbid "inline" compression mode entirely?
1049 # - split the data offset and flag field (the 2 bytes save are mostly trouble)
1050 # - split the data offset and flag field (the 2 bytes save are mostly trouble)
1050 # - keep track of uncompressed -chunk- size (to preallocate memory better)
1051 # - keep track of uncompressed -chunk- size (to preallocate memory better)
1051 # - keep track of chain base or size (probably not that useful anymore)
1052 # - keep track of chain base or size (probably not that useful anymore)
1052 [[items]]
1053 [[items]]
1053 section = "experimental"
1054 section = "experimental"
1054 name = "revlogv2"
1055 name = "revlogv2"
1055
1056
1056 [[items]]
1057 [[items]]
1057 section = "experimental"
1058 section = "experimental"
1058 name = "rust.index"
1059 name = "rust.index"
1059 default = false
1060 default = false
1060
1061
1061 [[items]]
1062 [[items]]
1062 section = "experimental"
1063 section = "experimental"
1063 name = "server.allow-hidden-access"
1064 name = "server.allow-hidden-access"
1064 default-type = "list_type"
1065 default-type = "list_type"
1065
1066
1066 [[items]]
1067 [[items]]
1067 section = "experimental"
1068 section = "experimental"
1068 name = "server.filesdata.recommended-batch-size"
1069 name = "server.filesdata.recommended-batch-size"
1069 default = 50000
1070 default = 50000
1070
1071
1071 [[items]]
1072 [[items]]
1072 section = "experimental"
1073 section = "experimental"
1073 name = "server.manifestdata.recommended-batch-size"
1074 name = "server.manifestdata.recommended-batch-size"
1074 default = 100000
1075 default = 100000
1075
1076
1076 [[items]]
1077 [[items]]
1077 section = "experimental"
1078 section = "experimental"
1078 name = "server.stream-narrow-clones"
1079 name = "server.stream-narrow-clones"
1079 default = false
1080 default = false
1080
1081
1081 [[items]]
1082 [[items]]
1082 section = "experimental"
1083 section = "experimental"
1083 name = "single-head-per-branch"
1084 name = "single-head-per-branch"
1084 default = false
1085 default = false
1085
1086
1086 [[items]]
1087 [[items]]
1087 section = "experimental"
1088 section = "experimental"
1088 name = "single-head-per-branch:account-closed-heads"
1089 name = "single-head-per-branch:account-closed-heads"
1089 default = false
1090 default = false
1090
1091
1091 [[items]]
1092 [[items]]
1092 section = "experimental"
1093 section = "experimental"
1093 name = "single-head-per-branch:public-changes-only"
1094 name = "single-head-per-branch:public-changes-only"
1094 default = false
1095 default = false
1095
1096
1096 [[items]]
1097 [[items]]
1097 section = "experimental"
1098 section = "experimental"
1098 name = "sparse-read"
1099 name = "sparse-read"
1099 default = false
1100 default = false
1100
1101
1101 [[items]]
1102 [[items]]
1102 section = "experimental"
1103 section = "experimental"
1103 name = "sparse-read.density-threshold"
1104 name = "sparse-read.density-threshold"
1104 default = 0.5
1105 default = 0.5
1105
1106
1106 [[items]]
1107 [[items]]
1107 section = "experimental"
1108 section = "experimental"
1108 name = "sparse-read.min-gap-size"
1109 name = "sparse-read.min-gap-size"
1109 default = "65K"
1110 default = "65K"
1110
1111
1111 [[items]]
1112 [[items]]
1112 section = "experimental"
1113 section = "experimental"
1113 name = "stream-v3"
1114 name = "stream-v3"
1114 default = false
1115 default = false
1115
1116
1116 [[items]]
1117 [[items]]
1117 section = "experimental"
1118 section = "experimental"
1118 name = "treemanifest"
1119 name = "treemanifest"
1119 default = false
1120 default = false
1120
1121
1121 [[items]]
1122 [[items]]
1122 section = "experimental"
1123 section = "experimental"
1123 name = "update.atomic-file"
1124 name = "update.atomic-file"
1124 default = false
1125 default = false
1125
1126
1126 [[items]]
1127 [[items]]
1127 section = "experimental"
1128 section = "experimental"
1128 name = "web.full-garbage-collection-rate"
1129 name = "web.full-garbage-collection-rate"
1129 default = 1 # still forcing a full collection on each request
1130 default = 1 # still forcing a full collection on each request
1130
1131
1131 [[items]]
1132 [[items]]
1132 section = "experimental"
1133 section = "experimental"
1133 name = "worker.repository-upgrade"
1134 name = "worker.repository-upgrade"
1134 default = false
1135 default = false
1135
1136
1136 [[items]]
1137 [[items]]
1137 section = "experimental"
1138 section = "experimental"
1138 name = "worker.wdir-get-thread-safe"
1139 name = "worker.wdir-get-thread-safe"
1139 default = false
1140 default = false
1140
1141
1141 [[items]]
1142 [[items]]
1142 section = "experimental"
1143 section = "experimental"
1143 name = "xdiff"
1144 name = "xdiff"
1144 default = false
1145 default = false
1145
1146
1146 [[items]]
1147 [[items]]
1147 section = "extdata"
1148 section = "extdata"
1148 name = ".*"
1149 name = ".*"
1149 generic = true
1150 generic = true
1150
1151
1151 [[items]]
1152 [[items]]
1152 section = "extensions"
1153 section = "extensions"
1153 name = "[^:]*"
1154 name = "[^:]*"
1154 generic = true
1155 generic = true
1155
1156
1156 [[items]]
1157 [[items]]
1157 section = "extensions"
1158 section = "extensions"
1158 name = "[^:]*:required"
1159 name = "[^:]*:required"
1159 default = false
1160 default = false
1160 generic = true
1161 generic = true
1161
1162
1162 [[items]]
1163 [[items]]
1163 section = "format"
1164 section = "format"
1164 name = "bookmarks-in-store"
1165 name = "bookmarks-in-store"
1165 default = false
1166 default = false
1166
1167
1167 [[items]]
1168 [[items]]
1168 section = "format"
1169 section = "format"
1169 name = "chunkcachesize"
1170 name = "chunkcachesize"
1170 experimental = true
1171 experimental = true
1171
1172
1172 [[items]]
1173 [[items]]
1173 section = "format"
1174 section = "format"
1174 name = "dotencode"
1175 name = "dotencode"
1175 default = true
1176 default = true
1176
1177
1177 # The interaction between the archived phase and obsolescence markers needs to
1178 # The interaction between the archived phase and obsolescence markers needs to
1178 # be sorted out before wider usage of this are to be considered.
1179 # be sorted out before wider usage of this are to be considered.
1179 #
1180 #
1180 # At the time this message is written, behavior when archiving obsolete
1181 # At the time this message is written, behavior when archiving obsolete
1181 # changeset differ significantly from stripping. As part of stripping, we also
1182 # changeset differ significantly from stripping. As part of stripping, we also
1182 # remove the obsolescence marker associated to the stripped changesets,
1183 # remove the obsolescence marker associated to the stripped changesets,
1183 # revealing the precedecessors changesets when applicable. When archiving, we
1184 # revealing the precedecessors changesets when applicable. When archiving, we
1184 # don't touch the obsolescence markers, keeping everything hidden. This can
1185 # don't touch the obsolescence markers, keeping everything hidden. This can
1185 # result in quite confusing situation for people combining exchanging draft
1186 # result in quite confusing situation for people combining exchanging draft
1186 # with the archived phases. As some markers needed by others may be skipped
1187 # with the archived phases. As some markers needed by others may be skipped
1187 # during exchange.
1188 # during exchange.
1188 [[items]]
1189 [[items]]
1189 section = "format"
1190 section = "format"
1190 name = "exp-archived-phase"
1191 name = "exp-archived-phase"
1191 default = false
1192 default = false
1192 experimental = true
1193 experimental = true
1193
1194
1194 # Experimental TODOs:
1195 # Experimental TODOs:
1195 #
1196 #
1196 # * Same as for revlogv2 (but for the reduction of the number of files)
1197 # * Same as for revlogv2 (but for the reduction of the number of files)
1197 # * Actually computing the rank of changesets
1198 # * Actually computing the rank of changesets
1198 # * Improvement to investigate
1199 # * Improvement to investigate
1199 # - storing .hgtags fnode
1200 # - storing .hgtags fnode
1200 # - storing branch related identifier
1201 # - storing branch related identifier
1201 [[items]]
1202 [[items]]
1202 section = "format"
1203 section = "format"
1203 name = "exp-use-changelog-v2"
1204 name = "exp-use-changelog-v2"
1204 experimental = true
1205 experimental = true
1205
1206
1206 [[items]]
1207 [[items]]
1207 section = "format"
1208 section = "format"
1208 name = "exp-use-copies-side-data-changeset"
1209 name = "exp-use-copies-side-data-changeset"
1209 default = false
1210 default = false
1210 experimental = true
1211 experimental = true
1211
1212
1212 [[items]]
1213 [[items]]
1213 section = "format"
1214 section = "format"
1214 name = "generaldelta"
1215 name = "generaldelta"
1215 default = false
1216 default = false
1216 experimental = true
1217 experimental = true
1217
1218
1218 [[items]]
1219 [[items]]
1219 section = "format"
1220 section = "format"
1220 name = "manifestcachesize"
1221 name = "manifestcachesize"
1221 experimental = true
1222 experimental = true
1222
1223
1223 [[items]]
1224 [[items]]
1224 section = "format"
1225 section = "format"
1225 name = "maxchainlen"
1226 name = "maxchainlen"
1226 default-type = "dynamic"
1227 default-type = "dynamic"
1227 experimental = true
1228 experimental = true
1228
1229
1229 [[items]]
1230 [[items]]
1230 section = "format"
1231 section = "format"
1231 name = "obsstore-version"
1232 name = "obsstore-version"
1232
1233
1233 [[items]]
1234 [[items]]
1234 section = "format"
1235 section = "format"
1235 name = "revlog-compression"
1236 name = "revlog-compression"
1236 default-type = "lambda"
1237 default-type = "lambda"
1237 alias = [["experimental", "format.compression"]]
1238 alias = [["experimental", "format.compression"]]
1238 default = [ "zstd", "zlib",]
1239 default = [ "zstd", "zlib",]
1239
1240
1240 [[items]]
1241 [[items]]
1241 section = "format"
1242 section = "format"
1242 name = "sparse-revlog"
1243 name = "sparse-revlog"
1243 default = true
1244 default = true
1244
1245
1245 [[items]]
1246 [[items]]
1246 section = "format"
1247 section = "format"
1247 name = "use-dirstate-tracked-hint"
1248 name = "use-dirstate-tracked-hint"
1248 default = false
1249 default = false
1249 experimental = true
1250 experimental = true
1250
1251
1251 [[items]]
1252 [[items]]
1252 section = "format"
1253 section = "format"
1253 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"
1254 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"
1254 default = false
1255 default = false
1255 experimental = true
1256 experimental = true
1256
1257
1257 [[items]]
1258 [[items]]
1258 section = "format"
1259 section = "format"
1259 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories:quiet"
1260 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories:quiet"
1260 default = false
1261 default = false
1261 experimental = true
1262 experimental = true
1262
1263
1263 [[items]]
1264 [[items]]
1264 section = "format"
1265 section = "format"
1265 name = "use-dirstate-tracked-hint.version"
1266 name = "use-dirstate-tracked-hint.version"
1266 default = 1
1267 default = 1
1267 experimental = true
1268 experimental = true
1268
1269
1269 [[items]]
1270 [[items]]
1270 section = "format"
1271 section = "format"
1271 name = "use-dirstate-v2"
1272 name = "use-dirstate-v2"
1272 default = false
1273 default = false
1273 alias = [["format", "exp-rc-dirstate-v2"]]
1274 alias = [["format", "exp-rc-dirstate-v2"]]
1274 experimental = true
1275 experimental = true
1275 documentation = """Enables dirstate-v2 format *when creating a new repository*.
1276 documentation = """Enables dirstate-v2 format *when creating a new repository*.
1276 Which format to use for existing repos is controlled by `.hg/requires`."""
1277 Which format to use for existing repos is controlled by `.hg/requires`."""
1277
1278
1278 [[items]]
1279 [[items]]
1279 section = "format"
1280 section = "format"
1280 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"
1281 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"
1281 default = false
1282 default = false
1282 experimental = true
1283 experimental = true
1283
1284
1284 [[items]]
1285 [[items]]
1285 section = "format"
1286 section = "format"
1286 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories:quiet"
1287 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories:quiet"
1287 default = false
1288 default = false
1288 experimental = true
1289 experimental = true
1289
1290
1290 # Having this on by default means we are confident about the scaling of phases.
1291 # Having this on by default means we are confident about the scaling of phases.
1291 # This is not garanteed to be the case at the time this message is written.
1292 # This is not garanteed to be the case at the time this message is written.
1292 [[items]]
1293 [[items]]
1293 section = "format"
1294 section = "format"
1294 name = "use-internal-phase"
1295 name = "use-internal-phase"
1295 default = false
1296 default = false
1296 experimental = true
1297 experimental = true
1297
1298
1298 [[items]]
1299 [[items]]
1299 section = "format"
1300 section = "format"
1300 name = "use-persistent-nodemap"
1301 name = "use-persistent-nodemap"
1301 default-type = "dynamic"
1302 default-type = "dynamic"
1302
1303
1303 [[items]]
1304 [[items]]
1304 section = "format"
1305 section = "format"
1305 name = "use-share-safe"
1306 name = "use-share-safe"
1306 default = true
1307 default = true
1307
1308
1308 [[items]]
1309 [[items]]
1309 section = "format"
1310 section = "format"
1310 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories"
1311 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories"
1311 default = false
1312 default = false
1312 experimental = true
1313 experimental = true
1313
1314
1314 [[items]]
1315 [[items]]
1315 section = "format"
1316 section = "format"
1316 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet"
1317 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet"
1317 default = false
1318 default = false
1318 experimental = true
1319 experimental = true
1319
1320
1320 [[items]]
1321 [[items]]
1321 section = "format"
1322 section = "format"
1322 name = "usefncache"
1323 name = "usefncache"
1323 default = true
1324 default = true
1324
1325
1325 [[items]]
1326 [[items]]
1326 section = "format"
1327 section = "format"
1327 name = "usegeneraldelta"
1328 name = "usegeneraldelta"
1328 default = true
1329 default = true
1329
1330
1330 [[items]]
1331 [[items]]
1331 section = "format"
1332 section = "format"
1332 name = "usestore"
1333 name = "usestore"
1333 default = true
1334 default = true
1334
1335
1335 [[items]]
1336 [[items]]
1336 section = "fsmonitor"
1337 section = "fsmonitor"
1337 name = "warn_update_file_count"
1338 name = "warn_update_file_count"
1338 default = 50000
1339 default = 50000
1339
1340
1340 [[items]]
1341 [[items]]
1341 section = "fsmonitor"
1342 section = "fsmonitor"
1342 name = "warn_update_file_count_rust"
1343 name = "warn_update_file_count_rust"
1343 default = 400000
1344 default = 400000
1344
1345
1345 [[items]]
1346 [[items]]
1346 section = "fsmonitor"
1347 section = "fsmonitor"
1347 name = "warn_when_unused"
1348 name = "warn_when_unused"
1348 default = true
1349 default = true
1349
1350
1350 [[items]]
1351 [[items]]
1351 section = "help"
1352 section = "help"
1352 name = 'hidden-command\..*'
1353 name = 'hidden-command\..*'
1353 default = false
1354 default = false
1354 generic = true
1355 generic = true
1355
1356
1356 [[items]]
1357 [[items]]
1357 section = "help"
1358 section = "help"
1358 name = 'hidden-topic\..*'
1359 name = 'hidden-topic\..*'
1359 default = false
1360 default = false
1360 generic = true
1361 generic = true
1361
1362
1362 [[items]]
1363 [[items]]
1363 section = "hgweb-paths"
1364 section = "hgweb-paths"
1364 name = ".*"
1365 name = ".*"
1365 default-type = "list_type"
1366 default-type = "list_type"
1366 generic = true
1367 generic = true
1367
1368
1368 [[items]]
1369 [[items]]
1369 section = "hooks"
1370 section = "hooks"
1370 name = ".*:run-with-plain"
1371 name = ".*:run-with-plain"
1371 default = true
1372 default = true
1372 generic = true
1373 generic = true
1373
1374
1374 [[items]]
1375 [[items]]
1375 section = "hooks"
1376 section = "hooks"
1376 name = "[^:]*"
1377 name = "[^:]*"
1377 default-type = "dynamic"
1378 default-type = "dynamic"
1378 generic = true
1379 generic = true
1379
1380
1380 [[items]]
1381 [[items]]
1381 section = "hostfingerprints"
1382 section = "hostfingerprints"
1382 name = ".*"
1383 name = ".*"
1383 default-type = "list_type"
1384 default-type = "list_type"
1384 generic = true
1385 generic = true
1385
1386
1386 [[items]]
1387 [[items]]
1387 section = "hostsecurity"
1388 section = "hostsecurity"
1388 name = ".*:ciphers$"
1389 name = ".*:ciphers$"
1389 default-type = "dynamic"
1390 default-type = "dynamic"
1390 generic = true
1391 generic = true
1391
1392
1392 [[items]]
1393 [[items]]
1393 section = "hostsecurity"
1394 section = "hostsecurity"
1394 name = ".*:fingerprints$"
1395 name = ".*:fingerprints$"
1395 default-type = "list_type"
1396 default-type = "list_type"
1396 generic = true
1397 generic = true
1397
1398
1398 [[items]]
1399 [[items]]
1399 section = "hostsecurity"
1400 section = "hostsecurity"
1400 name = ".*:minimumprotocol$"
1401 name = ".*:minimumprotocol$"
1401 default-type = "dynamic"
1402 default-type = "dynamic"
1402 generic = true
1403 generic = true
1403
1404
1404 [[items]]
1405 [[items]]
1405 section = "hostsecurity"
1406 section = "hostsecurity"
1406 name = ".*:verifycertsfile$"
1407 name = ".*:verifycertsfile$"
1407 generic = true
1408 generic = true
1408
1409
1409 [[items]]
1410 [[items]]
1410 section = "hostsecurity"
1411 section = "hostsecurity"
1411 name = "ciphers"
1412 name = "ciphers"
1412
1413
1413 [[items]]
1414 [[items]]
1414 section = "hostsecurity"
1415 section = "hostsecurity"
1415 name = "minimumprotocol"
1416 name = "minimumprotocol"
1416 default-type = "dynamic"
1417 default-type = "dynamic"
1417
1418
1418 [[items]]
1419 [[items]]
1419 section = "http"
1420 section = "http"
1420 name = "timeout"
1421 name = "timeout"
1421
1422
1422 [[items]]
1423 [[items]]
1423 section = "http_proxy"
1424 section = "http_proxy"
1424 name = "always"
1425 name = "always"
1425 default = false
1426 default = false
1426
1427
1427 [[items]]
1428 [[items]]
1428 section = "http_proxy"
1429 section = "http_proxy"
1429 name = "host"
1430 name = "host"
1430
1431
1431 [[items]]
1432 [[items]]
1432 section = "http_proxy"
1433 section = "http_proxy"
1433 name = "no"
1434 name = "no"
1434 default-type = "list_type"
1435 default-type = "list_type"
1435
1436
1436 [[items]]
1437 [[items]]
1437 section = "http_proxy"
1438 section = "http_proxy"
1438 name = "passwd"
1439 name = "passwd"
1439
1440
1440 [[items]]
1441 [[items]]
1441 section = "http_proxy"
1442 section = "http_proxy"
1442 name = "user"
1443 name = "user"
1443
1444
1444 [[items]]
1445 [[items]]
1445 section = "logtoprocess"
1446 section = "logtoprocess"
1446 name = "command"
1447 name = "command"
1447
1448
1448 [[items]]
1449 [[items]]
1449 section = "logtoprocess"
1450 section = "logtoprocess"
1450 name = "commandexception"
1451 name = "commandexception"
1451
1452
1452 [[items]]
1453 [[items]]
1453 section = "logtoprocess"
1454 section = "logtoprocess"
1454 name = "commandfinish"
1455 name = "commandfinish"
1455
1456
1456 [[items]]
1457 [[items]]
1457 section = "logtoprocess"
1458 section = "logtoprocess"
1458 name = "develwarn"
1459 name = "develwarn"
1459
1460
1460 [[items]]
1461 [[items]]
1461 section = "logtoprocess"
1462 section = "logtoprocess"
1462 name = "uiblocked"
1463 name = "uiblocked"
1463
1464
1464 [[items]]
1465 [[items]]
1465 section = "merge"
1466 section = "merge"
1466 name = "checkignored"
1467 name = "checkignored"
1467 default = "abort"
1468 default = "abort"
1468
1469
1469 [[items]]
1470 [[items]]
1470 section = "merge"
1471 section = "merge"
1471 name = "checkunknown"
1472 name = "checkunknown"
1472 default = "abort"
1473 default = "abort"
1473
1474
1474 [[items]]
1475 [[items]]
1475 section = "merge"
1476 section = "merge"
1476 name = "disable-partial-tools"
1477 name = "disable-partial-tools"
1477 default = false
1478 default = false
1478 experimental = true
1479 experimental = true
1479
1480
1480 [[items]]
1481 [[items]]
1481 section = "merge"
1482 section = "merge"
1482 name = "followcopies"
1483 name = "followcopies"
1483 default = true
1484 default = true
1484
1485
1485 [[items]]
1486 [[items]]
1486 section = "merge"
1487 section = "merge"
1487 name = "on-failure"
1488 name = "on-failure"
1488 default = "continue"
1489 default = "continue"
1489
1490
1490 [[items]]
1491 [[items]]
1491 section = "merge"
1492 section = "merge"
1492 name = "preferancestor"
1493 name = "preferancestor"
1493 default-type = "lambda"
1494 default-type = "lambda"
1494 default = ["*"]
1495 default = ["*"]
1495 experimental = true
1496 experimental = true
1496
1497
1497 [[items]]
1498 [[items]]
1498 section = "merge"
1499 section = "merge"
1499 name = "strict-capability-check"
1500 name = "strict-capability-check"
1500 default = false
1501 default = false
1501
1502
1502 [[items]]
1503 [[items]]
1503 section = "merge-tools"
1504 section = "merge-tools"
1504 name = ".*"
1505 name = ".*"
1505 generic = true
1506 generic = true
1506
1507
1507 [[items]]
1508 [[items]]
1508 section = "merge-tools"
1509 section = "merge-tools"
1509 name = '.*\.args$'
1510 name = '.*\.args$'
1510 default = "$local $base $other"
1511 default = "$local $base $other"
1511 generic = true
1512 generic = true
1512 priority = -1
1513 priority = -1
1513
1514
1514 [[items]]
1515 [[items]]
1515 section = "merge-tools"
1516 section = "merge-tools"
1516 name = '.*\.binary$'
1517 name = '.*\.binary$'
1517 default = false
1518 default = false
1518 generic = true
1519 generic = true
1519 priority = -1
1520 priority = -1
1520
1521
1521 [[items]]
1522 [[items]]
1522 section = "merge-tools"
1523 section = "merge-tools"
1523 name = '.*\.check$'
1524 name = '.*\.check$'
1524 default-type = "list_type"
1525 default-type = "list_type"
1525 generic = true
1526 generic = true
1526 priority = -1
1527 priority = -1
1527
1528
1528 [[items]]
1529 [[items]]
1529 section = "merge-tools"
1530 section = "merge-tools"
1530 name = '.*\.checkchanged$'
1531 name = '.*\.checkchanged$'
1531 default = false
1532 default = false
1532 generic = true
1533 generic = true
1533 priority = -1
1534 priority = -1
1534
1535
1535 [[items]]
1536 [[items]]
1536 section = "merge-tools"
1537 section = "merge-tools"
1537 name = '.*\.executable$'
1538 name = '.*\.executable$'
1538 default-type = "dynamic"
1539 default-type = "dynamic"
1539 generic = true
1540 generic = true
1540 priority = -1
1541 priority = -1
1541
1542
1542 [[items]]
1543 [[items]]
1543 section = "merge-tools"
1544 section = "merge-tools"
1544 name = '.*\.fixeol$'
1545 name = '.*\.fixeol$'
1545 default = false
1546 default = false
1546 generic = true
1547 generic = true
1547 priority = -1
1548 priority = -1
1548
1549
1549 [[items]]
1550 [[items]]
1550 section = "merge-tools"
1551 section = "merge-tools"
1551 name = '.*\.gui$'
1552 name = '.*\.gui$'
1552 default = false
1553 default = false
1553 generic = true
1554 generic = true
1554 priority = -1
1555 priority = -1
1555
1556
1556 [[items]]
1557 [[items]]
1557 section = "merge-tools"
1558 section = "merge-tools"
1558 name = '.*\.mergemarkers$'
1559 name = '.*\.mergemarkers$'
1559 default = "basic"
1560 default = "basic"
1560 generic = true
1561 generic = true
1561 priority = -1
1562 priority = -1
1562
1563
1563 [[items]]
1564 [[items]]
1564 section = "merge-tools"
1565 section = "merge-tools"
1565 name = '.*\.mergemarkertemplate$' # take from command-templates.mergemarker
1566 name = '.*\.mergemarkertemplate$' # take from command-templates.mergemarker
1566 default-type = "dynamic"
1567 default-type = "dynamic"
1567 generic = true
1568 generic = true
1568 priority = -1
1569 priority = -1
1569
1570
1570 [[items]]
1571 [[items]]
1571 section = "merge-tools"
1572 section = "merge-tools"
1572 name = '.*\.premerge$'
1573 name = '.*\.premerge$'
1573 default-type = "dynamic"
1574 default-type = "dynamic"
1574 generic = true
1575 generic = true
1575 priority = -1
1576 priority = -1
1576
1577
1577 [[items]]
1578 [[items]]
1578 section = "merge-tools"
1579 section = "merge-tools"
1579 name = '.*\.priority$'
1580 name = '.*\.priority$'
1580 default = 0
1581 default = 0
1581 generic = true
1582 generic = true
1582 priority = -1
1583 priority = -1
1583
1584
1584 [[items]]
1585 [[items]]
1585 section = "merge-tools"
1586 section = "merge-tools"
1586 name = '.*\.regappend$'
1587 name = '.*\.regappend$'
1587 default = ""
1588 default = ""
1588 generic = true
1589 generic = true
1589 priority = -1
1590 priority = -1
1590
1591
1591 [[items]]
1592 [[items]]
1592 section = "merge-tools"
1593 section = "merge-tools"
1593 name = '.*\.symlink$'
1594 name = '.*\.symlink$'
1594 default = false
1595 default = false
1595 generic = true
1596 generic = true
1596 priority = -1
1597 priority = -1
1597
1598
1598 [[items]]
1599 [[items]]
1599 section = "pager"
1600 section = "pager"
1600 name = "attend-.*"
1601 name = "attend-.*"
1601 default-type = "dynamic"
1602 default-type = "dynamic"
1602 generic = true
1603 generic = true
1603
1604
1604 [[items]]
1605 [[items]]
1605 section = "pager"
1606 section = "pager"
1606 name = "ignore"
1607 name = "ignore"
1607 default-type = "list_type"
1608 default-type = "list_type"
1608
1609
1609 [[items]]
1610 [[items]]
1610 section = "pager"
1611 section = "pager"
1611 name = "pager"
1612 name = "pager"
1612 default-type = "dynamic"
1613 default-type = "dynamic"
1613
1614
1614 [[items]]
1615 [[items]]
1615 section = "partial-merge-tools"
1616 section = "partial-merge-tools"
1616 name = ".*"
1617 name = ".*"
1617 generic = true
1618 generic = true
1618 experimental = true
1619 experimental = true
1619
1620
1620 [[items]]
1621 [[items]]
1621 section = "partial-merge-tools"
1622 section = "partial-merge-tools"
1622 name = '.*\.args'
1623 name = '.*\.args'
1623 default = "$local $base $other"
1624 default = "$local $base $other"
1624 generic = true
1625 generic = true
1625 priority = -1
1626 priority = -1
1626 experimental = true
1627 experimental = true
1627
1628
1628 [[items]]
1629 [[items]]
1629 section = "partial-merge-tools"
1630 section = "partial-merge-tools"
1630 name = '.*\.disable'
1631 name = '.*\.disable'
1631 default = false
1632 default = false
1632 generic = true
1633 generic = true
1633 priority = -1
1634 priority = -1
1634 experimental = true
1635 experimental = true
1635
1636
1636 [[items]]
1637 [[items]]
1637 section = "partial-merge-tools"
1638 section = "partial-merge-tools"
1638 name = '.*\.executable$'
1639 name = '.*\.executable$'
1639 default-type = "dynamic"
1640 default-type = "dynamic"
1640 generic = true
1641 generic = true
1641 priority = -1
1642 priority = -1
1642 experimental = true
1643 experimental = true
1643
1644
1644 [[items]]
1645 [[items]]
1645 section = "partial-merge-tools"
1646 section = "partial-merge-tools"
1646 name = '.*\.order'
1647 name = '.*\.order'
1647 default = 0
1648 default = 0
1648 generic = true
1649 generic = true
1649 priority = -1
1650 priority = -1
1650 experimental = true
1651 experimental = true
1651
1652
1652 [[items]]
1653 [[items]]
1653 section = "partial-merge-tools"
1654 section = "partial-merge-tools"
1654 name = '.*\.patterns'
1655 name = '.*\.patterns'
1655 default-type = "dynamic"
1656 default-type = "dynamic"
1656 generic = true
1657 generic = true
1657 priority = -1
1658 priority = -1
1658 experimental = true
1659 experimental = true
1659
1660
1660 [[items]]
1661 [[items]]
1661 section = "patch"
1662 section = "patch"
1662 name = "eol"
1663 name = "eol"
1663 default = "strict"
1664 default = "strict"
1664
1665
1665 [[items]]
1666 [[items]]
1666 section = "patch"
1667 section = "patch"
1667 name = "fuzz"
1668 name = "fuzz"
1668 default = 2
1669 default = 2
1669
1670
1670 [[items]]
1671 [[items]]
1671 section = "paths"
1672 section = "paths"
1672 name = "[^:]*"
1673 name = "[^:]*"
1673 generic = true
1674 generic = true
1674
1675
1675 [[items]]
1676 [[items]]
1676 section = "paths"
1677 section = "paths"
1677 name = ".*:bookmarks.mode"
1678 name = ".*:bookmarks.mode"
1678 default = "default"
1679 default = "default"
1679 generic = true
1680 generic = true
1680
1681
1681 [[items]]
1682 [[items]]
1682 section = "paths"
1683 section = "paths"
1683 name = ".*:multi-urls"
1684 name = ".*:multi-urls"
1684 default = false
1685 default = false
1685 generic = true
1686 generic = true
1686
1687
1687 [[items]]
1688 [[items]]
1688 section = "paths"
1689 section = "paths"
1689 name = ".*:pulled-delta-reuse-policy"
1690 name = ".*:pulled-delta-reuse-policy"
1690 generic = true
1691 generic = true
1691
1692
1692 [[items]]
1693 [[items]]
1693 section = "paths"
1694 section = "paths"
1694 name = ".*:pushrev"
1695 name = ".*:pushrev"
1695 generic = true
1696 generic = true
1696
1697
1697 [[items]]
1698 [[items]]
1698 section = "paths"
1699 section = "paths"
1699 name = ".*:pushurl"
1700 name = ".*:pushurl"
1700 generic = true
1701 generic = true
1701
1702
1702 [[items]]
1703 [[items]]
1703 section = "paths"
1704 section = "paths"
1704 name = "default"
1705 name = "default"
1705
1706
1706 [[items]]
1707 [[items]]
1707 section = "paths"
1708 section = "paths"
1708 name = "default-push"
1709 name = "default-push"
1709
1710
1710 [[items]]
1711 [[items]]
1711 section = "phases"
1712 section = "phases"
1712 name = "checksubrepos"
1713 name = "checksubrepos"
1713 default = "follow"
1714 default = "follow"
1714
1715
1715 [[items]]
1716 [[items]]
1716 section = "phases"
1717 section = "phases"
1717 name = "new-commit"
1718 name = "new-commit"
1718 default = "draft"
1719 default = "draft"
1719
1720
1720 [[items]]
1721 [[items]]
1721 section = "phases"
1722 section = "phases"
1722 name = "publish"
1723 name = "publish"
1723 default = true
1724 default = true
1724
1725
1725 [[items]]
1726 [[items]]
1726 section = "profiling"
1727 section = "profiling"
1727 name = "enabled"
1728 name = "enabled"
1728 default = false
1729 default = false
1729
1730
1730 [[items]]
1731 [[items]]
1731 section = "profiling"
1732 section = "profiling"
1732 name = "format"
1733 name = "format"
1733 default = "text"
1734 default = "text"
1734
1735
1735 [[items]]
1736 [[items]]
1736 section = "profiling"
1737 section = "profiling"
1737 name = "freq"
1738 name = "freq"
1738 default = 1000
1739 default = 1000
1739
1740
1740 [[items]]
1741 [[items]]
1741 section = "profiling"
1742 section = "profiling"
1742 name = "limit"
1743 name = "limit"
1743 default = 30
1744 default = 30
1744
1745
1745 [[items]]
1746 [[items]]
1746 section = "profiling"
1747 section = "profiling"
1747 name = "nested"
1748 name = "nested"
1748 default = 0
1749 default = 0
1749
1750
1750 [[items]]
1751 [[items]]
1751 section = "profiling"
1752 section = "profiling"
1752 name = "output"
1753 name = "output"
1753
1754
1754 [[items]]
1755 [[items]]
1755 section = "profiling"
1756 section = "profiling"
1756 name = "showmax"
1757 name = "showmax"
1757 default = 0.999
1758 default = 0.999
1758
1759
1759 [[items]]
1760 [[items]]
1760 section = "profiling"
1761 section = "profiling"
1761 name = "showmin"
1762 name = "showmin"
1762 default-type = "dynamic"
1763 default-type = "dynamic"
1763
1764
1764 [[items]]
1765 [[items]]
1765 section = "profiling"
1766 section = "profiling"
1766 name = "showtime"
1767 name = "showtime"
1767 default = true
1768 default = true
1768
1769
1769 [[items]]
1770 [[items]]
1770 section = "profiling"
1771 section = "profiling"
1771 name = "sort"
1772 name = "sort"
1772 default = "inlinetime"
1773 default = "inlinetime"
1773
1774
1774 [[items]]
1775 [[items]]
1775 section = "profiling"
1776 section = "profiling"
1776 name = "statformat"
1777 name = "statformat"
1777 default = "hotpath"
1778 default = "hotpath"
1778
1779
1779 [[items]]
1780 [[items]]
1780 section = "profiling"
1781 section = "profiling"
1781 name = "time-track"
1782 name = "time-track"
1782 default-type = "dynamic"
1783 default-type = "dynamic"
1783
1784
1784 [[items]]
1785 [[items]]
1785 section = "profiling"
1786 section = "profiling"
1786 name = "type"
1787 name = "type"
1787 default = "stat"
1788 default = "stat"
1788
1789
1789 [[items]]
1790 [[items]]
1790 section = "progress"
1791 section = "progress"
1791 name = "assume-tty"
1792 name = "assume-tty"
1792 default = false
1793 default = false
1793
1794
1794 [[items]]
1795 [[items]]
1795 section = "progress"
1796 section = "progress"
1796 name = "changedelay"
1797 name = "changedelay"
1797 default = 1
1798 default = 1
1798
1799
1799 [[items]]
1800 [[items]]
1800 section = "progress"
1801 section = "progress"
1801 name = "clear-complete"
1802 name = "clear-complete"
1802 default = true
1803 default = true
1803
1804
1804 [[items]]
1805 [[items]]
1805 section = "progress"
1806 section = "progress"
1806 name = "debug"
1807 name = "debug"
1807 default = false
1808 default = false
1808
1809
1809 [[items]]
1810 [[items]]
1810 section = "progress"
1811 section = "progress"
1811 name = "delay"
1812 name = "delay"
1812 default = 3
1813 default = 3
1813
1814
1814 [[items]]
1815 [[items]]
1815 section = "progress"
1816 section = "progress"
1816 name = "disable"
1817 name = "disable"
1817 default = false
1818 default = false
1818
1819
1819 [[items]]
1820 [[items]]
1820 section = "progress"
1821 section = "progress"
1821 name = "estimateinterval"
1822 name = "estimateinterval"
1822 default = 60.0
1823 default = 60.0
1823
1824
1824 [[items]]
1825 [[items]]
1825 section = "progress"
1826 section = "progress"
1826 name = "format"
1827 name = "format"
1827 default-type = "lambda"
1828 default-type = "lambda"
1828 default = [ "topic", "bar", "number", "estimate",]
1829 default = [ "topic", "bar", "number", "estimate",]
1829
1830
1830 [[items]]
1831 [[items]]
1831 section = "progress"
1832 section = "progress"
1832 name = "refresh"
1833 name = "refresh"
1833 default = 0.1
1834 default = 0.1
1834
1835
1835 [[items]]
1836 [[items]]
1836 section = "progress"
1837 section = "progress"
1837 name = "width"
1838 name = "width"
1838 default-type = "dynamic"
1839 default-type = "dynamic"
1839
1840
1840 [[items]]
1841 [[items]]
1841 section = "pull"
1842 section = "pull"
1842 name = "confirm"
1843 name = "confirm"
1843 default = false
1844 default = false
1844
1845
1845 [[items]]
1846 [[items]]
1846 section = "push"
1847 section = "push"
1847 name = "pushvars.server"
1848 name = "pushvars.server"
1848 default = false
1849 default = false
1849
1850
1850 [[items]]
1851 [[items]]
1851 section = "rebase"
1852 section = "rebase"
1852 name = "experimental.inmemory"
1853 name = "experimental.inmemory"
1853 default = false
1854 default = false
1854
1855
1855 [[items]]
1856 [[items]]
1856 section = "rebase"
1857 section = "rebase"
1857 name = "singletransaction"
1858 name = "singletransaction"
1858 default = false
1859 default = false
1859
1860
1860 [[items]]
1861 [[items]]
1861 section = "rebase"
1862 section = "rebase"
1862 name = "store-source"
1863 name = "store-source"
1863 default = true
1864 default = true
1864 experimental = true
1865 experimental = true
1865 documentation = """Controls creation of a `rebase_source` extra field during rebase.
1866 documentation = """Controls creation of a `rebase_source` extra field during rebase.
1866 When false, no such field is created. This is useful e.g. for incrementally \
1867 When false, no such field is created. This is useful e.g. for incrementally \
1867 converting changesets and then rebasing them onto an existing repo.
1868 converting changesets and then rebasing them onto an existing repo.
1868 WARNING: this is an advanced setting reserved for people who know \
1869 WARNING: this is an advanced setting reserved for people who know \
1869 exactly what they are doing. Misuse of this setting can easily \
1870 exactly what they are doing. Misuse of this setting can easily \
1870 result in obsmarker cycles and a vivid headache."""
1871 result in obsmarker cycles and a vivid headache."""
1871
1872
1872 [[items]]
1873 [[items]]
1873 section = "rewrite"
1874 section = "rewrite"
1874 name = "backup-bundle"
1875 name = "backup-bundle"
1875 default = true
1876 default = true
1876 alias = [["ui", "history-editing-backup"]]
1877 alias = [["ui", "history-editing-backup"]]
1877
1878
1878 [[items]]
1879 [[items]]
1879 section = "rewrite"
1880 section = "rewrite"
1880 name = "empty-successor"
1881 name = "empty-successor"
1881 default = "skip"
1882 default = "skip"
1882 experimental = true
1883 experimental = true
1883
1884
1884 [[items]]
1885 [[items]]
1885 section = "rewrite"
1886 section = "rewrite"
1886 name = "update-timestamp"
1887 name = "update-timestamp"
1887 default = false
1888 default = false
1888
1889
1889 [[items]]
1890 [[items]]
1890 section = "server"
1891 section = "server"
1891 name = "bookmarks-pushkey-compat"
1892 name = "bookmarks-pushkey-compat"
1892 default = true
1893 default = true
1893
1894
1894 [[items]]
1895 [[items]]
1895 section = "server"
1896 section = "server"
1896 name = "bundle1"
1897 name = "bundle1"
1897 default = true
1898 default = true
1898
1899
1899 [[items]]
1900 [[items]]
1900 section = "server"
1901 section = "server"
1901 name = "bundle1.pull"
1902 name = "bundle1.pull"
1902
1903
1903 [[items]]
1904 [[items]]
1904 section = "server"
1905 section = "server"
1905 name = "bundle1.push"
1906 name = "bundle1.push"
1906
1907
1907 [[items]]
1908 [[items]]
1908 section = "server"
1909 section = "server"
1909 name = "bundle1gd"
1910 name = "bundle1gd"
1910
1911
1911 [[items]]
1912 [[items]]
1912 section = "server"
1913 section = "server"
1913 name = "bundle1gd.pull"
1914 name = "bundle1gd.pull"
1914
1915
1915 [[items]]
1916 [[items]]
1916 section = "server"
1917 section = "server"
1917 name = "bundle1gd.push"
1918 name = "bundle1gd.push"
1918
1919
1919 [[items]]
1920 [[items]]
1920 section = "server"
1921 section = "server"
1921 name = "bundle2.stream"
1922 name = "bundle2.stream"
1922 default = true
1923 default = true
1923 alias = [["experimental", "bundle2.stream"]]
1924 alias = [["experimental", "bundle2.stream"]]
1924
1925
1925 [[items]]
1926 [[items]]
1926 section = "server"
1927 section = "server"
1927 name = "compressionengines"
1928 name = "compressionengines"
1928 default-type = "list_type"
1929 default-type = "list_type"
1929
1930
1930 [[items]]
1931 [[items]]
1931 section = "server"
1932 section = "server"
1932 name = "concurrent-push-mode"
1933 name = "concurrent-push-mode"
1933 default = "check-related"
1934 default = "check-related"
1934
1935
1935 [[items]]
1936 [[items]]
1936 section = "server"
1937 section = "server"
1937 name = "disablefullbundle"
1938 name = "disablefullbundle"
1938 default = false
1939 default = false
1939
1940
1940 [[items]]
1941 [[items]]
1941 section = "server"
1942 section = "server"
1942 name = "maxhttpheaderlen"
1943 name = "maxhttpheaderlen"
1943 default = 1024
1944 default = 1024
1944
1945
1945 [[items]]
1946 [[items]]
1946 section = "server"
1947 section = "server"
1947 name = "preferuncompressed"
1948 name = "preferuncompressed"
1948 default = false
1949 default = false
1949
1950
1950 [[items]]
1951 [[items]]
1951 section = "server"
1952 section = "server"
1952 name = "pullbundle"
1953 name = "pullbundle"
1953 default = true
1954 default = true
1954
1955
1955 [[items]]
1956 [[items]]
1956 section = "server"
1957 section = "server"
1957 name = "streamunbundle"
1958 name = "streamunbundle"
1958 default = false
1959 default = false
1959
1960
1960 [[items]]
1961 [[items]]
1961 section = "server"
1962 section = "server"
1962 name = "uncompressed"
1963 name = "uncompressed"
1963 default = true
1964 default = true
1964
1965
1965 [[items]]
1966 [[items]]
1966 section = "server"
1967 section = "server"
1967 name = "uncompressedallowsecret"
1968 name = "uncompressedallowsecret"
1968 default = false
1969 default = false
1969
1970
1970 [[items]]
1971 [[items]]
1971 section = "server"
1972 section = "server"
1972 name = "validate"
1973 name = "validate"
1973 default = false
1974 default = false
1974
1975
1975 [[items]]
1976 [[items]]
1976 section = "server"
1977 section = "server"
1977 name = "view"
1978 name = "view"
1978 default = "served"
1979 default = "served"
1979
1980
1980 [[items]]
1981 [[items]]
1981 section = "server"
1982 section = "server"
1982 name = "zliblevel"
1983 name = "zliblevel"
1983 default = -1
1984 default = -1
1984
1985
1985 [[items]]
1986 [[items]]
1986 section = "server"
1987 section = "server"
1987 name = "zstdlevel"
1988 name = "zstdlevel"
1988 default = 3
1989 default = 3
1989
1990
1990 [[items]]
1991 [[items]]
1991 section = "share"
1992 section = "share"
1992 name = "pool"
1993 name = "pool"
1993
1994
1994 [[items]]
1995 [[items]]
1995 section = "share"
1996 section = "share"
1996 name = "poolnaming"
1997 name = "poolnaming"
1997 default = "identity"
1998 default = "identity"
1998
1999
1999 [[items]]
2000 [[items]]
2000 section = "share"
2001 section = "share"
2001 name = "safe-mismatch.source-not-safe"
2002 name = "safe-mismatch.source-not-safe"
2002 default = "abort"
2003 default = "abort"
2003
2004
2004 [[items]]
2005 [[items]]
2005 section = "share"
2006 section = "share"
2006 name = "safe-mismatch.source-not-safe.warn"
2007 name = "safe-mismatch.source-not-safe.warn"
2007 default = true
2008 default = true
2008
2009
2009 [[items]]
2010 [[items]]
2010 section = "share"
2011 section = "share"
2011 name = "safe-mismatch.source-not-safe:verbose-upgrade"
2012 name = "safe-mismatch.source-not-safe:verbose-upgrade"
2012 default = true
2013 default = true
2013
2014
2014 [[items]]
2015 [[items]]
2015 section = "share"
2016 section = "share"
2016 name = "safe-mismatch.source-safe"
2017 name = "safe-mismatch.source-safe"
2017 default = "abort"
2018 default = "abort"
2018
2019
2019 [[items]]
2020 [[items]]
2020 section = "share"
2021 section = "share"
2021 name = "safe-mismatch.source-safe.warn"
2022 name = "safe-mismatch.source-safe.warn"
2022 default = true
2023 default = true
2023
2024
2024 [[items]]
2025 [[items]]
2025 section = "share"
2026 section = "share"
2026 name = "safe-mismatch.source-safe:verbose-upgrade"
2027 name = "safe-mismatch.source-safe:verbose-upgrade"
2027 default = true
2028 default = true
2028
2029
2029 [[items]]
2030 [[items]]
2030 section = "shelve"
2031 section = "shelve"
2031 name = "maxbackups"
2032 name = "maxbackups"
2032 default = 10
2033 default = 10
2033
2034
2034 [[items]]
2035 [[items]]
2035 section = "shelve"
2036 section = "shelve"
2036 name = "store"
2037 name = "store"
2037 default = "internal"
2038 default = "internal"
2038 experimental = true
2039 experimental = true
2039
2040
2040 [[items]]
2041 [[items]]
2041 section = "smtp"
2042 section = "smtp"
2042 name = "host"
2043 name = "host"
2043
2044
2044 [[items]]
2045 [[items]]
2045 section = "smtp"
2046 section = "smtp"
2046 name = "local_hostname"
2047 name = "local_hostname"
2047
2048
2048 [[items]]
2049 [[items]]
2049 section = "smtp"
2050 section = "smtp"
2050 name = "password"
2051 name = "password"
2051
2052
2052 [[items]]
2053 [[items]]
2053 section = "smtp"
2054 section = "smtp"
2054 name = "port"
2055 name = "port"
2055 default-type = "dynamic"
2056 default-type = "dynamic"
2056
2057
2057 [[items]]
2058 [[items]]
2058 section = "smtp"
2059 section = "smtp"
2059 name = "tls"
2060 name = "tls"
2060 default = "none"
2061 default = "none"
2061
2062
2062 [[items]]
2063 [[items]]
2063 section = "smtp"
2064 section = "smtp"
2064 name = "username"
2065 name = "username"
2065
2066
2066 [[items]]
2067 [[items]]
2067 section = "sparse"
2068 section = "sparse"
2068 name = "missingwarning"
2069 name = "missingwarning"
2069 default = true
2070 default = true
2070 experimental = true
2071 experimental = true
2071
2072
2072 [[items]]
2073 [[items]]
2073 section = "storage"
2074 section = "storage"
2074 name = "dirstate-v2.slow-path"
2075 name = "dirstate-v2.slow-path"
2075 default = "abort"
2076 default = "abort"
2076 experimental = true # experimental as long as format.use-dirstate-v2 is.
2077 experimental = true # experimental as long as format.use-dirstate-v2 is.
2077
2078
2078 [[items]]
2079 [[items]]
2079 section = "storage"
2080 section = "storage"
2080 name = "new-repo-backend"
2081 name = "new-repo-backend"
2081 default = "revlogv1"
2082 default = "revlogv1"
2082 experimental = true
2083 experimental = true
2083
2084
2084 [[items]]
2085 [[items]]
2085 section = "storage"
2086 section = "storage"
2086 name = "revlog.delta-parent-search.candidate-group-chunk-size"
2087 name = "revlog.delta-parent-search.candidate-group-chunk-size"
2087 default = 20
2088 default = 20
2088
2089
2089 [[items]]
2090 [[items]]
2090 section = "storage"
2091 section = "storage"
2091 name = "revlog.issue6528.fix-incoming"
2092 name = "revlog.issue6528.fix-incoming"
2092 default = true
2093 default = true
2093
2094
2094 [[items]]
2095 [[items]]
2095 section = "storage"
2096 section = "storage"
2096 name = "revlog.optimize-delta-parent-choice"
2097 name = "revlog.optimize-delta-parent-choice"
2097 default = true
2098 default = true
2098 alias = [["format", "aggressivemergedeltas"]]
2099 alias = [["format", "aggressivemergedeltas"]]
2099
2100
2100 [[items]]
2101 [[items]]
2101 section = "storage"
2102 section = "storage"
2102 name = "revlog.persistent-nodemap.mmap"
2103 name = "revlog.persistent-nodemap.mmap"
2103 default = true
2104 default = true
2104
2105
2105 [[items]]
2106 [[items]]
2106 section = "storage"
2107 section = "storage"
2107 name = "revlog.persistent-nodemap.slow-path"
2108 name = "revlog.persistent-nodemap.slow-path"
2108 default = "abort"
2109 default = "abort"
2109
2110
2110 [[items]]
2111 [[items]]
2111 section = "storage"
2112 section = "storage"
2112 name = "revlog.reuse-external-delta"
2113 name = "revlog.reuse-external-delta"
2113 default = true
2114 default = true
2114
2115
2115 [[items]]
2116 [[items]]
2116 section = "storage"
2117 section = "storage"
2117 name = "revlog.reuse-external-delta-parent"
2118 name = "revlog.reuse-external-delta-parent"
2118 documentation = """This option is true unless `format.generaldelta` is set."""
2119 documentation = """This option is true unless `format.generaldelta` is set."""
2119
2120
2120 [[items]]
2121 [[items]]
2121 section = "storage"
2122 section = "storage"
2122 name = "revlog.zlib.level"
2123 name = "revlog.zlib.level"
2123
2124
2124 [[items]]
2125 [[items]]
2125 section = "storage"
2126 section = "storage"
2126 name = "revlog.zstd.level"
2127 name = "revlog.zstd.level"
2127
2128
2128 [[items]]
2129 [[items]]
2129 section = "subrepos"
2130 section = "subrepos"
2130 name = "allowed"
2131 name = "allowed"
2131 default-type = "dynamic" # to make backporting simpler
2132 default-type = "dynamic" # to make backporting simpler
2132
2133
2133 [[items]]
2134 [[items]]
2134 section = "subrepos"
2135 section = "subrepos"
2135 name = "git:allowed"
2136 name = "git:allowed"
2136 default-type = "dynamic"
2137 default-type = "dynamic"
2137
2138
2138 [[items]]
2139 [[items]]
2139 section = "subrepos"
2140 section = "subrepos"
2140 name = "hg:allowed"
2141 name = "hg:allowed"
2141 default-type = "dynamic"
2142 default-type = "dynamic"
2142
2143
2143 [[items]]
2144 [[items]]
2144 section = "subrepos"
2145 section = "subrepos"
2145 name = "svn:allowed"
2146 name = "svn:allowed"
2146 default-type = "dynamic"
2147 default-type = "dynamic"
2147
2148
2148 [[items]]
2149 [[items]]
2149 section = "templateconfig"
2150 section = "templateconfig"
2150 name = ".*"
2151 name = ".*"
2151 default-type = "dynamic"
2152 default-type = "dynamic"
2152 generic = true
2153 generic = true
2153
2154
2154 [[items]]
2155 [[items]]
2155 section = "templates"
2156 section = "templates"
2156 name = ".*"
2157 name = ".*"
2157 generic = true
2158 generic = true
2158
2159
2159 [[items]]
2160 [[items]]
2160 section = "trusted"
2161 section = "trusted"
2161 name = "groups"
2162 name = "groups"
2162 default-type = "list_type"
2163 default-type = "list_type"
2163
2164
2164 [[items]]
2165 [[items]]
2165 section = "trusted"
2166 section = "trusted"
2166 name = "users"
2167 name = "users"
2167 default-type = "list_type"
2168 default-type = "list_type"
2168
2169
2169 [[items]]
2170 [[items]]
2170 section = "ui"
2171 section = "ui"
2171 name = "_usedassubrepo"
2172 name = "_usedassubrepo"
2172 default = false
2173 default = false
2173
2174
2174 [[items]]
2175 [[items]]
2175 section = "ui"
2176 section = "ui"
2176 name = "allowemptycommit"
2177 name = "allowemptycommit"
2177 default = false
2178 default = false
2178
2179
2179 [[items]]
2180 [[items]]
2180 section = "ui"
2181 section = "ui"
2181 name = "archivemeta"
2182 name = "archivemeta"
2182 default = true
2183 default = true
2183
2184
2184 [[items]]
2185 [[items]]
2185 section = "ui"
2186 section = "ui"
2186 name = "askusername"
2187 name = "askusername"
2187 default = false
2188 default = false
2188
2189
2189 [[items]]
2190 [[items]]
2190 section = "ui"
2191 section = "ui"
2191 name = "available-memory"
2192 name = "available-memory"
2192
2193
2193 [[items]]
2194 [[items]]
2194 section = "ui"
2195 section = "ui"
2195 name = "clonebundlefallback"
2196 name = "clonebundlefallback"
2196 default = false
2197 default = false
2197
2198
2198 [[items]]
2199 [[items]]
2199 section = "ui"
2200 section = "ui"
2200 name = "clonebundleprefers"
2201 name = "clonebundleprefers"
2201 default-type = "list_type"
2202 default-type = "list_type"
2202
2203
2203 [[items]]
2204 [[items]]
2204 section = "ui"
2205 section = "ui"
2205 name = "clonebundles"
2206 name = "clonebundles"
2206 default = true
2207 default = true
2207
2208
2208 [[items]]
2209 [[items]]
2209 section = "ui"
2210 section = "ui"
2210 name = "color"
2211 name = "color"
2211 default = "auto"
2212 default = "auto"
2212
2213
2213 [[items]]
2214 [[items]]
2214 section = "ui"
2215 section = "ui"
2215 name = "commitsubrepos"
2216 name = "commitsubrepos"
2216 default = false
2217 default = false
2217
2218
2218 [[items]]
2219 [[items]]
2219 section = "ui"
2220 section = "ui"
2220 name = "debug"
2221 name = "debug"
2221 default = false
2222 default = false
2222
2223
2223 [[items]]
2224 [[items]]
2224 section = "ui"
2225 section = "ui"
2225 name = "debugger"
2226 name = "debugger"
2226
2227
2227 [[items]]
2228 [[items]]
2228 section = "ui"
2229 section = "ui"
2229 name = "detailed-exit-code"
2230 name = "detailed-exit-code"
2230 default = false
2231 default = false
2231 experimental = true
2232 experimental = true
2232
2233
2233 [[items]]
2234 [[items]]
2234 section = "ui"
2235 section = "ui"
2235 name = "editor"
2236 name = "editor"
2236 default-type = "dynamic"
2237 default-type = "dynamic"
2237
2238
2238 [[items]]
2239 [[items]]
2239 section = "ui"
2240 section = "ui"
2240 name = "fallbackencoding"
2241 name = "fallbackencoding"
2241
2242
2242 [[items]]
2243 [[items]]
2243 section = "ui"
2244 section = "ui"
2244 name = "forcecwd"
2245 name = "forcecwd"
2245
2246
2246 [[items]]
2247 [[items]]
2247 section = "ui"
2248 section = "ui"
2248 name = "forcemerge"
2249 name = "forcemerge"
2249
2250
2250 [[items]]
2251 [[items]]
2251 section = "ui"
2252 section = "ui"
2252 name = "formatdebug"
2253 name = "formatdebug"
2253 default = false
2254 default = false
2254
2255
2255 [[items]]
2256 [[items]]
2256 section = "ui"
2257 section = "ui"
2257 name = "formatjson"
2258 name = "formatjson"
2258 default = false
2259 default = false
2259
2260
2260 [[items]]
2261 [[items]]
2261 section = "ui"
2262 section = "ui"
2262 name = "formatted"
2263 name = "formatted"
2263
2264
2264 [[items]]
2265 [[items]]
2265 section = "ui"
2266 section = "ui"
2266 name = "interactive"
2267 name = "interactive"
2267
2268
2268 [[items]]
2269 [[items]]
2269 section = "ui"
2270 section = "ui"
2270 name = "interface"
2271 name = "interface"
2271
2272
2272 [[items]]
2273 [[items]]
2273 section = "ui"
2274 section = "ui"
2274 name = "interface.chunkselector"
2275 name = "interface.chunkselector"
2275
2276
2276 [[items]]
2277 [[items]]
2277 section = "ui"
2278 section = "ui"
2278 name = "large-file-limit"
2279 name = "large-file-limit"
2279 default = 10485760
2280 default = 10485760
2280
2281
2281 [[items]]
2282 [[items]]
2282 section = "ui"
2283 section = "ui"
2283 name = "logblockedtimes"
2284 name = "logblockedtimes"
2284 default = false
2285 default = false
2285
2286
2286 [[items]]
2287 [[items]]
2287 section = "ui"
2288 section = "ui"
2288 name = "merge"
2289 name = "merge"
2289
2290
2290 [[items]]
2291 [[items]]
2291 section = "ui"
2292 section = "ui"
2292 name = "mergemarkers"
2293 name = "mergemarkers"
2293 default = "basic"
2294 default = "basic"
2294
2295
2295 [[items]]
2296 [[items]]
2296 section = "ui"
2297 section = "ui"
2297 name = "message-output"
2298 name = "message-output"
2298 default = "stdio"
2299 default = "stdio"
2299
2300
2300 [[items]]
2301 [[items]]
2301 section = "ui"
2302 section = "ui"
2302 name = "nontty"
2303 name = "nontty"
2303 default = false
2304 default = false
2304
2305
2305 [[items]]
2306 [[items]]
2306 section = "ui"
2307 section = "ui"
2307 name = "origbackuppath"
2308 name = "origbackuppath"
2308
2309
2309 [[items]]
2310 [[items]]
2310 section = "ui"
2311 section = "ui"
2311 name = "paginate"
2312 name = "paginate"
2312 default = true
2313 default = true
2313
2314
2314 [[items]]
2315 [[items]]
2315 section = "ui"
2316 section = "ui"
2316 name = "patch"
2317 name = "patch"
2317
2318
2318 [[items]]
2319 [[items]]
2319 section = "ui"
2320 section = "ui"
2320 name = "portablefilenames"
2321 name = "portablefilenames"
2321 default = "warn"
2322 default = "warn"
2322
2323
2323 [[items]]
2324 [[items]]
2324 section = "ui"
2325 section = "ui"
2325 name = "promptecho"
2326 name = "promptecho"
2326 default = false
2327 default = false
2327
2328
2328 [[items]]
2329 [[items]]
2329 section = "ui"
2330 section = "ui"
2330 name = "quiet"
2331 name = "quiet"
2331 default = false
2332 default = false
2332
2333
2333 [[items]]
2334 [[items]]
2334 section = "ui"
2335 section = "ui"
2335 name = "quietbookmarkmove"
2336 name = "quietbookmarkmove"
2336 default = false
2337 default = false
2337
2338
2338 [[items]]
2339 [[items]]
2339 section = "ui"
2340 section = "ui"
2340 name = "relative-paths"
2341 name = "relative-paths"
2341 default = "legacy"
2342 default = "legacy"
2342
2343
2343 [[items]]
2344 [[items]]
2344 section = "ui"
2345 section = "ui"
2345 name = "remotecmd"
2346 name = "remotecmd"
2346 default = "hg"
2347 default = "hg"
2347
2348
2348 [[items]]
2349 [[items]]
2349 section = "ui"
2350 section = "ui"
2350 name = "report_untrusted"
2351 name = "report_untrusted"
2351 default = true
2352 default = true
2352
2353
2353 [[items]]
2354 [[items]]
2354 section = "ui"
2355 section = "ui"
2355 name = "rollback"
2356 name = "rollback"
2356 default = true
2357 default = true
2357
2358
2358 [[items]]
2359 [[items]]
2359 section = "ui"
2360 section = "ui"
2360 name = "signal-safe-lock"
2361 name = "signal-safe-lock"
2361 default = true
2362 default = true
2362
2363
2363 [[items]]
2364 [[items]]
2364 section = "ui"
2365 section = "ui"
2365 name = "slash"
2366 name = "slash"
2366 default = false
2367 default = false
2367
2368
2368 [[items]]
2369 [[items]]
2369 section = "ui"
2370 section = "ui"
2370 name = "ssh"
2371 name = "ssh"
2371 default = "ssh"
2372 default = "ssh"
2372
2373
2373 [[items]]
2374 [[items]]
2374 section = "ui"
2375 section = "ui"
2375 name = "ssherrorhint"
2376 name = "ssherrorhint"
2376
2377
2377 [[items]]
2378 [[items]]
2378 section = "ui"
2379 section = "ui"
2379 name = "statuscopies"
2380 name = "statuscopies"
2380 default = false
2381 default = false
2381
2382
2382 [[items]]
2383 [[items]]
2383 section = "ui"
2384 section = "ui"
2384 name = "strict"
2385 name = "strict"
2385 default = false
2386 default = false
2386
2387
2387 [[items]]
2388 [[items]]
2388 section = "ui"
2389 section = "ui"
2389 name = "style"
2390 name = "style"
2390 default = ""
2391 default = ""
2391
2392
2392 [[items]]
2393 [[items]]
2393 section = "ui"
2394 section = "ui"
2394 name = "supportcontact"
2395 name = "supportcontact"
2395
2396
2396 [[items]]
2397 [[items]]
2397 section = "ui"
2398 section = "ui"
2398 name = "textwidth"
2399 name = "textwidth"
2399 default = 78
2400 default = 78
2400
2401
2401 [[items]]
2402 [[items]]
2402 section = "ui"
2403 section = "ui"
2403 name = "timeout"
2404 name = "timeout"
2404 default = "600"
2405 default = "600"
2405
2406
2406 [[items]]
2407 [[items]]
2407 section = "ui"
2408 section = "ui"
2408 name = "timeout.warn"
2409 name = "timeout.warn"
2409 default = 0
2410 default = 0
2410
2411
2411 [[items]]
2412 [[items]]
2412 section = "ui"
2413 section = "ui"
2413 name = "timestamp-output"
2414 name = "timestamp-output"
2414 default = false
2415 default = false
2415
2416
2416 [[items]]
2417 [[items]]
2417 section = "ui"
2418 section = "ui"
2418 name = "traceback"
2419 name = "traceback"
2419 default = false
2420 default = false
2420
2421
2421 [[items]]
2422 [[items]]
2422 section = "ui"
2423 section = "ui"
2423 name = "tweakdefaults"
2424 name = "tweakdefaults"
2424 default = false
2425 default = false
2425
2426
2426 [[items]]
2427 [[items]]
2427 section = "ui"
2428 section = "ui"
2428 name = "username"
2429 name = "username"
2429 alias = [["ui", "user"]]
2430 alias = [["ui", "user"]]
2430
2431
2431 [[items]]
2432 [[items]]
2432 section = "ui"
2433 section = "ui"
2433 name = "verbose"
2434 name = "verbose"
2434 default = false
2435 default = false
2435
2436
2436 [[items]]
2437 [[items]]
2437 section = "verify"
2438 section = "verify"
2438 name = "skipflags"
2439 name = "skipflags"
2439 default = 0
2440 default = 0
2440
2441
2441 [[items]]
2442 [[items]]
2442 section = "web"
2443 section = "web"
2443 name = "accesslog"
2444 name = "accesslog"
2444 default = "-"
2445 default = "-"
2445
2446
2446 [[items]]
2447 [[items]]
2447 section = "web"
2448 section = "web"
2448 name = "address"
2449 name = "address"
2449 default = ""
2450 default = ""
2450
2451
2451 [[items]]
2452 [[items]]
2452 section = "web"
2453 section = "web"
2453 name = "allow-archive"
2454 name = "allow-archive"
2454 default-type = "list_type"
2455 default-type = "list_type"
2455 alias = [["web", "allow_archive"]]
2456 alias = [["web", "allow_archive"]]
2456
2457
2457 [[items]]
2458 [[items]]
2458 section = "web"
2459 section = "web"
2459 name = "allow-pull"
2460 name = "allow-pull"
2460 default = true
2461 default = true
2461 alias = [["web", "allowpull"]]
2462 alias = [["web", "allowpull"]]
2462
2463
2463 [[items]]
2464 [[items]]
2464 section = "web"
2465 section = "web"
2465 name = "allow-push"
2466 name = "allow-push"
2466 default-type = "list_type"
2467 default-type = "list_type"
2467 alias = [["web", "allow_push"]]
2468 alias = [["web", "allow_push"]]
2468
2469
2469 [[items]]
2470 [[items]]
2470 section = "web"
2471 section = "web"
2471 name = "allow_read"
2472 name = "allow_read"
2472 default-type = "list_type"
2473 default-type = "list_type"
2473
2474
2474 [[items]]
2475 [[items]]
2475 section = "web"
2476 section = "web"
2476 name = "allowbz2"
2477 name = "allowbz2"
2477 default = false
2478 default = false
2478
2479
2479 [[items]]
2480 [[items]]
2480 section = "web"
2481 section = "web"
2481 name = "allowgz"
2482 name = "allowgz"
2482 default = false
2483 default = false
2483
2484
2484 [[items]]
2485 [[items]]
2485 section = "web"
2486 section = "web"
2486 name = "allowzip"
2487 name = "allowzip"
2487 default = false
2488 default = false
2488
2489
2489 [[items]]
2490 [[items]]
2490 section = "web"
2491 section = "web"
2491 name = "archivesubrepos"
2492 name = "archivesubrepos"
2492 default = false
2493 default = false
2493
2494
2494 [[items]]
2495 [[items]]
2495 section = "web"
2496 section = "web"
2496 name = "baseurl"
2497 name = "baseurl"
2497
2498
2498 [[items]]
2499 [[items]]
2499 section = "web"
2500 section = "web"
2500 name = "cacerts"
2501 name = "cacerts"
2501
2502
2502 [[items]]
2503 [[items]]
2503 section = "web"
2504 section = "web"
2504 name = "cache"
2505 name = "cache"
2505 default = true
2506 default = true
2506
2507
2507 [[items]]
2508 [[items]]
2508 section = "web"
2509 section = "web"
2509 name = "certificate"
2510 name = "certificate"
2510
2511
2511 [[items]]
2512 [[items]]
2512 section = "web"
2513 section = "web"
2513 name = "collapse"
2514 name = "collapse"
2514 default = false
2515 default = false
2515
2516
2516 [[items]]
2517 [[items]]
2517 section = "web"
2518 section = "web"
2518 name = "comparisoncontext"
2519 name = "comparisoncontext"
2519 default = 5
2520 default = 5
2520
2521
2521 [[items]]
2522 [[items]]
2522 section = "web"
2523 section = "web"
2523 name = "contact"
2524 name = "contact"
2524
2525
2525 [[items]]
2526 [[items]]
2526 section = "web"
2527 section = "web"
2527 name = "csp"
2528 name = "csp"
2528
2529
2529 [[items]]
2530 [[items]]
2530 section = "web"
2531 section = "web"
2531 name = "deny_push"
2532 name = "deny_push"
2532 default-type = "list_type"
2533 default-type = "list_type"
2533
2534
2534 [[items]]
2535 [[items]]
2535 section = "web"
2536 section = "web"
2536 name = "deny_read"
2537 name = "deny_read"
2537 default-type = "list_type"
2538 default-type = "list_type"
2538
2539
2539 [[items]]
2540 [[items]]
2540 section = "web"
2541 section = "web"
2541 name = "descend"
2542 name = "descend"
2542 default = true
2543 default = true
2543
2544
2544 [[items]]
2545 [[items]]
2545 section = "web"
2546 section = "web"
2546 name = "description"
2547 name = "description"
2547 default = ""
2548 default = ""
2548
2549
2549 [[items]]
2550 [[items]]
2550 section = "web"
2551 section = "web"
2551 name = "encoding"
2552 name = "encoding"
2552 default-type = "lazy_module"
2553 default-type = "lazy_module"
2553 default = "encoding.encoding"
2554 default = "encoding.encoding"
2554
2555
2555 [[items]]
2556 [[items]]
2556 section = "web"
2557 section = "web"
2557 name = "errorlog"
2558 name = "errorlog"
2558 default = "-"
2559 default = "-"
2559
2560
2560 [[items]]
2561 [[items]]
2561 section = "web"
2562 section = "web"
2562 name = "guessmime"
2563 name = "guessmime"
2563 default = false
2564 default = false
2564
2565
2565 [[items]]
2566 [[items]]
2566 section = "web"
2567 section = "web"
2567 name = "hidden"
2568 name = "hidden"
2568 default = false
2569 default = false
2569
2570
2570 [[items]]
2571 [[items]]
2571 section = "web"
2572 section = "web"
2572 name = "ipv6"
2573 name = "ipv6"
2573 default = false
2574 default = false
2574
2575
2575 [[items]]
2576 [[items]]
2576 section = "web"
2577 section = "web"
2577 name = "labels"
2578 name = "labels"
2578 default-type = "list_type"
2579 default-type = "list_type"
2579
2580
2580 [[items]]
2581 [[items]]
2581 section = "web"
2582 section = "web"
2582 name = "logoimg"
2583 name = "logoimg"
2583 default = "hglogo.png"
2584 default = "hglogo.png"
2584
2585
2585 [[items]]
2586 [[items]]
2586 section = "web"
2587 section = "web"
2587 name = "logourl"
2588 name = "logourl"
2588 default = "https://mercurial-scm.org/"
2589 default = "https://mercurial-scm.org/"
2589
2590
2590 [[items]]
2591 [[items]]
2591 section = "web"
2592 section = "web"
2592 name = "maxchanges"
2593 name = "maxchanges"
2593 default = 10
2594 default = 10
2594
2595
2595 [[items]]
2596 [[items]]
2596 section = "web"
2597 section = "web"
2597 name = "maxfiles"
2598 name = "maxfiles"
2598 default = 10
2599 default = 10
2599
2600
2600 [[items]]
2601 [[items]]
2601 section = "web"
2602 section = "web"
2602 name = "maxshortchanges"
2603 name = "maxshortchanges"
2603 default = 60
2604 default = 60
2604
2605
2605 [[items]]
2606 [[items]]
2606 section = "web"
2607 section = "web"
2607 name = "motd"
2608 name = "motd"
2608 default = ""
2609 default = ""
2609
2610
2610 [[items]]
2611 [[items]]
2611 section = "web"
2612 section = "web"
2612 name = "name"
2613 name = "name"
2613 default-type = "dynamic"
2614 default-type = "dynamic"
2614
2615
2615 [[items]]
2616 [[items]]
2616 section = "web"
2617 section = "web"
2617 name = "port"
2618 name = "port"
2618 default = 8000
2619 default = 8000
2619
2620
2620 [[items]]
2621 [[items]]
2621 section = "web"
2622 section = "web"
2622 name = "prefix"
2623 name = "prefix"
2623 default = ""
2624 default = ""
2624
2625
2625 [[items]]
2626 [[items]]
2626 section = "web"
2627 section = "web"
2627 name = "push_ssl"
2628 name = "push_ssl"
2628 default = true
2629 default = true
2629
2630
2630 [[items]]
2631 [[items]]
2631 section = "web"
2632 section = "web"
2632 name = "refreshinterval"
2633 name = "refreshinterval"
2633 default = 20
2634 default = 20
2634
2635
2635 [[items]]
2636 [[items]]
2636 section = "web"
2637 section = "web"
2637 name = "server-header"
2638 name = "server-header"
2638
2639
2639 [[items]]
2640 [[items]]
2640 section = "web"
2641 section = "web"
2641 name = "static"
2642 name = "static"
2642
2643
2643 [[items]]
2644 [[items]]
2644 section = "web"
2645 section = "web"
2645 name = "staticurl"
2646 name = "staticurl"
2646
2647
2647 [[items]]
2648 [[items]]
2648 section = "web"
2649 section = "web"
2649 name = "stripes"
2650 name = "stripes"
2650 default = 1
2651 default = 1
2651
2652
2652 [[items]]
2653 [[items]]
2653 section = "web"
2654 section = "web"
2654 name = "style"
2655 name = "style"
2655 default = "paper"
2656 default = "paper"
2656
2657
2657 [[items]]
2658 [[items]]
2658 section = "web"
2659 section = "web"
2659 name = "templates"
2660 name = "templates"
2660
2661
2661 [[items]]
2662 [[items]]
2662 section = "web"
2663 section = "web"
2663 name = "view"
2664 name = "view"
2664 default = "served"
2665 default = "served"
2665 experimental = true
2666 experimental = true
2666
2667
2667 [[items]]
2668 [[items]]
2668 section = "worker"
2669 section = "worker"
2669 name = "backgroundclose"
2670 name = "backgroundclose"
2670 default-type = "dynamic"
2671 default-type = "dynamic"
2671
2672
2672 [[items]]
2673 [[items]]
2673 section = "worker"
2674 section = "worker"
2674 name = "backgroundclosemaxqueue"
2675 name = "backgroundclosemaxqueue"
2675 # Windows defaults to a limit of 512 open files. A buffer of 128
2676 # Windows defaults to a limit of 512 open files. A buffer of 128
2676 # should give us enough headway.
2677 # should give us enough headway.
2677 default = 384
2678 default = 384
2678
2679
2679 [[items]]
2680 [[items]]
2680 section = "worker"
2681 section = "worker"
2681 name = "backgroundcloseminfilecount"
2682 name = "backgroundcloseminfilecount"
2682 default = 2048
2683 default = 2048
2683
2684
2684 [[items]]
2685 [[items]]
2685 section = "worker"
2686 section = "worker"
2686 name = "backgroundclosethreadcount"
2687 name = "backgroundclosethreadcount"
2687 default = 4
2688 default = 4
2688
2689
2689 [[items]]
2690 [[items]]
2690 section = "worker"
2691 section = "worker"
2691 name = "enabled"
2692 name = "enabled"
2692 default = true
2693 default = true
2693
2694
2694 [[items]]
2695 [[items]]
2695 section = "worker"
2696 section = "worker"
2696 name = "numcpus"
2697 name = "numcpus"
2697
2698
2699 # Templates and template applications
2700
2698 [[template-applications]]
2701 [[template-applications]]
2699 template = "diff-options"
2702 template = "diff-options"
2700 section = "annotate"
2703 section = "annotate"
2701
2704
2702 [[template-applications]]
2705 [[template-applications]]
2703 template = "diff-options"
2706 template = "diff-options"
2704 section = "commands"
2707 section = "commands"
2705 prefix = "commit.interactive"
2708 prefix = "commit.interactive"
2706
2709
2707 [[template-applications]]
2710 [[template-applications]]
2708 template = "diff-options"
2711 template = "diff-options"
2709 section = "commands"
2712 section = "commands"
2710 prefix = "revert.interactive"
2713 prefix = "revert.interactive"
2711
2714
2712 [[template-applications]]
2715 [[template-applications]]
2713 template = "diff-options"
2716 template = "diff-options"
2714 section = "diff"
2717 section = "diff"
2715
2718
2716 [templates]
2719 [templates]
2717 [[templates.diff-options]]
2720 [[templates.diff-options]]
2718 suffix = "nodates"
2721 suffix = "nodates"
2719 default = false
2722 default = false
2720
2723
2721 [[templates.diff-options]]
2724 [[templates.diff-options]]
2722 suffix = "showfunc"
2725 suffix = "showfunc"
2723 default = false
2726 default = false
2724
2727
2725 [[templates.diff-options]]
2728 [[templates.diff-options]]
2726 suffix = "unified"
2729 suffix = "unified"
2727
2730
2728 [[templates.diff-options]]
2731 [[templates.diff-options]]
2729 suffix = "git"
2732 suffix = "git"
2730 default = false
2733 default = false
2731
2734
2732 [[templates.diff-options]]
2735 [[templates.diff-options]]
2733 suffix = "ignorews"
2736 suffix = "ignorews"
2734 default = false
2737 default = false
2735
2738
2736 [[templates.diff-options]]
2739 [[templates.diff-options]]
2737 suffix = "ignorewsamount"
2740 suffix = "ignorewsamount"
2738 default = false
2741 default = false
2739
2742
2740 [[templates.diff-options]]
2743 [[templates.diff-options]]
2741 suffix = "ignoreblanklines"
2744 suffix = "ignoreblanklines"
2742 default = false
2745 default = false
2743
2746
2744 [[templates.diff-options]]
2747 [[templates.diff-options]]
2745 suffix = "ignorewseol"
2748 suffix = "ignorewseol"
2746 default = false
2749 default = false
2747
2750
2748 [[templates.diff-options]]
2751 [[templates.diff-options]]
2749 suffix = "nobinary"
2752 suffix = "nobinary"
2750 default = false
2753 default = false
2751
2754
2752 [[templates.diff-options]]
2755 [[templates.diff-options]]
2753 suffix = "noprefix"
2756 suffix = "noprefix"
2754 default = false
2757 default = false
2755
2758
2756 [[templates.diff-options]]
2759 [[templates.diff-options]]
2757 suffix = "word-diff"
2760 suffix = "word-diff"
2758 default = false
2761 default = false
2759
2762
2763 # In-core extensions
2764
2765 [[items]]
2766 section = "blackbox"
2767 name = "dirty"
2768 default = false
2769 in_core_extension = "blackbox"
2770
2771 [[items]]
2772 section = "blackbox"
2773 name = "maxsize"
2774 default = "1 MB"
2775 in_core_extension = "blackbox"
2776
2777 [[items]]
2778 section = "blackbox"
2779 name = "logsource"
2780 default = false
2781 in_core_extension = "blackbox"
2782
2783 [[items]]
2784 section = "blackbox"
2785 name = "maxfiles"
2786 default = 7
2787 in_core_extension = "blackbox"
2788
2789 [[items]]
2790 section = "blackbox"
2791 name = "track"
2792 default-type = "lambda"
2793 default = ["*"]
2794 in_core_extension = "blackbox"
2795
2796 [[items]]
2797 section = "blackbox"
2798 name = "ignore"
2799 default-type = "lambda"
2800 default = ["chgserver", "cmdserver", "extension"]
2801 in_core_extension = "blackbox"
2802
2803 [[items]]
2804 section = "blackbox"
2805 name = "date-format"
2806 default = ""
2807 in_core_extension = "blackbox"
@@ -1,2337 +1,2344 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8
8
9 import collections
9 import collections
10 import contextlib
10 import contextlib
11 import datetime
11 import datetime
12 import errno
12 import errno
13 import inspect
13 import inspect
14 import os
14 import os
15 import re
15 import re
16 import signal
16 import signal
17 import socket
17 import socket
18 import subprocess
18 import subprocess
19 import sys
19 import sys
20 import traceback
20 import traceback
21
21
22 from typing import (
22 from typing import (
23 Any,
23 Any,
24 Callable,
24 Callable,
25 Dict,
25 Dict,
26 List,
26 List,
27 NoReturn,
27 NoReturn,
28 Optional,
28 Optional,
29 Tuple,
29 Tuple,
30 Type,
30 Type,
31 TypeVar,
31 TypeVar,
32 Union,
32 Union,
33 cast,
33 cast,
34 overload,
34 overload,
35 )
35 )
36
36
37 from .i18n import _
37 from .i18n import _
38 from .node import hex
38 from .node import hex
39 from .pycompat import (
39 from .pycompat import (
40 getattr,
40 getattr,
41 open,
41 open,
42 )
42 )
43
43
44 from . import (
44 from . import (
45 color,
45 color,
46 config,
46 config,
47 configitems,
47 configitems,
48 encoding,
48 encoding,
49 error,
49 error,
50 extensions,
50 formatter,
51 formatter,
51 loggingutil,
52 loggingutil,
52 progress,
53 progress,
53 pycompat,
54 pycompat,
54 rcutil,
55 rcutil,
55 scmutil,
56 scmutil,
56 util,
57 util,
57 )
58 )
58 from .utils import (
59 from .utils import (
59 dateutil,
60 dateutil,
60 procutil,
61 procutil,
61 resourceutil,
62 resourceutil,
62 stringutil,
63 stringutil,
63 urlutil,
64 urlutil,
64 )
65 )
65
66
66 _ConfigItems = Dict[Tuple[bytes, bytes], object] # {(section, name) : value}
67 _ConfigItems = Dict[Tuple[bytes, bytes], object] # {(section, name) : value}
67 # The **opts args of the various write() methods can be basically anything, but
68 # The **opts args of the various write() methods can be basically anything, but
68 # there's no way to express it as "anything but str". So type it to be the
69 # there's no way to express it as "anything but str". So type it to be the
69 # handful of known types that are used.
70 # handful of known types that are used.
70 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
71 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
71 _PromptChoice = Tuple[bytes, bytes]
72 _PromptChoice = Tuple[bytes, bytes]
72 _Tui = TypeVar('_Tui', bound="ui")
73 _Tui = TypeVar('_Tui', bound="ui")
73
74
74 urlreq = util.urlreq
75 urlreq = util.urlreq
75
76
76 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
77 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
77 _keepalnum: bytes = b''.join(
78 _keepalnum: bytes = b''.join(
78 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
79 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
79 )
80 )
80
81
81 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
82 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
82 tweakrc: bytes = b"""
83 tweakrc: bytes = b"""
83 [ui]
84 [ui]
84 # The rollback command is dangerous. As a rule, don't use it.
85 # The rollback command is dangerous. As a rule, don't use it.
85 rollback = False
86 rollback = False
86 # Make `hg status` report copy information
87 # Make `hg status` report copy information
87 statuscopies = yes
88 statuscopies = yes
88 # Prefer curses UIs when available. Revert to plain-text with `text`.
89 # Prefer curses UIs when available. Revert to plain-text with `text`.
89 interface = curses
90 interface = curses
90 # Make compatible commands emit cwd-relative paths by default.
91 # Make compatible commands emit cwd-relative paths by default.
91 relative-paths = yes
92 relative-paths = yes
92
93
93 [commands]
94 [commands]
94 # Grep working directory by default.
95 # Grep working directory by default.
95 grep.all-files = True
96 grep.all-files = True
96 # Refuse to perform an `hg update` that would cause a file content merge
97 # Refuse to perform an `hg update` that would cause a file content merge
97 update.check = noconflict
98 update.check = noconflict
98 # Show conflicts information in `hg status`
99 # Show conflicts information in `hg status`
99 status.verbose = True
100 status.verbose = True
100 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
101 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
101 resolve.explicit-re-merge = True
102 resolve.explicit-re-merge = True
102
103
103 [diff]
104 [diff]
104 git = 1
105 git = 1
105 showfunc = 1
106 showfunc = 1
106 word-diff = 1
107 word-diff = 1
107 """
108 """
108
109
109 samplehgrcs: Dict[bytes, bytes] = {
110 samplehgrcs: Dict[bytes, bytes] = {
110 b'user': b"""# example user config (see 'hg help config' for more info)
111 b'user': b"""# example user config (see 'hg help config' for more info)
111 [ui]
112 [ui]
112 # name and email, e.g.
113 # name and email, e.g.
113 # username = Jane Doe <jdoe@example.com>
114 # username = Jane Doe <jdoe@example.com>
114 username =
115 username =
115
116
116 # We recommend enabling tweakdefaults to get slight improvements to
117 # We recommend enabling tweakdefaults to get slight improvements to
117 # the UI over time. Make sure to set HGPLAIN in the environment when
118 # the UI over time. Make sure to set HGPLAIN in the environment when
118 # writing scripts!
119 # writing scripts!
119 # tweakdefaults = True
120 # tweakdefaults = True
120
121
121 # uncomment to disable color in command output
122 # uncomment to disable color in command output
122 # (see 'hg help color' for details)
123 # (see 'hg help color' for details)
123 # color = never
124 # color = never
124
125
125 # uncomment to disable command output pagination
126 # uncomment to disable command output pagination
126 # (see 'hg help pager' for details)
127 # (see 'hg help pager' for details)
127 # paginate = never
128 # paginate = never
128
129
129 [extensions]
130 [extensions]
130 # uncomment the lines below to enable some popular extensions
131 # uncomment the lines below to enable some popular extensions
131 # (see 'hg help extensions' for more info)
132 # (see 'hg help extensions' for more info)
132 #
133 #
133 # histedit =
134 # histedit =
134 # rebase =
135 # rebase =
135 # uncommit =
136 # uncommit =
136 """,
137 """,
137 b'cloned': b"""# example repository config (see 'hg help config' for more info)
138 b'cloned': b"""# example repository config (see 'hg help config' for more info)
138 [paths]
139 [paths]
139 default = %s
140 default = %s
140
141
141 # path aliases to other clones of this repo in URLs or filesystem paths
142 # path aliases to other clones of this repo in URLs or filesystem paths
142 # (see 'hg help config.paths' for more info)
143 # (see 'hg help config.paths' for more info)
143 #
144 #
144 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
145 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
145 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
146 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
146 # my-clone = /home/jdoe/jdoes-clone
147 # my-clone = /home/jdoe/jdoes-clone
147
148
148 [ui]
149 [ui]
149 # name and email (local to this repository, optional), e.g.
150 # name and email (local to this repository, optional), e.g.
150 # username = Jane Doe <jdoe@example.com>
151 # username = Jane Doe <jdoe@example.com>
151 """,
152 """,
152 b'local': b"""# example repository config (see 'hg help config' for more info)
153 b'local': b"""# example repository config (see 'hg help config' for more info)
153 [paths]
154 [paths]
154 # path aliases to other clones of this repo in URLs or filesystem paths
155 # path aliases to other clones of this repo in URLs or filesystem paths
155 # (see 'hg help config.paths' for more info)
156 # (see 'hg help config.paths' for more info)
156 #
157 #
157 # default = http://example.com/hg/example-repo
158 # default = http://example.com/hg/example-repo
158 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
159 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
159 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
160 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
160 # my-clone = /home/jdoe/jdoes-clone
161 # my-clone = /home/jdoe/jdoes-clone
161
162
162 [ui]
163 [ui]
163 # name and email (local to this repository, optional), e.g.
164 # name and email (local to this repository, optional), e.g.
164 # username = Jane Doe <jdoe@example.com>
165 # username = Jane Doe <jdoe@example.com>
165 """,
166 """,
166 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
167 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
167
168
168 [ui]
169 [ui]
169 # uncomment to disable color in command output
170 # uncomment to disable color in command output
170 # (see 'hg help color' for details)
171 # (see 'hg help color' for details)
171 # color = never
172 # color = never
172
173
173 # uncomment to disable command output pagination
174 # uncomment to disable command output pagination
174 # (see 'hg help pager' for details)
175 # (see 'hg help pager' for details)
175 # paginate = never
176 # paginate = never
176
177
177 [extensions]
178 [extensions]
178 # uncomment the lines below to enable some popular extensions
179 # uncomment the lines below to enable some popular extensions
179 # (see 'hg help extensions' for more info)
180 # (see 'hg help extensions' for more info)
180 #
181 #
181 # blackbox =
182 # blackbox =
182 # churn =
183 # churn =
183 """,
184 """,
184 }
185 }
185
186
186
187
187 def _maybestrurl(maybebytes):
188 def _maybestrurl(maybebytes):
188 return pycompat.rapply(pycompat.strurl, maybebytes)
189 return pycompat.rapply(pycompat.strurl, maybebytes)
189
190
190
191
191 def _maybebytesurl(maybestr):
192 def _maybebytesurl(maybestr):
192 return pycompat.rapply(pycompat.bytesurl, maybestr)
193 return pycompat.rapply(pycompat.bytesurl, maybestr)
193
194
194
195
195 class httppasswordmgrdbproxy:
196 class httppasswordmgrdbproxy:
196 """Delays loading urllib2 until it's needed."""
197 """Delays loading urllib2 until it's needed."""
197
198
198 def __init__(self) -> None:
199 def __init__(self) -> None:
199 self._mgr = None
200 self._mgr = None
200
201
201 def _get_mgr(self):
202 def _get_mgr(self):
202 if self._mgr is None:
203 if self._mgr is None:
203 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
204 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
204 return self._mgr
205 return self._mgr
205
206
206 def add_password(self, realm, uris, user, passwd):
207 def add_password(self, realm, uris, user, passwd):
207 return self._get_mgr().add_password(
208 return self._get_mgr().add_password(
208 _maybestrurl(realm),
209 _maybestrurl(realm),
209 _maybestrurl(uris),
210 _maybestrurl(uris),
210 _maybestrurl(user),
211 _maybestrurl(user),
211 _maybestrurl(passwd),
212 _maybestrurl(passwd),
212 )
213 )
213
214
214 def find_user_password(self, realm, uri):
215 def find_user_password(self, realm, uri):
215 mgr = self._get_mgr()
216 mgr = self._get_mgr()
216 return _maybebytesurl(
217 return _maybebytesurl(
217 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
218 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
218 )
219 )
219
220
220
221
221 def _catchterm(*args) -> NoReturn:
222 def _catchterm(*args) -> NoReturn:
222 raise error.SignalInterrupt
223 raise error.SignalInterrupt
223
224
224
225
225 # unique object used to detect no default value has been provided when
226 # unique object used to detect no default value has been provided when
226 # retrieving configuration value.
227 # retrieving configuration value.
227 _unset = object()
228 _unset = object()
228
229
229 # _reqexithandlers: callbacks run at the end of a request
230 # _reqexithandlers: callbacks run at the end of a request
230 _reqexithandlers: List = []
231 _reqexithandlers: List = []
231
232
232
233
233 class ui:
234 class ui:
234 def __init__(self, src: Optional["ui"] = None) -> None:
235 def __init__(self, src: Optional["ui"] = None) -> None:
235 """Create a fresh new ui object if no src given
236 """Create a fresh new ui object if no src given
236
237
237 Use uimod.ui.load() to create a ui which knows global and user configs.
238 Use uimod.ui.load() to create a ui which knows global and user configs.
238 In most cases, you should use ui.copy() to create a copy of an existing
239 In most cases, you should use ui.copy() to create a copy of an existing
239 ui object.
240 ui object.
240 """
241 """
241 # _buffers: used for temporary capture of output
242 # _buffers: used for temporary capture of output
242 self._buffers = []
243 self._buffers = []
243 # 3-tuple describing how each buffer in the stack behaves.
244 # 3-tuple describing how each buffer in the stack behaves.
244 # Values are (capture stderr, capture subprocesses, apply labels).
245 # Values are (capture stderr, capture subprocesses, apply labels).
245 self._bufferstates = []
246 self._bufferstates = []
246 # When a buffer is active, defines whether we are expanding labels.
247 # When a buffer is active, defines whether we are expanding labels.
247 # This exists to prevent an extra list lookup.
248 # This exists to prevent an extra list lookup.
248 self._bufferapplylabels = None
249 self._bufferapplylabels = None
249 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
250 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
250 self._reportuntrusted = True
251 self._reportuntrusted = True
251 self._knownconfig = configitems.coreitems
252 self._knownconfig = configitems.coreitems
252 self._ocfg = config.config() # overlay
253 self._ocfg = config.config() # overlay
253 self._tcfg = config.config() # trusted
254 self._tcfg = config.config() # trusted
254 self._ucfg = config.config() # untrusted
255 self._ucfg = config.config() # untrusted
255 self._trustusers = set()
256 self._trustusers = set()
256 self._trustgroups = set()
257 self._trustgroups = set()
257 self.callhooks = True
258 self.callhooks = True
258 # hold the root to use for each [paths] entry
259 # hold the root to use for each [paths] entry
259 self._path_to_root = {}
260 self._path_to_root = {}
260 # Insecure server connections requested.
261 # Insecure server connections requested.
261 self.insecureconnections = False
262 self.insecureconnections = False
262 # Blocked time
263 # Blocked time
263 self.logblockedtimes = False
264 self.logblockedtimes = False
264 # color mode: see mercurial/color.py for possible value
265 # color mode: see mercurial/color.py for possible value
265 self._colormode = None
266 self._colormode = None
266 self._terminfoparams = {}
267 self._terminfoparams = {}
267 self._styles = {}
268 self._styles = {}
268 self._uninterruptible = False
269 self._uninterruptible = False
269 self.showtimestamp = False
270 self.showtimestamp = False
270
271
271 if src:
272 if src:
272 self._fout = src._fout
273 self._fout = src._fout
273 self._ferr = src._ferr
274 self._ferr = src._ferr
274 self._fin = src._fin
275 self._fin = src._fin
275 self._fmsg = src._fmsg
276 self._fmsg = src._fmsg
276 self._fmsgout = src._fmsgout
277 self._fmsgout = src._fmsgout
277 self._fmsgerr = src._fmsgerr
278 self._fmsgerr = src._fmsgerr
278 self._finoutredirected = src._finoutredirected
279 self._finoutredirected = src._finoutredirected
279 self._loggers = src._loggers.copy()
280 self._loggers = src._loggers.copy()
280 self.pageractive = src.pageractive
281 self.pageractive = src.pageractive
281 self._disablepager = src._disablepager
282 self._disablepager = src._disablepager
282 self._tweaked = src._tweaked
283 self._tweaked = src._tweaked
283
284
284 self._tcfg = src._tcfg.copy()
285 self._tcfg = src._tcfg.copy()
285 self._ucfg = src._ucfg.copy()
286 self._ucfg = src._ucfg.copy()
286 self._ocfg = src._ocfg.copy()
287 self._ocfg = src._ocfg.copy()
287 self._trustusers = src._trustusers.copy()
288 self._trustusers = src._trustusers.copy()
288 self._trustgroups = src._trustgroups.copy()
289 self._trustgroups = src._trustgroups.copy()
289 self.environ = src.environ
290 self.environ = src.environ
290 self.callhooks = src.callhooks
291 self.callhooks = src.callhooks
291 self._path_to_root = src._path_to_root
292 self._path_to_root = src._path_to_root
292 self.insecureconnections = src.insecureconnections
293 self.insecureconnections = src.insecureconnections
293 self._colormode = src._colormode
294 self._colormode = src._colormode
294 self._terminfoparams = src._terminfoparams.copy()
295 self._terminfoparams = src._terminfoparams.copy()
295 self._styles = src._styles.copy()
296 self._styles = src._styles.copy()
296
297
297 self.fixconfig()
298 self.fixconfig()
298
299
299 self.httppasswordmgrdb = src.httppasswordmgrdb
300 self.httppasswordmgrdb = src.httppasswordmgrdb
300 self._blockedtimes = src._blockedtimes
301 self._blockedtimes = src._blockedtimes
301 else:
302 else:
302 self._fout = procutil.stdout
303 self._fout = procutil.stdout
303 self._ferr = procutil.stderr
304 self._ferr = procutil.stderr
304 self._fin = procutil.stdin
305 self._fin = procutil.stdin
305 self._fmsg = None
306 self._fmsg = None
306 self._fmsgout = self.fout # configurable
307 self._fmsgout = self.fout # configurable
307 self._fmsgerr = self.ferr # configurable
308 self._fmsgerr = self.ferr # configurable
308 self._finoutredirected = False
309 self._finoutredirected = False
309 self._loggers = {}
310 self._loggers = {}
310 self.pageractive = False
311 self.pageractive = False
311 self._disablepager = False
312 self._disablepager = False
312 self._tweaked = False
313 self._tweaked = False
313
314
314 # shared read-only environment
315 # shared read-only environment
315 self.environ = encoding.environ
316 self.environ = encoding.environ
316
317
317 self.httppasswordmgrdb = httppasswordmgrdbproxy()
318 self.httppasswordmgrdb = httppasswordmgrdbproxy()
318 self._blockedtimes = collections.defaultdict(int)
319 self._blockedtimes = collections.defaultdict(int)
319
320
320 allowed = self.configlist(b'experimental', b'exportableenviron')
321 allowed = self.configlist(b'experimental', b'exportableenviron')
321 if b'*' in allowed:
322 if b'*' in allowed:
322 self._exportableenviron = self.environ
323 self._exportableenviron = self.environ
323 else:
324 else:
324 self._exportableenviron = {}
325 self._exportableenviron = {}
325 for k in allowed:
326 for k in allowed:
326 if k in self.environ:
327 if k in self.environ:
327 self._exportableenviron[k] = self.environ[k]
328 self._exportableenviron[k] = self.environ[k]
328
329
329 def _new_source(self) -> None:
330 def _new_source(self) -> None:
330 self._ocfg.new_source()
331 self._ocfg.new_source()
331 self._tcfg.new_source()
332 self._tcfg.new_source()
332 self._ucfg.new_source()
333 self._ucfg.new_source()
333
334
334 @classmethod
335 @classmethod
335 def load(cls: Type[_Tui]) -> _Tui:
336 def load(cls: Type[_Tui]) -> _Tui:
336 """Create a ui and load global and user configs"""
337 """Create a ui and load global and user configs"""
337 u = cls()
338 u = cls()
338 # we always trust global config files and environment variables
339 # we always trust global config files and environment variables
339 for t, f in rcutil.rccomponents():
340 for t, f in rcutil.rccomponents():
340 if t == b'path':
341 if t == b'path':
341 u.readconfig(f, trust=True)
342 u.readconfig(f, trust=True)
342 elif t == b'resource':
343 elif t == b'resource':
343 u.read_resource_config(f, trust=True)
344 u.read_resource_config(f, trust=True)
344 elif t == b'items':
345 elif t == b'items':
345 u._new_source()
346 u._new_source()
346 sections = set()
347 sections = set()
347 for section, name, value, source in f:
348 for section, name, value, source in f:
348 # do not set u._ocfg
349 # do not set u._ocfg
349 # XXX clean this up once immutable config object is a thing
350 # XXX clean this up once immutable config object is a thing
350 u._tcfg.set(section, name, value, source)
351 u._tcfg.set(section, name, value, source)
351 u._ucfg.set(section, name, value, source)
352 u._ucfg.set(section, name, value, source)
352 sections.add(section)
353 sections.add(section)
353 for section in sections:
354 for section in sections:
354 u.fixconfig(section=section)
355 u.fixconfig(section=section)
355 else:
356 else:
356 raise error.ProgrammingError(b'unknown rctype: %s' % t)
357 raise error.ProgrammingError(b'unknown rctype: %s' % t)
357 u._maybetweakdefaults()
358 u._maybetweakdefaults()
358 u._new_source() # anything after that is a different level
359 u._new_source() # anything after that is a different level
359 return u
360 return u
360
361
361 def _maybetweakdefaults(self) -> None:
362 def _maybetweakdefaults(self) -> None:
362 if not self.configbool(b'ui', b'tweakdefaults'):
363 if not self.configbool(b'ui', b'tweakdefaults'):
363 return
364 return
364 if self._tweaked or self.plain(b'tweakdefaults'):
365 if self._tweaked or self.plain(b'tweakdefaults'):
365 return
366 return
366
367
367 # Note: it is SUPER IMPORTANT that you set self._tweaked to
368 # Note: it is SUPER IMPORTANT that you set self._tweaked to
368 # True *before* any calls to setconfig(), otherwise you'll get
369 # True *before* any calls to setconfig(), otherwise you'll get
369 # infinite recursion between setconfig and this method.
370 # infinite recursion between setconfig and this method.
370 #
371 #
371 # TODO: We should extract an inner method in setconfig() to
372 # TODO: We should extract an inner method in setconfig() to
372 # avoid this weirdness.
373 # avoid this weirdness.
373 self._tweaked = True
374 self._tweaked = True
374 tmpcfg = config.config()
375 tmpcfg = config.config()
375 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
376 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
376 for section in tmpcfg:
377 for section in tmpcfg:
377 for name, value in tmpcfg.items(section):
378 for name, value in tmpcfg.items(section):
378 if not self.hasconfig(section, name):
379 if not self.hasconfig(section, name):
379 self.setconfig(section, name, value, b"<tweakdefaults>")
380 self.setconfig(section, name, value, b"<tweakdefaults>")
380
381
381 def copy(self: _Tui) -> _Tui:
382 def copy(self: _Tui) -> _Tui:
382 return self.__class__(self)
383 return self.__class__(self)
383
384
384 def resetstate(self) -> None:
385 def resetstate(self) -> None:
385 """Clear internal state that shouldn't persist across commands"""
386 """Clear internal state that shouldn't persist across commands"""
386 if self._progbar:
387 if self._progbar:
387 self._progbar.resetstate() # reset last-print time of progress bar
388 self._progbar.resetstate() # reset last-print time of progress bar
388 self.httppasswordmgrdb = httppasswordmgrdbproxy()
389 self.httppasswordmgrdb = httppasswordmgrdbproxy()
389
390
390 @contextlib.contextmanager
391 @contextlib.contextmanager
391 def timeblockedsection(self, key: bytes):
392 def timeblockedsection(self, key: bytes):
392 # this is open-coded below - search for timeblockedsection to find them
393 # this is open-coded below - search for timeblockedsection to find them
393 starttime = util.timer()
394 starttime = util.timer()
394 try:
395 try:
395 yield
396 yield
396 finally:
397 finally:
397 self._blockedtimes[key + b'_blocked'] += (
398 self._blockedtimes[key + b'_blocked'] += (
398 util.timer() - starttime
399 util.timer() - starttime
399 ) * 1000
400 ) * 1000
400
401
401 @contextlib.contextmanager
402 @contextlib.contextmanager
402 def uninterruptible(self):
403 def uninterruptible(self):
403 """Mark an operation as unsafe.
404 """Mark an operation as unsafe.
404
405
405 Most operations on a repository are safe to interrupt, but a
406 Most operations on a repository are safe to interrupt, but a
406 few are risky (for example repair.strip). This context manager
407 few are risky (for example repair.strip). This context manager
407 lets you advise Mercurial that something risky is happening so
408 lets you advise Mercurial that something risky is happening so
408 that control-C etc can be blocked if desired.
409 that control-C etc can be blocked if desired.
409 """
410 """
410 enabled = self.configbool(b'experimental', b'nointerrupt')
411 enabled = self.configbool(b'experimental', b'nointerrupt')
411 if enabled and self.configbool(
412 if enabled and self.configbool(
412 b'experimental', b'nointerrupt-interactiveonly'
413 b'experimental', b'nointerrupt-interactiveonly'
413 ):
414 ):
414 enabled = self.interactive()
415 enabled = self.interactive()
415 if self._uninterruptible or not enabled:
416 if self._uninterruptible or not enabled:
416 # if nointerrupt support is turned off, the process isn't
417 # if nointerrupt support is turned off, the process isn't
417 # interactive, or we're already in an uninterruptible
418 # interactive, or we're already in an uninterruptible
418 # block, do nothing.
419 # block, do nothing.
419 yield
420 yield
420 return
421 return
421
422
422 def warn():
423 def warn():
423 self.warn(_(b"shutting down cleanly\n"))
424 self.warn(_(b"shutting down cleanly\n"))
424 self.warn(
425 self.warn(
425 _(b"press ^C again to terminate immediately (dangerous)\n")
426 _(b"press ^C again to terminate immediately (dangerous)\n")
426 )
427 )
427 return True
428 return True
428
429
429 with procutil.uninterruptible(warn):
430 with procutil.uninterruptible(warn):
430 try:
431 try:
431 self._uninterruptible = True
432 self._uninterruptible = True
432 yield
433 yield
433 finally:
434 finally:
434 self._uninterruptible = False
435 self._uninterruptible = False
435
436
436 def formatter(self, topic: bytes, opts):
437 def formatter(self, topic: bytes, opts):
437 return formatter.formatter(self, self, topic, opts)
438 return formatter.formatter(self, self, topic, opts)
438
439
439 def _trusted(self, fp, f: bytes) -> bool:
440 def _trusted(self, fp, f: bytes) -> bool:
440 st = util.fstat(fp)
441 st = util.fstat(fp)
441 if util.isowner(st):
442 if util.isowner(st):
442 return True
443 return True
443
444
444 tusers, tgroups = self._trustusers, self._trustgroups
445 tusers, tgroups = self._trustusers, self._trustgroups
445 if b'*' in tusers or b'*' in tgroups:
446 if b'*' in tusers or b'*' in tgroups:
446 return True
447 return True
447
448
448 user = util.username(st.st_uid)
449 user = util.username(st.st_uid)
449 group = util.groupname(st.st_gid)
450 group = util.groupname(st.st_gid)
450 if user in tusers or group in tgroups or user == util.username():
451 if user in tusers or group in tgroups or user == util.username():
451 return True
452 return True
452
453
453 if self._reportuntrusted:
454 if self._reportuntrusted:
454 self.warn(
455 self.warn(
455 _(
456 _(
456 b'not trusting file %s from untrusted '
457 b'not trusting file %s from untrusted '
457 b'user %s, group %s\n'
458 b'user %s, group %s\n'
458 )
459 )
459 % (f, user, group)
460 % (f, user, group)
460 )
461 )
461 return False
462 return False
462
463
463 def read_resource_config(
464 def read_resource_config(
464 self, name, root=None, trust=False, sections=None, remap=None
465 self, name, root=None, trust=False, sections=None, remap=None
465 ) -> None:
466 ) -> None:
466 try:
467 try:
467 fp = resourceutil.open_resource(name[0], name[1])
468 fp = resourceutil.open_resource(name[0], name[1])
468 except IOError:
469 except IOError:
469 if not sections: # ignore unless we were looking for something
470 if not sections: # ignore unless we were looking for something
470 return
471 return
471 raise
472 raise
472
473
473 self._readconfig(
474 self._readconfig(
474 b'resource:%s.%s' % name, fp, root, trust, sections, remap
475 b'resource:%s.%s' % name, fp, root, trust, sections, remap
475 )
476 )
476
477
477 def readconfig(
478 def readconfig(
478 self, filename, root=None, trust=False, sections=None, remap=None
479 self, filename, root=None, trust=False, sections=None, remap=None
479 ) -> None:
480 ) -> None:
480 try:
481 try:
481 fp = open(filename, 'rb')
482 fp = open(filename, 'rb')
482 except IOError:
483 except IOError:
483 if not sections: # ignore unless we were looking for something
484 if not sections: # ignore unless we were looking for something
484 return
485 return
485 raise
486 raise
486
487
487 self._readconfig(filename, fp, root, trust, sections, remap)
488 self._readconfig(filename, fp, root, trust, sections, remap)
488
489
489 def _readconfig(
490 def _readconfig(
490 self, filename, fp, root=None, trust=False, sections=None, remap=None
491 self, filename, fp, root=None, trust=False, sections=None, remap=None
491 ) -> None:
492 ) -> None:
492 with fp:
493 with fp:
493 cfg = config.config()
494 cfg = config.config()
494 trusted = sections or trust or self._trusted(fp, filename)
495 trusted = sections or trust or self._trusted(fp, filename)
495
496
496 try:
497 try:
497 cfg.read(filename, fp, sections=sections, remap=remap)
498 cfg.read(filename, fp, sections=sections, remap=remap)
498 except error.ConfigError as inst:
499 except error.ConfigError as inst:
499 if trusted:
500 if trusted:
500 raise
501 raise
501 self.warn(
502 self.warn(
502 _(b'ignored %s: %s\n') % (inst.location, inst.message)
503 _(b'ignored %s: %s\n') % (inst.location, inst.message)
503 )
504 )
504
505
505 self._applyconfig(cfg, trusted, root)
506 self._applyconfig(cfg, trusted, root)
506
507
507 def applyconfig(
508 def applyconfig(
508 self, configitems: _ConfigItems, source=b"", root=None
509 self, configitems: _ConfigItems, source=b"", root=None
509 ) -> None:
510 ) -> None:
510 """Add configitems from a non-file source. Unlike with ``setconfig()``,
511 """Add configitems from a non-file source. Unlike with ``setconfig()``,
511 they can be overridden by subsequent config file reads. The items are
512 they can be overridden by subsequent config file reads. The items are
512 in the same format as ``configoverride()``, namely a dict of the
513 in the same format as ``configoverride()``, namely a dict of the
513 following structures: {(section, name) : value}
514 following structures: {(section, name) : value}
514
515
515 Typically this is used by extensions that inject themselves into the
516 Typically this is used by extensions that inject themselves into the
516 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
517 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
517 """
518 """
518 cfg = config.config()
519 cfg = config.config()
519
520
520 for (section, name), value in configitems.items():
521 for (section, name), value in configitems.items():
521 cfg.set(section, name, value, source)
522 cfg.set(section, name, value, source)
522
523
523 self._applyconfig(cfg, True, root)
524 self._applyconfig(cfg, True, root)
524
525
525 def _applyconfig(self, cfg, trusted, root) -> None:
526 def _applyconfig(self, cfg, trusted, root) -> None:
526 if self.plain():
527 if self.plain():
527 for k in (
528 for k in (
528 b'debug',
529 b'debug',
529 b'fallbackencoding',
530 b'fallbackencoding',
530 b'quiet',
531 b'quiet',
531 b'slash',
532 b'slash',
532 b'logtemplate',
533 b'logtemplate',
533 b'message-output',
534 b'message-output',
534 b'statuscopies',
535 b'statuscopies',
535 b'style',
536 b'style',
536 b'traceback',
537 b'traceback',
537 b'verbose',
538 b'verbose',
538 ):
539 ):
539 if k in cfg[b'ui']:
540 if k in cfg[b'ui']:
540 del cfg[b'ui'][k]
541 del cfg[b'ui'][k]
541 for k, v in cfg.items(b'defaults'):
542 for k, v in cfg.items(b'defaults'):
542 del cfg[b'defaults'][k]
543 del cfg[b'defaults'][k]
543 for k, v in cfg.items(b'commands'):
544 for k, v in cfg.items(b'commands'):
544 del cfg[b'commands'][k]
545 del cfg[b'commands'][k]
545 for k, v in cfg.items(b'command-templates'):
546 for k, v in cfg.items(b'command-templates'):
546 del cfg[b'command-templates'][k]
547 del cfg[b'command-templates'][k]
547 # Don't remove aliases from the configuration if in the exceptionlist
548 # Don't remove aliases from the configuration if in the exceptionlist
548 if self.plain(b'alias'):
549 if self.plain(b'alias'):
549 for k, v in cfg.items(b'alias'):
550 for k, v in cfg.items(b'alias'):
550 del cfg[b'alias'][k]
551 del cfg[b'alias'][k]
551 if self.plain(b'revsetalias'):
552 if self.plain(b'revsetalias'):
552 for k, v in cfg.items(b'revsetalias'):
553 for k, v in cfg.items(b'revsetalias'):
553 del cfg[b'revsetalias'][k]
554 del cfg[b'revsetalias'][k]
554 if self.plain(b'templatealias'):
555 if self.plain(b'templatealias'):
555 for k, v in cfg.items(b'templatealias'):
556 for k, v in cfg.items(b'templatealias'):
556 del cfg[b'templatealias'][k]
557 del cfg[b'templatealias'][k]
557
558
558 if trusted:
559 if trusted:
559 self._tcfg.update(cfg)
560 self._tcfg.update(cfg)
560 self._tcfg.update(self._ocfg)
561 self._tcfg.update(self._ocfg)
561 self._ucfg.update(cfg)
562 self._ucfg.update(cfg)
562 self._ucfg.update(self._ocfg)
563 self._ucfg.update(self._ocfg)
563
564
564 if root is None:
565 if root is None:
565 root = os.path.expanduser(b'~')
566 root = os.path.expanduser(b'~')
566 self.fixconfig(root=root)
567 self.fixconfig(root=root)
567
568
568 def fixconfig(self, root=None, section=None) -> None:
569 def fixconfig(self, root=None, section=None) -> None:
569 if section in (None, b'paths'):
570 if section in (None, b'paths'):
570 # expand vars and ~
571 # expand vars and ~
571 # translate paths relative to root (or home) into absolute paths
572 # translate paths relative to root (or home) into absolute paths
572 root = root or encoding.getcwd()
573 root = root or encoding.getcwd()
573 for c in self._tcfg, self._ucfg, self._ocfg:
574 for c in self._tcfg, self._ucfg, self._ocfg:
574 for n, p in c.items(b'paths'):
575 for n, p in c.items(b'paths'):
575 old_p = p
576 old_p = p
576 s = self.configsource(b'paths', n) or b'none'
577 s = self.configsource(b'paths', n) or b'none'
577 root_key = (n, p, s)
578 root_key = (n, p, s)
578 if root_key not in self._path_to_root:
579 if root_key not in self._path_to_root:
579 self._path_to_root[root_key] = root
580 self._path_to_root[root_key] = root
580 # Ignore sub-options.
581 # Ignore sub-options.
581 if b':' in n:
582 if b':' in n:
582 continue
583 continue
583 if not p:
584 if not p:
584 continue
585 continue
585 if b'%%' in p:
586 if b'%%' in p:
586 if s is None:
587 if s is None:
587 s = 'none'
588 s = 'none'
588 self.warn(
589 self.warn(
589 _(b"(deprecated '%%' in path %s=%s from %s)\n")
590 _(b"(deprecated '%%' in path %s=%s from %s)\n")
590 % (n, p, s)
591 % (n, p, s)
591 )
592 )
592 p = p.replace(b'%%', b'%')
593 p = p.replace(b'%%', b'%')
593 if p != old_p:
594 if p != old_p:
594 c.alter(b"paths", n, p)
595 c.alter(b"paths", n, p)
595
596
596 if section in (None, b'ui'):
597 if section in (None, b'ui'):
597 # update ui options
598 # update ui options
598 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
599 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
599 self.debugflag = self.configbool(b'ui', b'debug')
600 self.debugflag = self.configbool(b'ui', b'debug')
600 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
601 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
601 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
602 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
602 if self.verbose and self.quiet:
603 if self.verbose and self.quiet:
603 self.quiet = self.verbose = False
604 self.quiet = self.verbose = False
604 self._reportuntrusted = self.debugflag or self.configbool(
605 self._reportuntrusted = self.debugflag or self.configbool(
605 b"ui", b"report_untrusted"
606 b"ui", b"report_untrusted"
606 )
607 )
607 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
608 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
608 self.tracebackflag = self.configbool(b'ui', b'traceback')
609 self.tracebackflag = self.configbool(b'ui', b'traceback')
609 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
610 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
610
611
611 if section in (None, b'trusted'):
612 if section in (None, b'trusted'):
612 # update trust information
613 # update trust information
613 self._trustusers.update(self.configlist(b'trusted', b'users'))
614 self._trustusers.update(self.configlist(b'trusted', b'users'))
614 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
615 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
615
616
616 if section in (None, b'devel', b'ui') and self.debugflag:
617 if section in (None, b'devel', b'ui') and self.debugflag:
617 tracked = set()
618 tracked = set()
618 if self.configbool(b'devel', b'debug.extensions'):
619 if self.configbool(b'devel', b'debug.extensions'):
619 tracked.add(b'extension')
620 tracked.add(b'extension')
620 if tracked:
621 if tracked:
621 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
622 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
622 self.setlogger(b'debug', logger)
623 self.setlogger(b'debug', logger)
623
624
624 def backupconfig(self, section, item):
625 def backupconfig(self, section, item):
625 return (
626 return (
626 self._ocfg.backup(section, item),
627 self._ocfg.backup(section, item),
627 self._tcfg.backup(section, item),
628 self._tcfg.backup(section, item),
628 self._ucfg.backup(section, item),
629 self._ucfg.backup(section, item),
629 )
630 )
630
631
631 def restoreconfig(self, data) -> None:
632 def restoreconfig(self, data) -> None:
632 self._ocfg.restore(data[0])
633 self._ocfg.restore(data[0])
633 self._tcfg.restore(data[1])
634 self._tcfg.restore(data[1])
634 self._ucfg.restore(data[2])
635 self._ucfg.restore(data[2])
635
636
636 def setconfig(self, section, name, value, source=b'') -> None:
637 def setconfig(self, section, name, value, source=b'') -> None:
637 for cfg in (self._ocfg, self._tcfg, self._ucfg):
638 for cfg in (self._ocfg, self._tcfg, self._ucfg):
638 cfg.set(section, name, value, source)
639 cfg.set(section, name, value, source)
639 self.fixconfig(section=section)
640 self.fixconfig(section=section)
640 self._maybetweakdefaults()
641 self._maybetweakdefaults()
641
642
642 def _data(self, untrusted):
643 def _data(self, untrusted):
643 return untrusted and self._ucfg or self._tcfg
644 return untrusted and self._ucfg or self._tcfg
644
645
645 def configsource(self, section, name, untrusted=False):
646 def configsource(self, section, name, untrusted=False):
646 return self._data(untrusted).source(section, name)
647 return self._data(untrusted).source(section, name)
647
648
648 def config(self, section, name, default=_unset, untrusted=False):
649 def config(self, section, name, default=_unset, untrusted=False):
649 """return the plain string version of a config"""
650 """return the plain string version of a config"""
650 value = self._config(
651 value = self._config(
651 section, name, default=default, untrusted=untrusted
652 section, name, default=default, untrusted=untrusted
652 )
653 )
653 if value is _unset:
654 if value is _unset:
654 return None
655 return None
655 return value
656 return value
656
657
657 def _config(self, section, name, default=_unset, untrusted=False):
658 def _config(self, section, name, default=_unset, untrusted=False):
658 value = itemdefault = default
659 value = itemdefault = default
659 item = self._knownconfig.get(section, {}).get(name)
660 item = self._knownconfig.get(section, {}).get(name)
660 alternates = [(section, name)]
661 alternates = [(section, name)]
661
662
663 if item is not None and item.in_core_extension is not None:
664 # Only return the default for an in-core extension item if said
665 # extension is enabled
666 if item.in_core_extension in extensions.extensions(self):
667 item = None
668
662 if item is not None:
669 if item is not None:
663 alternates.extend(item.alias)
670 alternates.extend(item.alias)
664 if callable(item.default):
671 if callable(item.default):
665 itemdefault = item.default()
672 itemdefault = item.default()
666 else:
673 else:
667 itemdefault = item.default
674 itemdefault = item.default
668 else:
675 else:
669 msg = b"accessing unregistered config item: '%s.%s'"
676 msg = b"accessing unregistered config item: '%s.%s'"
670 msg %= (section, name)
677 msg %= (section, name)
671 self.develwarn(msg, 2, b'warn-config-unknown')
678 self.develwarn(msg, 2, b'warn-config-unknown')
672
679
673 if default is _unset:
680 if default is _unset:
674 if item is None:
681 if item is None:
675 value = default
682 value = default
676 elif item.default is configitems.dynamicdefault:
683 elif item.default is configitems.dynamicdefault:
677 value = None
684 value = None
678 msg = b"config item requires an explicit default value: '%s.%s'"
685 msg = b"config item requires an explicit default value: '%s.%s'"
679 msg %= (section, name)
686 msg %= (section, name)
680 self.develwarn(msg, 2, b'warn-config-default')
687 self.develwarn(msg, 2, b'warn-config-default')
681 else:
688 else:
682 value = itemdefault
689 value = itemdefault
683 elif (
690 elif (
684 item is not None
691 item is not None
685 and item.default is not configitems.dynamicdefault
692 and item.default is not configitems.dynamicdefault
686 and default != itemdefault
693 and default != itemdefault
687 ):
694 ):
688 msg = (
695 msg = (
689 b"specifying a mismatched default value for a registered "
696 b"specifying a mismatched default value for a registered "
690 b"config item: '%s.%s' '%s'"
697 b"config item: '%s.%s' '%s'"
691 )
698 )
692 msg %= (section, name, pycompat.bytestr(default))
699 msg %= (section, name, pycompat.bytestr(default))
693 self.develwarn(msg, 2, b'warn-config-default')
700 self.develwarn(msg, 2, b'warn-config-default')
694
701
695 candidates = []
702 candidates = []
696 config = self._data(untrusted)
703 config = self._data(untrusted)
697 for s, n in alternates:
704 for s, n in alternates:
698 candidate = config.get(s, n, None)
705 candidate = config.get(s, n, None)
699 if candidate is not None:
706 if candidate is not None:
700 candidates.append((s, n, candidate))
707 candidates.append((s, n, candidate))
701 if candidates:
708 if candidates:
702
709
703 def level(x):
710 def level(x):
704 return config.level(x[0], x[1])
711 return config.level(x[0], x[1])
705
712
706 value = max(candidates, key=level)[2]
713 value = max(candidates, key=level)[2]
707
714
708 if self.debugflag and not untrusted and self._reportuntrusted:
715 if self.debugflag and not untrusted and self._reportuntrusted:
709 for s, n in alternates:
716 for s, n in alternates:
710 uvalue = self._ucfg.get(s, n)
717 uvalue = self._ucfg.get(s, n)
711 if uvalue is not None and uvalue != value:
718 if uvalue is not None and uvalue != value:
712 self.debug(
719 self.debug(
713 b"ignoring untrusted configuration option "
720 b"ignoring untrusted configuration option "
714 b"%s.%s = %s\n" % (s, n, uvalue)
721 b"%s.%s = %s\n" % (s, n, uvalue)
715 )
722 )
716 return value
723 return value
717
724
718 def config_default(self, section, name):
725 def config_default(self, section, name):
719 """return the default value for a config option
726 """return the default value for a config option
720
727
721 The default is returned "raw", for example if it is a callable, the
728 The default is returned "raw", for example if it is a callable, the
722 callable was not called.
729 callable was not called.
723 """
730 """
724 item = self._knownconfig.get(section, {}).get(name)
731 item = self._knownconfig.get(section, {}).get(name)
725
732
726 if item is None:
733 if item is None:
727 raise KeyError((section, name))
734 raise KeyError((section, name))
728 return item.default
735 return item.default
729
736
730 def configsuboptions(self, section, name, default=_unset, untrusted=False):
737 def configsuboptions(self, section, name, default=_unset, untrusted=False):
731 """Get a config option and all sub-options.
738 """Get a config option and all sub-options.
732
739
733 Some config options have sub-options that are declared with the
740 Some config options have sub-options that are declared with the
734 format "key:opt = value". This method is used to return the main
741 format "key:opt = value". This method is used to return the main
735 option and all its declared sub-options.
742 option and all its declared sub-options.
736
743
737 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
744 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
738 is a dict of defined sub-options where keys and values are strings.
745 is a dict of defined sub-options where keys and values are strings.
739 """
746 """
740 main = self.config(section, name, default, untrusted=untrusted)
747 main = self.config(section, name, default, untrusted=untrusted)
741 data = self._data(untrusted)
748 data = self._data(untrusted)
742 sub = {}
749 sub = {}
743 prefix = b'%s:' % name
750 prefix = b'%s:' % name
744 for k, v in data.items(section):
751 for k, v in data.items(section):
745 if k.startswith(prefix):
752 if k.startswith(prefix):
746 sub[k[len(prefix) :]] = v
753 sub[k[len(prefix) :]] = v
747
754
748 if self.debugflag and not untrusted and self._reportuntrusted:
755 if self.debugflag and not untrusted and self._reportuntrusted:
749 for k, v in sub.items():
756 for k, v in sub.items():
750 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
757 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
751 if uvalue is not None and uvalue != v:
758 if uvalue is not None and uvalue != v:
752 self.debug(
759 self.debug(
753 b'ignoring untrusted configuration option '
760 b'ignoring untrusted configuration option '
754 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
761 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
755 )
762 )
756
763
757 return main, sub
764 return main, sub
758
765
759 def configpath(self, section, name, default=_unset, untrusted=False):
766 def configpath(self, section, name, default=_unset, untrusted=False):
760 """get a path config item, expanded relative to repo root or config
767 """get a path config item, expanded relative to repo root or config
761 file"""
768 file"""
762 v = self.config(section, name, default, untrusted)
769 v = self.config(section, name, default, untrusted)
763 if v is None:
770 if v is None:
764 return None
771 return None
765 if not os.path.isabs(v) or b"://" not in v:
772 if not os.path.isabs(v) or b"://" not in v:
766 src = self.configsource(section, name, untrusted)
773 src = self.configsource(section, name, untrusted)
767 if b':' in src:
774 if b':' in src:
768 base = os.path.dirname(src.rsplit(b':')[0])
775 base = os.path.dirname(src.rsplit(b':')[0])
769 v = os.path.join(base, os.path.expanduser(v))
776 v = os.path.join(base, os.path.expanduser(v))
770 return v
777 return v
771
778
772 def configbool(self, section, name, default=_unset, untrusted=False):
779 def configbool(self, section, name, default=_unset, untrusted=False):
773 """parse a configuration element as a boolean
780 """parse a configuration element as a boolean
774
781
775 >>> u = ui(); s = b'foo'
782 >>> u = ui(); s = b'foo'
776 >>> u.setconfig(s, b'true', b'yes')
783 >>> u.setconfig(s, b'true', b'yes')
777 >>> u.configbool(s, b'true')
784 >>> u.configbool(s, b'true')
778 True
785 True
779 >>> u.setconfig(s, b'false', b'no')
786 >>> u.setconfig(s, b'false', b'no')
780 >>> u.configbool(s, b'false')
787 >>> u.configbool(s, b'false')
781 False
788 False
782 >>> u.configbool(s, b'unknown')
789 >>> u.configbool(s, b'unknown')
783 False
790 False
784 >>> u.configbool(s, b'unknown', True)
791 >>> u.configbool(s, b'unknown', True)
785 True
792 True
786 >>> u.setconfig(s, b'invalid', b'somevalue')
793 >>> u.setconfig(s, b'invalid', b'somevalue')
787 >>> u.configbool(s, b'invalid')
794 >>> u.configbool(s, b'invalid')
788 Traceback (most recent call last):
795 Traceback (most recent call last):
789 ...
796 ...
790 ConfigError: foo.invalid is not a boolean ('somevalue')
797 ConfigError: foo.invalid is not a boolean ('somevalue')
791 """
798 """
792
799
793 v = self._config(section, name, default, untrusted=untrusted)
800 v = self._config(section, name, default, untrusted=untrusted)
794 if v is None:
801 if v is None:
795 return v
802 return v
796 if v is _unset:
803 if v is _unset:
797 if default is _unset:
804 if default is _unset:
798 return False
805 return False
799 return default
806 return default
800 if isinstance(v, bool):
807 if isinstance(v, bool):
801 return v
808 return v
802 b = stringutil.parsebool(v)
809 b = stringutil.parsebool(v)
803 if b is None:
810 if b is None:
804 raise error.ConfigError(
811 raise error.ConfigError(
805 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
812 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
806 )
813 )
807 return b
814 return b
808
815
809 def configwith(
816 def configwith(
810 self, convert, section, name, default=_unset, desc=None, untrusted=False
817 self, convert, section, name, default=_unset, desc=None, untrusted=False
811 ):
818 ):
812 """parse a configuration element with a conversion function
819 """parse a configuration element with a conversion function
813
820
814 >>> u = ui(); s = b'foo'
821 >>> u = ui(); s = b'foo'
815 >>> u.setconfig(s, b'float1', b'42')
822 >>> u.setconfig(s, b'float1', b'42')
816 >>> u.configwith(float, s, b'float1')
823 >>> u.configwith(float, s, b'float1')
817 42.0
824 42.0
818 >>> u.setconfig(s, b'float2', b'-4.25')
825 >>> u.setconfig(s, b'float2', b'-4.25')
819 >>> u.configwith(float, s, b'float2')
826 >>> u.configwith(float, s, b'float2')
820 -4.25
827 -4.25
821 >>> u.configwith(float, s, b'unknown', 7)
828 >>> u.configwith(float, s, b'unknown', 7)
822 7.0
829 7.0
823 >>> u.setconfig(s, b'invalid', b'somevalue')
830 >>> u.setconfig(s, b'invalid', b'somevalue')
824 >>> u.configwith(float, s, b'invalid')
831 >>> u.configwith(float, s, b'invalid')
825 Traceback (most recent call last):
832 Traceback (most recent call last):
826 ...
833 ...
827 ConfigError: foo.invalid is not a valid float ('somevalue')
834 ConfigError: foo.invalid is not a valid float ('somevalue')
828 >>> u.configwith(float, s, b'invalid', desc=b'womble')
835 >>> u.configwith(float, s, b'invalid', desc=b'womble')
829 Traceback (most recent call last):
836 Traceback (most recent call last):
830 ...
837 ...
831 ConfigError: foo.invalid is not a valid womble ('somevalue')
838 ConfigError: foo.invalid is not a valid womble ('somevalue')
832 """
839 """
833
840
834 v = self.config(section, name, default, untrusted)
841 v = self.config(section, name, default, untrusted)
835 if v is None:
842 if v is None:
836 return v # do not attempt to convert None
843 return v # do not attempt to convert None
837 try:
844 try:
838 return convert(v)
845 return convert(v)
839 except (ValueError, error.ParseError):
846 except (ValueError, error.ParseError):
840 if desc is None:
847 if desc is None:
841 desc = pycompat.sysbytes(convert.__name__)
848 desc = pycompat.sysbytes(convert.__name__)
842 raise error.ConfigError(
849 raise error.ConfigError(
843 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
850 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
844 )
851 )
845
852
846 def configint(self, section, name, default=_unset, untrusted=False):
853 def configint(self, section, name, default=_unset, untrusted=False):
847 """parse a configuration element as an integer
854 """parse a configuration element as an integer
848
855
849 >>> u = ui(); s = b'foo'
856 >>> u = ui(); s = b'foo'
850 >>> u.setconfig(s, b'int1', b'42')
857 >>> u.setconfig(s, b'int1', b'42')
851 >>> u.configint(s, b'int1')
858 >>> u.configint(s, b'int1')
852 42
859 42
853 >>> u.setconfig(s, b'int2', b'-42')
860 >>> u.setconfig(s, b'int2', b'-42')
854 >>> u.configint(s, b'int2')
861 >>> u.configint(s, b'int2')
855 -42
862 -42
856 >>> u.configint(s, b'unknown', 7)
863 >>> u.configint(s, b'unknown', 7)
857 7
864 7
858 >>> u.setconfig(s, b'invalid', b'somevalue')
865 >>> u.setconfig(s, b'invalid', b'somevalue')
859 >>> u.configint(s, b'invalid')
866 >>> u.configint(s, b'invalid')
860 Traceback (most recent call last):
867 Traceback (most recent call last):
861 ...
868 ...
862 ConfigError: foo.invalid is not a valid integer ('somevalue')
869 ConfigError: foo.invalid is not a valid integer ('somevalue')
863 """
870 """
864
871
865 return self.configwith(
872 return self.configwith(
866 int, section, name, default, b'integer', untrusted
873 int, section, name, default, b'integer', untrusted
867 )
874 )
868
875
869 def configbytes(self, section, name, default=_unset, untrusted=False):
876 def configbytes(self, section, name, default=_unset, untrusted=False):
870 """parse a configuration element as a quantity in bytes
877 """parse a configuration element as a quantity in bytes
871
878
872 Units can be specified as b (bytes), k or kb (kilobytes), m or
879 Units can be specified as b (bytes), k or kb (kilobytes), m or
873 mb (megabytes), g or gb (gigabytes).
880 mb (megabytes), g or gb (gigabytes).
874
881
875 >>> u = ui(); s = b'foo'
882 >>> u = ui(); s = b'foo'
876 >>> u.setconfig(s, b'val1', b'42')
883 >>> u.setconfig(s, b'val1', b'42')
877 >>> u.configbytes(s, b'val1')
884 >>> u.configbytes(s, b'val1')
878 42
885 42
879 >>> u.setconfig(s, b'val2', b'42.5 kb')
886 >>> u.setconfig(s, b'val2', b'42.5 kb')
880 >>> u.configbytes(s, b'val2')
887 >>> u.configbytes(s, b'val2')
881 43520
888 43520
882 >>> u.configbytes(s, b'unknown', b'7 MB')
889 >>> u.configbytes(s, b'unknown', b'7 MB')
883 7340032
890 7340032
884 >>> u.setconfig(s, b'invalid', b'somevalue')
891 >>> u.setconfig(s, b'invalid', b'somevalue')
885 >>> u.configbytes(s, b'invalid')
892 >>> u.configbytes(s, b'invalid')
886 Traceback (most recent call last):
893 Traceback (most recent call last):
887 ...
894 ...
888 ConfigError: foo.invalid is not a byte quantity ('somevalue')
895 ConfigError: foo.invalid is not a byte quantity ('somevalue')
889 """
896 """
890
897
891 value = self._config(section, name, default, untrusted)
898 value = self._config(section, name, default, untrusted)
892 if value is _unset:
899 if value is _unset:
893 if default is _unset:
900 if default is _unset:
894 default = 0
901 default = 0
895 value = default
902 value = default
896 if not isinstance(value, bytes):
903 if not isinstance(value, bytes):
897 return value
904 return value
898 try:
905 try:
899 return util.sizetoint(value)
906 return util.sizetoint(value)
900 except error.ParseError:
907 except error.ParseError:
901 raise error.ConfigError(
908 raise error.ConfigError(
902 _(b"%s.%s is not a byte quantity ('%s')")
909 _(b"%s.%s is not a byte quantity ('%s')")
903 % (section, name, value)
910 % (section, name, value)
904 )
911 )
905
912
906 def configlist(self, section, name, default=_unset, untrusted=False):
913 def configlist(self, section, name, default=_unset, untrusted=False):
907 """parse a configuration element as a list of comma/space separated
914 """parse a configuration element as a list of comma/space separated
908 strings
915 strings
909
916
910 >>> u = ui(); s = b'foo'
917 >>> u = ui(); s = b'foo'
911 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
918 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
912 >>> u.configlist(s, b'list1')
919 >>> u.configlist(s, b'list1')
913 ['this', 'is', 'a small', 'test']
920 ['this', 'is', 'a small', 'test']
914 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
921 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
915 >>> u.configlist(s, b'list2')
922 >>> u.configlist(s, b'list2')
916 ['this', 'is', 'a small', 'test']
923 ['this', 'is', 'a small', 'test']
917 """
924 """
918 # default is not always a list
925 # default is not always a list
919 v = self.configwith(
926 v = self.configwith(
920 stringutil.parselist, section, name, default, b'list', untrusted
927 stringutil.parselist, section, name, default, b'list', untrusted
921 )
928 )
922 if isinstance(v, bytes):
929 if isinstance(v, bytes):
923 return stringutil.parselist(v)
930 return stringutil.parselist(v)
924 elif v is None:
931 elif v is None:
925 return []
932 return []
926 return v
933 return v
927
934
928 def configdate(self, section, name, default=_unset, untrusted=False):
935 def configdate(self, section, name, default=_unset, untrusted=False):
929 """parse a configuration element as a tuple of ints
936 """parse a configuration element as a tuple of ints
930
937
931 >>> u = ui(); s = b'foo'
938 >>> u = ui(); s = b'foo'
932 >>> u.setconfig(s, b'date', b'0 0')
939 >>> u.setconfig(s, b'date', b'0 0')
933 >>> u.configdate(s, b'date')
940 >>> u.configdate(s, b'date')
934 (0, 0)
941 (0, 0)
935 """
942 """
936 if self.config(section, name, default, untrusted):
943 if self.config(section, name, default, untrusted):
937 return self.configwith(
944 return self.configwith(
938 dateutil.parsedate, section, name, default, b'date', untrusted
945 dateutil.parsedate, section, name, default, b'date', untrusted
939 )
946 )
940 if default is _unset:
947 if default is _unset:
941 return None
948 return None
942 return default
949 return default
943
950
944 def configdefault(self, section, name):
951 def configdefault(self, section, name):
945 """returns the default value of the config item"""
952 """returns the default value of the config item"""
946 item = self._knownconfig.get(section, {}).get(name)
953 item = self._knownconfig.get(section, {}).get(name)
947 itemdefault = None
954 itemdefault = None
948 if item is not None:
955 if item is not None:
949 if callable(item.default):
956 if callable(item.default):
950 itemdefault = item.default()
957 itemdefault = item.default()
951 else:
958 else:
952 itemdefault = item.default
959 itemdefault = item.default
953 return itemdefault
960 return itemdefault
954
961
955 def hasconfig(self, section, name, untrusted=False):
962 def hasconfig(self, section, name, untrusted=False):
956 return self._data(untrusted).hasitem(section, name)
963 return self._data(untrusted).hasitem(section, name)
957
964
958 def has_section(self, section, untrusted=False):
965 def has_section(self, section, untrusted=False):
959 '''tell whether section exists in config.'''
966 '''tell whether section exists in config.'''
960 return section in self._data(untrusted)
967 return section in self._data(untrusted)
961
968
962 def configitems(self, section, untrusted=False, ignoresub=False):
969 def configitems(self, section, untrusted=False, ignoresub=False):
963 items = self._data(untrusted).items(section)
970 items = self._data(untrusted).items(section)
964 if ignoresub:
971 if ignoresub:
965 items = [i for i in items if b':' not in i[0]]
972 items = [i for i in items if b':' not in i[0]]
966 if self.debugflag and not untrusted and self._reportuntrusted:
973 if self.debugflag and not untrusted and self._reportuntrusted:
967 for k, v in self._ucfg.items(section):
974 for k, v in self._ucfg.items(section):
968 if self._tcfg.get(section, k) != v:
975 if self._tcfg.get(section, k) != v:
969 self.debug(
976 self.debug(
970 b"ignoring untrusted configuration option "
977 b"ignoring untrusted configuration option "
971 b"%s.%s = %s\n" % (section, k, v)
978 b"%s.%s = %s\n" % (section, k, v)
972 )
979 )
973 return items
980 return items
974
981
975 def walkconfig(self, untrusted=False, all_known=False):
982 def walkconfig(self, untrusted=False, all_known=False):
976 defined = self._walk_config(untrusted)
983 defined = self._walk_config(untrusted)
977 if not all_known:
984 if not all_known:
978 for d in defined:
985 for d in defined:
979 yield d
986 yield d
980 return
987 return
981 known = self._walk_known()
988 known = self._walk_known()
982 current_defined = next(defined, None)
989 current_defined = next(defined, None)
983 current_known = next(known, None)
990 current_known = next(known, None)
984 while current_defined is not None or current_known is not None:
991 while current_defined is not None or current_known is not None:
985 if current_defined is None:
992 if current_defined is None:
986 yield current_known
993 yield current_known
987 current_known = next(known, None)
994 current_known = next(known, None)
988 elif current_known is None:
995 elif current_known is None:
989 yield current_defined
996 yield current_defined
990 current_defined = next(defined, None)
997 current_defined = next(defined, None)
991 elif current_known[0:2] == current_defined[0:2]:
998 elif current_known[0:2] == current_defined[0:2]:
992 yield current_defined
999 yield current_defined
993 current_defined = next(defined, None)
1000 current_defined = next(defined, None)
994 current_known = next(known, None)
1001 current_known = next(known, None)
995 elif current_known[0:2] < current_defined[0:2]:
1002 elif current_known[0:2] < current_defined[0:2]:
996 yield current_known
1003 yield current_known
997 current_known = next(known, None)
1004 current_known = next(known, None)
998 else:
1005 else:
999 yield current_defined
1006 yield current_defined
1000 current_defined = next(defined, None)
1007 current_defined = next(defined, None)
1001
1008
1002 def _walk_known(self):
1009 def _walk_known(self):
1003 for section, items in sorted(self._knownconfig.items()):
1010 for section, items in sorted(self._knownconfig.items()):
1004 for k, i in sorted(items.items()):
1011 for k, i in sorted(items.items()):
1005 # We don't have a way to display generic well, so skip them
1012 # We don't have a way to display generic well, so skip them
1006 if i.generic:
1013 if i.generic:
1007 continue
1014 continue
1008 if callable(i.default):
1015 if callable(i.default):
1009 default = i.default()
1016 default = i.default()
1010 elif i.default is configitems.dynamicdefault:
1017 elif i.default is configitems.dynamicdefault:
1011 default = b'<DYNAMIC>'
1018 default = b'<DYNAMIC>'
1012 else:
1019 else:
1013 default = i.default
1020 default = i.default
1014 yield section, i.name, default
1021 yield section, i.name, default
1015
1022
1016 def _walk_config(self, untrusted):
1023 def _walk_config(self, untrusted):
1017 cfg = self._data(untrusted)
1024 cfg = self._data(untrusted)
1018 for section in cfg.sections():
1025 for section in cfg.sections():
1019 for name, value in self.configitems(section, untrusted):
1026 for name, value in self.configitems(section, untrusted):
1020 yield section, name, value
1027 yield section, name, value
1021
1028
1022 def plain(self, feature: Optional[bytes] = None) -> bool:
1029 def plain(self, feature: Optional[bytes] = None) -> bool:
1023 """is plain mode active?
1030 """is plain mode active?
1024
1031
1025 Plain mode means that all configuration variables which affect
1032 Plain mode means that all configuration variables which affect
1026 the behavior and output of Mercurial should be
1033 the behavior and output of Mercurial should be
1027 ignored. Additionally, the output should be stable,
1034 ignored. Additionally, the output should be stable,
1028 reproducible and suitable for use in scripts or applications.
1035 reproducible and suitable for use in scripts or applications.
1029
1036
1030 The only way to trigger plain mode is by setting either the
1037 The only way to trigger plain mode is by setting either the
1031 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
1038 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
1032
1039
1033 The return value can either be
1040 The return value can either be
1034 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
1041 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
1035 - False if feature is disabled by default and not included in HGPLAIN
1042 - False if feature is disabled by default and not included in HGPLAIN
1036 - True otherwise
1043 - True otherwise
1037 """
1044 """
1038 if (
1045 if (
1039 b'HGPLAIN' not in encoding.environ
1046 b'HGPLAIN' not in encoding.environ
1040 and b'HGPLAINEXCEPT' not in encoding.environ
1047 and b'HGPLAINEXCEPT' not in encoding.environ
1041 ):
1048 ):
1042 return False
1049 return False
1043 exceptions = (
1050 exceptions = (
1044 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
1051 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
1045 )
1052 )
1046 # TODO: add support for HGPLAIN=+feature,-feature syntax
1053 # TODO: add support for HGPLAIN=+feature,-feature syntax
1047 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
1054 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
1048 b','
1055 b','
1049 ):
1056 ):
1050 exceptions.append(b'strictflags')
1057 exceptions.append(b'strictflags')
1051 if feature and exceptions:
1058 if feature and exceptions:
1052 return feature not in exceptions
1059 return feature not in exceptions
1053 return True
1060 return True
1054
1061
1055 def username(self, acceptempty=False):
1062 def username(self, acceptempty=False):
1056 """Return default username to be used in commits.
1063 """Return default username to be used in commits.
1057
1064
1058 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
1065 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
1059 and stop searching if one of these is set.
1066 and stop searching if one of these is set.
1060 If not found and acceptempty is True, returns None.
1067 If not found and acceptempty is True, returns None.
1061 If not found and ui.askusername is True, ask the user, else use
1068 If not found and ui.askusername is True, ask the user, else use
1062 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
1069 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
1063 If no username could be found, raise an Abort error.
1070 If no username could be found, raise an Abort error.
1064 """
1071 """
1065 user = encoding.environ.get(b"HGUSER")
1072 user = encoding.environ.get(b"HGUSER")
1066 if user is None:
1073 if user is None:
1067 user = self.config(b"ui", b"username")
1074 user = self.config(b"ui", b"username")
1068 if user is not None:
1075 if user is not None:
1069 user = os.path.expandvars(user)
1076 user = os.path.expandvars(user)
1070 if user is None:
1077 if user is None:
1071 user = encoding.environ.get(b"EMAIL")
1078 user = encoding.environ.get(b"EMAIL")
1072 if user is None and acceptempty:
1079 if user is None and acceptempty:
1073 return user
1080 return user
1074 if user is None and self.configbool(b"ui", b"askusername"):
1081 if user is None and self.configbool(b"ui", b"askusername"):
1075 user = self.prompt(_(b"enter a commit username:"), default=None)
1082 user = self.prompt(_(b"enter a commit username:"), default=None)
1076 if user is None and not self.interactive():
1083 if user is None and not self.interactive():
1077 try:
1084 try:
1078 user = b'%s@%s' % (
1085 user = b'%s@%s' % (
1079 procutil.getuser(),
1086 procutil.getuser(),
1080 encoding.strtolocal(socket.getfqdn()),
1087 encoding.strtolocal(socket.getfqdn()),
1081 )
1088 )
1082 self.warn(_(b"no username found, using '%s' instead\n") % user)
1089 self.warn(_(b"no username found, using '%s' instead\n") % user)
1083 except KeyError:
1090 except KeyError:
1084 pass
1091 pass
1085 if not user:
1092 if not user:
1086 raise error.Abort(
1093 raise error.Abort(
1087 _(b'no username supplied'),
1094 _(b'no username supplied'),
1088 hint=_(b"use 'hg config --edit' " b'to set your username'),
1095 hint=_(b"use 'hg config --edit' " b'to set your username'),
1089 )
1096 )
1090 if b"\n" in user:
1097 if b"\n" in user:
1091 raise error.Abort(
1098 raise error.Abort(
1092 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1099 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1093 )
1100 )
1094 return user
1101 return user
1095
1102
1096 def shortuser(self, user: bytes) -> bytes:
1103 def shortuser(self, user: bytes) -> bytes:
1097 """Return a short representation of a user name or email address."""
1104 """Return a short representation of a user name or email address."""
1098 if not self.verbose:
1105 if not self.verbose:
1099 user = stringutil.shortuser(user)
1106 user = stringutil.shortuser(user)
1100 return user
1107 return user
1101
1108
1102 @util.propertycache
1109 @util.propertycache
1103 def paths(self):
1110 def paths(self):
1104 return urlutil.paths(self)
1111 return urlutil.paths(self)
1105
1112
1106 @property
1113 @property
1107 def fout(self):
1114 def fout(self):
1108 return self._fout
1115 return self._fout
1109
1116
1110 @util.propertycache
1117 @util.propertycache
1111 def _fout_is_a_tty(self):
1118 def _fout_is_a_tty(self):
1112 self._isatty(self._fout)
1119 self._isatty(self._fout)
1113
1120
1114 @fout.setter
1121 @fout.setter
1115 def fout(self, f):
1122 def fout(self, f):
1116 self._fout = f
1123 self._fout = f
1117 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1124 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1118 if '_fout_is_a_tty' in vars(self):
1125 if '_fout_is_a_tty' in vars(self):
1119 del self._fout_is_a_tty
1126 del self._fout_is_a_tty
1120
1127
1121 @property
1128 @property
1122 def ferr(self):
1129 def ferr(self):
1123 return self._ferr
1130 return self._ferr
1124
1131
1125 @ferr.setter
1132 @ferr.setter
1126 def ferr(self, f):
1133 def ferr(self, f):
1127 self._ferr = f
1134 self._ferr = f
1128 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1135 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1129
1136
1130 @property
1137 @property
1131 def fin(self):
1138 def fin(self):
1132 return self._fin
1139 return self._fin
1133
1140
1134 @fin.setter
1141 @fin.setter
1135 def fin(self, f):
1142 def fin(self, f):
1136 self._fin = f
1143 self._fin = f
1137
1144
1138 @property
1145 @property
1139 def fmsg(self):
1146 def fmsg(self):
1140 """Stream dedicated for status/error messages; may be None if
1147 """Stream dedicated for status/error messages; may be None if
1141 fout/ferr are used"""
1148 fout/ferr are used"""
1142 return self._fmsg
1149 return self._fmsg
1143
1150
1144 @fmsg.setter
1151 @fmsg.setter
1145 def fmsg(self, f):
1152 def fmsg(self, f):
1146 self._fmsg = f
1153 self._fmsg = f
1147 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1154 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1148
1155
1149 @contextlib.contextmanager
1156 @contextlib.contextmanager
1150 def silent(
1157 def silent(
1151 self, error: bool = False, subproc: bool = False, labeled: bool = False
1158 self, error: bool = False, subproc: bool = False, labeled: bool = False
1152 ):
1159 ):
1153 self.pushbuffer(error=error, subproc=subproc, labeled=labeled)
1160 self.pushbuffer(error=error, subproc=subproc, labeled=labeled)
1154 try:
1161 try:
1155 yield
1162 yield
1156 finally:
1163 finally:
1157 self.popbuffer()
1164 self.popbuffer()
1158
1165
1159 def pushbuffer(
1166 def pushbuffer(
1160 self, error: bool = False, subproc: bool = False, labeled: bool = False
1167 self, error: bool = False, subproc: bool = False, labeled: bool = False
1161 ) -> None:
1168 ) -> None:
1162 """install a buffer to capture standard output of the ui object
1169 """install a buffer to capture standard output of the ui object
1163
1170
1164 If error is True, the error output will be captured too.
1171 If error is True, the error output will be captured too.
1165
1172
1166 If subproc is True, output from subprocesses (typically hooks) will be
1173 If subproc is True, output from subprocesses (typically hooks) will be
1167 captured too.
1174 captured too.
1168
1175
1169 If labeled is True, any labels associated with buffered
1176 If labeled is True, any labels associated with buffered
1170 output will be handled. By default, this has no effect
1177 output will be handled. By default, this has no effect
1171 on the output returned, but extensions and GUI tools may
1178 on the output returned, but extensions and GUI tools may
1172 handle this argument and returned styled output. If output
1179 handle this argument and returned styled output. If output
1173 is being buffered so it can be captured and parsed or
1180 is being buffered so it can be captured and parsed or
1174 processed, labeled should not be set to True.
1181 processed, labeled should not be set to True.
1175 """
1182 """
1176 self._buffers.append([])
1183 self._buffers.append([])
1177 self._bufferstates.append((error, subproc, labeled))
1184 self._bufferstates.append((error, subproc, labeled))
1178 self._bufferapplylabels = labeled
1185 self._bufferapplylabels = labeled
1179
1186
1180 def popbuffer(self) -> bytes:
1187 def popbuffer(self) -> bytes:
1181 '''pop the last buffer and return the buffered output'''
1188 '''pop the last buffer and return the buffered output'''
1182 self._bufferstates.pop()
1189 self._bufferstates.pop()
1183 if self._bufferstates:
1190 if self._bufferstates:
1184 self._bufferapplylabels = self._bufferstates[-1][2]
1191 self._bufferapplylabels = self._bufferstates[-1][2]
1185 else:
1192 else:
1186 self._bufferapplylabels = None
1193 self._bufferapplylabels = None
1187
1194
1188 return b"".join(self._buffers.pop())
1195 return b"".join(self._buffers.pop())
1189
1196
1190 def _isbuffered(self, dest) -> bool:
1197 def _isbuffered(self, dest) -> bool:
1191 if dest is self._fout:
1198 if dest is self._fout:
1192 return bool(self._buffers)
1199 return bool(self._buffers)
1193 if dest is self._ferr:
1200 if dest is self._ferr:
1194 return bool(self._bufferstates and self._bufferstates[-1][0])
1201 return bool(self._bufferstates and self._bufferstates[-1][0])
1195 return False
1202 return False
1196
1203
1197 def canwritewithoutlabels(self) -> bool:
1204 def canwritewithoutlabels(self) -> bool:
1198 '''check if write skips the label'''
1205 '''check if write skips the label'''
1199 if self._buffers and not self._bufferapplylabels:
1206 if self._buffers and not self._bufferapplylabels:
1200 return True
1207 return True
1201 return self._colormode is None
1208 return self._colormode is None
1202
1209
1203 def canbatchlabeledwrites(self) -> bool:
1210 def canbatchlabeledwrites(self) -> bool:
1204 '''check if write calls with labels are batchable'''
1211 '''check if write calls with labels are batchable'''
1205 # Windows color printing is special, see ``write``.
1212 # Windows color printing is special, see ``write``.
1206 return self._colormode != b'win32'
1213 return self._colormode != b'win32'
1207
1214
1208 def write(self, *args: bytes, **opts: _MsgOpts) -> None:
1215 def write(self, *args: bytes, **opts: _MsgOpts) -> None:
1209 """write args to output
1216 """write args to output
1210
1217
1211 By default, this method simply writes to the buffer or stdout.
1218 By default, this method simply writes to the buffer or stdout.
1212 Color mode can be set on the UI class to have the output decorated
1219 Color mode can be set on the UI class to have the output decorated
1213 with color modifier before being written to stdout.
1220 with color modifier before being written to stdout.
1214
1221
1215 The color used is controlled by an optional keyword argument, "label".
1222 The color used is controlled by an optional keyword argument, "label".
1216 This should be a string containing label names separated by space.
1223 This should be a string containing label names separated by space.
1217 Label names take the form of "topic.type". For example, ui.debug()
1224 Label names take the form of "topic.type". For example, ui.debug()
1218 issues a label of "ui.debug".
1225 issues a label of "ui.debug".
1219
1226
1220 Progress reports via stderr are normally cleared before writing as
1227 Progress reports via stderr are normally cleared before writing as
1221 stdout and stderr go to the same terminal. This can be skipped with
1228 stdout and stderr go to the same terminal. This can be skipped with
1222 the optional keyword argument "keepprogressbar". The progress bar
1229 the optional keyword argument "keepprogressbar". The progress bar
1223 will continue to occupy a partial line on stderr in that case.
1230 will continue to occupy a partial line on stderr in that case.
1224 This functionality is intended when Mercurial acts as data source
1231 This functionality is intended when Mercurial acts as data source
1225 in a pipe.
1232 in a pipe.
1226
1233
1227 When labeling output for a specific command, a label of
1234 When labeling output for a specific command, a label of
1228 "cmdname.type" is recommended. For example, status issues
1235 "cmdname.type" is recommended. For example, status issues
1229 a label of "status.modified" for modified files.
1236 a label of "status.modified" for modified files.
1230 """
1237 """
1231 dest = self._fout
1238 dest = self._fout
1232
1239
1233 # inlined _write() for speed
1240 # inlined _write() for speed
1234 if self._buffers:
1241 if self._buffers:
1235 label = opts.get('label', b'')
1242 label = opts.get('label', b'')
1236 if label and self._bufferapplylabels:
1243 if label and self._bufferapplylabels:
1237 self._buffers[-1].extend(self.label(a, label) for a in args)
1244 self._buffers[-1].extend(self.label(a, label) for a in args)
1238 else:
1245 else:
1239 self._buffers[-1].extend(args)
1246 self._buffers[-1].extend(args)
1240 return
1247 return
1241
1248
1242 # inlined _writenobuf() for speed
1249 # inlined _writenobuf() for speed
1243 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1250 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1244 self._progclear()
1251 self._progclear()
1245 msg = b''.join(args)
1252 msg = b''.join(args)
1246
1253
1247 # opencode timeblockedsection because this is a critical path
1254 # opencode timeblockedsection because this is a critical path
1248 starttime = util.timer()
1255 starttime = util.timer()
1249 try:
1256 try:
1250 if self._colormode == b'win32':
1257 if self._colormode == b'win32':
1251 # windows color printing is its own can of crab, defer to
1258 # windows color printing is its own can of crab, defer to
1252 # the color module and that is it.
1259 # the color module and that is it.
1253 color.win32print(self, dest.write, msg, **opts)
1260 color.win32print(self, dest.write, msg, **opts)
1254 else:
1261 else:
1255 if self._colormode is not None:
1262 if self._colormode is not None:
1256 label = opts.get('label', b'')
1263 label = opts.get('label', b'')
1257 msg = self.label(msg, label)
1264 msg = self.label(msg, label)
1258 dest.write(msg)
1265 dest.write(msg)
1259 except IOError as err:
1266 except IOError as err:
1260 raise error.StdioError(err)
1267 raise error.StdioError(err)
1261 finally:
1268 finally:
1262 self._blockedtimes[b'stdio_blocked'] += (
1269 self._blockedtimes[b'stdio_blocked'] += (
1263 util.timer() - starttime
1270 util.timer() - starttime
1264 ) * 1000
1271 ) * 1000
1265
1272
1266 def write_err(self, *args: bytes, **opts: _MsgOpts) -> None:
1273 def write_err(self, *args: bytes, **opts: _MsgOpts) -> None:
1267 self._write(self._ferr, *args, **opts)
1274 self._write(self._ferr, *args, **opts)
1268
1275
1269 def _write(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1276 def _write(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1270 # update write() as well if you touch this code
1277 # update write() as well if you touch this code
1271 if self._isbuffered(dest):
1278 if self._isbuffered(dest):
1272 label = opts.get('label', b'')
1279 label = opts.get('label', b'')
1273 if label and self._bufferapplylabels:
1280 if label and self._bufferapplylabels:
1274 self._buffers[-1].extend(self.label(a, label) for a in args)
1281 self._buffers[-1].extend(self.label(a, label) for a in args)
1275 else:
1282 else:
1276 self._buffers[-1].extend(args)
1283 self._buffers[-1].extend(args)
1277 else:
1284 else:
1278 self._writenobuf(dest, *args, **opts)
1285 self._writenobuf(dest, *args, **opts)
1279
1286
1280 def _writenobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1287 def _writenobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1281 # update write() as well if you touch this code
1288 # update write() as well if you touch this code
1282 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1289 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1283 self._progclear()
1290 self._progclear()
1284 msg = b''.join(args)
1291 msg = b''.join(args)
1285
1292
1286 # opencode timeblockedsection because this is a critical path
1293 # opencode timeblockedsection because this is a critical path
1287 starttime = util.timer()
1294 starttime = util.timer()
1288 try:
1295 try:
1289 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1296 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1290 self._fout.flush()
1297 self._fout.flush()
1291 if getattr(dest, 'structured', False):
1298 if getattr(dest, 'structured', False):
1292 # channel for machine-readable output with metadata, where
1299 # channel for machine-readable output with metadata, where
1293 # no extra colorization is necessary.
1300 # no extra colorization is necessary.
1294 dest.write(msg, **opts)
1301 dest.write(msg, **opts)
1295 elif self._colormode == b'win32':
1302 elif self._colormode == b'win32':
1296 # windows color printing is its own can of crab, defer to
1303 # windows color printing is its own can of crab, defer to
1297 # the color module and that is it.
1304 # the color module and that is it.
1298 color.win32print(self, dest.write, msg, **opts)
1305 color.win32print(self, dest.write, msg, **opts)
1299 else:
1306 else:
1300 if self._colormode is not None:
1307 if self._colormode is not None:
1301 label = opts.get('label', b'')
1308 label = opts.get('label', b'')
1302 msg = self.label(msg, label)
1309 msg = self.label(msg, label)
1303 dest.write(msg)
1310 dest.write(msg)
1304 # stderr may be buffered under win32 when redirected to files,
1311 # stderr may be buffered under win32 when redirected to files,
1305 # including stdout.
1312 # including stdout.
1306 if dest is self._ferr and not getattr(dest, 'closed', False):
1313 if dest is self._ferr and not getattr(dest, 'closed', False):
1307 dest.flush()
1314 dest.flush()
1308 except IOError as err:
1315 except IOError as err:
1309 if dest is self._ferr and err.errno in (
1316 if dest is self._ferr and err.errno in (
1310 errno.EPIPE,
1317 errno.EPIPE,
1311 errno.EIO,
1318 errno.EIO,
1312 errno.EBADF,
1319 errno.EBADF,
1313 ):
1320 ):
1314 # no way to report the error, so ignore it
1321 # no way to report the error, so ignore it
1315 return
1322 return
1316 raise error.StdioError(err)
1323 raise error.StdioError(err)
1317 finally:
1324 finally:
1318 self._blockedtimes[b'stdio_blocked'] += (
1325 self._blockedtimes[b'stdio_blocked'] += (
1319 util.timer() - starttime
1326 util.timer() - starttime
1320 ) * 1000
1327 ) * 1000
1321
1328
1322 def _writemsg(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1329 def _writemsg(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1323 timestamp = self.showtimestamp and opts.get('type') in {
1330 timestamp = self.showtimestamp and opts.get('type') in {
1324 b'debug',
1331 b'debug',
1325 b'error',
1332 b'error',
1326 b'note',
1333 b'note',
1327 b'status',
1334 b'status',
1328 b'warning',
1335 b'warning',
1329 }
1336 }
1330 if timestamp:
1337 if timestamp:
1331 args = (
1338 args = (
1332 b'[%s] '
1339 b'[%s] '
1333 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1340 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1334 ) + args
1341 ) + args
1335 _writemsgwith(self._write, dest, *args, **opts)
1342 _writemsgwith(self._write, dest, *args, **opts)
1336 if timestamp:
1343 if timestamp:
1337 dest.flush()
1344 dest.flush()
1338
1345
1339 def _writemsgnobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1346 def _writemsgnobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1340 _writemsgwith(self._writenobuf, dest, *args, **opts)
1347 _writemsgwith(self._writenobuf, dest, *args, **opts)
1341
1348
1342 def flush(self) -> None:
1349 def flush(self) -> None:
1343 # opencode timeblockedsection because this is a critical path
1350 # opencode timeblockedsection because this is a critical path
1344 starttime = util.timer()
1351 starttime = util.timer()
1345 try:
1352 try:
1346 try:
1353 try:
1347 self._fout.flush()
1354 self._fout.flush()
1348 except IOError as err:
1355 except IOError as err:
1349 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1356 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1350 raise error.StdioError(err)
1357 raise error.StdioError(err)
1351 finally:
1358 finally:
1352 try:
1359 try:
1353 self._ferr.flush()
1360 self._ferr.flush()
1354 except IOError as err:
1361 except IOError as err:
1355 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1362 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1356 raise error.StdioError(err)
1363 raise error.StdioError(err)
1357 finally:
1364 finally:
1358 self._blockedtimes[b'stdio_blocked'] += (
1365 self._blockedtimes[b'stdio_blocked'] += (
1359 util.timer() - starttime
1366 util.timer() - starttime
1360 ) * 1000
1367 ) * 1000
1361
1368
1362 def _isatty(self, fh) -> bool:
1369 def _isatty(self, fh) -> bool:
1363 if self.configbool(b'ui', b'nontty'):
1370 if self.configbool(b'ui', b'nontty'):
1364 return False
1371 return False
1365 return procutil.isatty(fh)
1372 return procutil.isatty(fh)
1366
1373
1367 def protectfinout(self):
1374 def protectfinout(self):
1368 """Duplicate ui streams and redirect original if they are stdio
1375 """Duplicate ui streams and redirect original if they are stdio
1369
1376
1370 Returns (fin, fout) which point to the original ui fds, but may be
1377 Returns (fin, fout) which point to the original ui fds, but may be
1371 copy of them. The returned streams can be considered "owned" in that
1378 copy of them. The returned streams can be considered "owned" in that
1372 print(), exec(), etc. never reach to them.
1379 print(), exec(), etc. never reach to them.
1373 """
1380 """
1374 if self._finoutredirected:
1381 if self._finoutredirected:
1375 # if already redirected, protectstdio() would just create another
1382 # if already redirected, protectstdio() would just create another
1376 # nullfd pair, which is equivalent to returning self._fin/_fout.
1383 # nullfd pair, which is equivalent to returning self._fin/_fout.
1377 return self._fin, self._fout
1384 return self._fin, self._fout
1378 fin, fout = procutil.protectstdio(self._fin, self._fout)
1385 fin, fout = procutil.protectstdio(self._fin, self._fout)
1379 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1386 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1380 return fin, fout
1387 return fin, fout
1381
1388
1382 def restorefinout(self, fin, fout):
1389 def restorefinout(self, fin, fout):
1383 """Restore ui streams from possibly duplicated (fin, fout)"""
1390 """Restore ui streams from possibly duplicated (fin, fout)"""
1384 if (fin, fout) == (self._fin, self._fout):
1391 if (fin, fout) == (self._fin, self._fout):
1385 return
1392 return
1386 procutil.restorestdio(self._fin, self._fout, fin, fout)
1393 procutil.restorestdio(self._fin, self._fout, fin, fout)
1387 # protectfinout() won't create more than one duplicated streams,
1394 # protectfinout() won't create more than one duplicated streams,
1388 # so we can just turn the redirection flag off.
1395 # so we can just turn the redirection flag off.
1389 self._finoutredirected = False
1396 self._finoutredirected = False
1390
1397
1391 @contextlib.contextmanager
1398 @contextlib.contextmanager
1392 def protectedfinout(self):
1399 def protectedfinout(self):
1393 """Run code block with protected standard streams"""
1400 """Run code block with protected standard streams"""
1394 fin, fout = self.protectfinout()
1401 fin, fout = self.protectfinout()
1395 try:
1402 try:
1396 yield fin, fout
1403 yield fin, fout
1397 finally:
1404 finally:
1398 self.restorefinout(fin, fout)
1405 self.restorefinout(fin, fout)
1399
1406
1400 def disablepager(self) -> None:
1407 def disablepager(self) -> None:
1401 self._disablepager = True
1408 self._disablepager = True
1402
1409
1403 def pager(self, command: bytes) -> None:
1410 def pager(self, command: bytes) -> None:
1404 """Start a pager for subsequent command output.
1411 """Start a pager for subsequent command output.
1405
1412
1406 Commands which produce a long stream of output should call
1413 Commands which produce a long stream of output should call
1407 this function to activate the user's preferred pagination
1414 this function to activate the user's preferred pagination
1408 mechanism (which may be no pager). Calling this function
1415 mechanism (which may be no pager). Calling this function
1409 precludes any future use of interactive functionality, such as
1416 precludes any future use of interactive functionality, such as
1410 prompting the user or activating curses.
1417 prompting the user or activating curses.
1411
1418
1412 Args:
1419 Args:
1413 command: The full, non-aliased name of the command. That is, "log"
1420 command: The full, non-aliased name of the command. That is, "log"
1414 not "history, "summary" not "summ", etc.
1421 not "history, "summary" not "summ", etc.
1415 """
1422 """
1416 if self._disablepager or self.pageractive:
1423 if self._disablepager or self.pageractive:
1417 # how pager should do is already determined
1424 # how pager should do is already determined
1418 return
1425 return
1419
1426
1420 if not command.startswith(b'internal-always-') and (
1427 if not command.startswith(b'internal-always-') and (
1421 # explicit --pager=on (= 'internal-always-' prefix) should
1428 # explicit --pager=on (= 'internal-always-' prefix) should
1422 # take precedence over disabling factors below
1429 # take precedence over disabling factors below
1423 command in self.configlist(b'pager', b'ignore')
1430 command in self.configlist(b'pager', b'ignore')
1424 or not self.configbool(b'ui', b'paginate')
1431 or not self.configbool(b'ui', b'paginate')
1425 or not self.configbool(b'pager', b'attend-' + command, True)
1432 or not self.configbool(b'pager', b'attend-' + command, True)
1426 or encoding.environ.get(b'TERM') == b'dumb'
1433 or encoding.environ.get(b'TERM') == b'dumb'
1427 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1434 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1428 # formatted() will need some adjustment.
1435 # formatted() will need some adjustment.
1429 or not self.formatted()
1436 or not self.formatted()
1430 or self.plain()
1437 or self.plain()
1431 or self._buffers
1438 or self._buffers
1432 # TODO: expose debugger-enabled on the UI object
1439 # TODO: expose debugger-enabled on the UI object
1433 or b'--debugger' in pycompat.sysargv
1440 or b'--debugger' in pycompat.sysargv
1434 ):
1441 ):
1435 # We only want to paginate if the ui appears to be
1442 # We only want to paginate if the ui appears to be
1436 # interactive, the user didn't say HGPLAIN or
1443 # interactive, the user didn't say HGPLAIN or
1437 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1444 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1438 return
1445 return
1439
1446
1440 # py2exe doesn't appear to be able to use legacy I/O, and nothing is
1447 # py2exe doesn't appear to be able to use legacy I/O, and nothing is
1441 # output to the pager for paged commands. Piping to `more` in cmd.exe
1448 # output to the pager for paged commands. Piping to `more` in cmd.exe
1442 # works, but is easy to forget. Just disable pager for py2exe, but
1449 # works, but is easy to forget. Just disable pager for py2exe, but
1443 # leave it working for pyoxidizer and exewrapper builds.
1450 # leave it working for pyoxidizer and exewrapper builds.
1444 if pycompat.iswindows and getattr(sys, "frozen", None) == "console_exe":
1451 if pycompat.iswindows and getattr(sys, "frozen", None) == "console_exe":
1445 self.debug(b"pager is unavailable with py2exe packaging\n")
1452 self.debug(b"pager is unavailable with py2exe packaging\n")
1446 return
1453 return
1447
1454
1448 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1455 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1449 if not pagercmd:
1456 if not pagercmd:
1450 return
1457 return
1451
1458
1452 pagerenv = {}
1459 pagerenv = {}
1453 for name, value in rcutil.defaultpagerenv().items():
1460 for name, value in rcutil.defaultpagerenv().items():
1454 if name not in encoding.environ:
1461 if name not in encoding.environ:
1455 pagerenv[name] = value
1462 pagerenv[name] = value
1456
1463
1457 self.debug(
1464 self.debug(
1458 b'starting pager for command %s\n' % stringutil.pprint(command)
1465 b'starting pager for command %s\n' % stringutil.pprint(command)
1459 )
1466 )
1460 self.flush()
1467 self.flush()
1461
1468
1462 wasformatted = self.formatted()
1469 wasformatted = self.formatted()
1463 if util.safehasattr(signal, b"SIGPIPE"):
1470 if util.safehasattr(signal, b"SIGPIPE"):
1464 signal.signal(signal.SIGPIPE, _catchterm)
1471 signal.signal(signal.SIGPIPE, _catchterm)
1465 if self._runpager(pagercmd, pagerenv):
1472 if self._runpager(pagercmd, pagerenv):
1466 self.pageractive = True
1473 self.pageractive = True
1467 # Preserve the formatted-ness of the UI. This is important
1474 # Preserve the formatted-ness of the UI. This is important
1468 # because we mess with stdout, which might confuse
1475 # because we mess with stdout, which might confuse
1469 # auto-detection of things being formatted.
1476 # auto-detection of things being formatted.
1470 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1477 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1471 self.setconfig(b'ui', b'interactive', False, b'pager')
1478 self.setconfig(b'ui', b'interactive', False, b'pager')
1472
1479
1473 # If pagermode differs from color.mode, reconfigure color now that
1480 # If pagermode differs from color.mode, reconfigure color now that
1474 # pageractive is set.
1481 # pageractive is set.
1475 cm = self._colormode
1482 cm = self._colormode
1476 if cm != self.config(b'color', b'pagermode', cm):
1483 if cm != self.config(b'color', b'pagermode', cm):
1477 color.setup(self)
1484 color.setup(self)
1478 else:
1485 else:
1479 # If the pager can't be spawned in dispatch when --pager=on is
1486 # If the pager can't be spawned in dispatch when --pager=on is
1480 # given, don't try again when the command runs, to avoid a duplicate
1487 # given, don't try again when the command runs, to avoid a duplicate
1481 # warning about a missing pager command.
1488 # warning about a missing pager command.
1482 self.disablepager()
1489 self.disablepager()
1483
1490
1484 def _runpager(self, command: bytes, env=None) -> bool:
1491 def _runpager(self, command: bytes, env=None) -> bool:
1485 """Actually start the pager and set up file descriptors.
1492 """Actually start the pager and set up file descriptors.
1486
1493
1487 This is separate in part so that extensions (like chg) can
1494 This is separate in part so that extensions (like chg) can
1488 override how a pager is invoked.
1495 override how a pager is invoked.
1489 """
1496 """
1490 if command == b'cat':
1497 if command == b'cat':
1491 # Save ourselves some work.
1498 # Save ourselves some work.
1492 return False
1499 return False
1493 # If the command doesn't contain any of these characters, we
1500 # If the command doesn't contain any of these characters, we
1494 # assume it's a binary and exec it directly. This means for
1501 # assume it's a binary and exec it directly. This means for
1495 # simple pager command configurations, we can degrade
1502 # simple pager command configurations, we can degrade
1496 # gracefully and tell the user about their broken pager.
1503 # gracefully and tell the user about their broken pager.
1497 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1504 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1498
1505
1499 if pycompat.iswindows and not shell:
1506 if pycompat.iswindows and not shell:
1500 # Window's built-in `more` cannot be invoked with shell=False, but
1507 # Window's built-in `more` cannot be invoked with shell=False, but
1501 # its `more.com` can. Hide this implementation detail from the
1508 # its `more.com` can. Hide this implementation detail from the
1502 # user so we can also get sane bad PAGER behavior. MSYS has
1509 # user so we can also get sane bad PAGER behavior. MSYS has
1503 # `more.exe`, so do a cmd.exe style resolution of the executable to
1510 # `more.exe`, so do a cmd.exe style resolution of the executable to
1504 # determine which one to use.
1511 # determine which one to use.
1505 fullcmd = procutil.findexe(command)
1512 fullcmd = procutil.findexe(command)
1506 if not fullcmd:
1513 if not fullcmd:
1507 self.warn(
1514 self.warn(
1508 _(b"missing pager command '%s', skipping pager\n") % command
1515 _(b"missing pager command '%s', skipping pager\n") % command
1509 )
1516 )
1510 return False
1517 return False
1511
1518
1512 command = fullcmd
1519 command = fullcmd
1513
1520
1514 try:
1521 try:
1515 pager = subprocess.Popen(
1522 pager = subprocess.Popen(
1516 procutil.tonativestr(command),
1523 procutil.tonativestr(command),
1517 shell=shell,
1524 shell=shell,
1518 bufsize=-1,
1525 bufsize=-1,
1519 close_fds=procutil.closefds,
1526 close_fds=procutil.closefds,
1520 stdin=subprocess.PIPE,
1527 stdin=subprocess.PIPE,
1521 stdout=procutil.stdout,
1528 stdout=procutil.stdout,
1522 stderr=procutil.stderr,
1529 stderr=procutil.stderr,
1523 env=procutil.tonativeenv(procutil.shellenviron(env)),
1530 env=procutil.tonativeenv(procutil.shellenviron(env)),
1524 )
1531 )
1525 except FileNotFoundError:
1532 except FileNotFoundError:
1526 if not shell:
1533 if not shell:
1527 self.warn(
1534 self.warn(
1528 _(b"missing pager command '%s', skipping pager\n") % command
1535 _(b"missing pager command '%s', skipping pager\n") % command
1529 )
1536 )
1530 return False
1537 return False
1531 raise
1538 raise
1532
1539
1533 # back up original file descriptors
1540 # back up original file descriptors
1534 stdoutfd = os.dup(procutil.stdout.fileno())
1541 stdoutfd = os.dup(procutil.stdout.fileno())
1535 stderrfd = os.dup(procutil.stderr.fileno())
1542 stderrfd = os.dup(procutil.stderr.fileno())
1536
1543
1537 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1544 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1538 if self._isatty(procutil.stderr):
1545 if self._isatty(procutil.stderr):
1539 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1546 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1540
1547
1541 @self.atexit
1548 @self.atexit
1542 def killpager():
1549 def killpager():
1543 if util.safehasattr(signal, b"SIGINT"):
1550 if util.safehasattr(signal, b"SIGINT"):
1544 signal.signal(signal.SIGINT, signal.SIG_IGN)
1551 signal.signal(signal.SIGINT, signal.SIG_IGN)
1545 # restore original fds, closing pager.stdin copies in the process
1552 # restore original fds, closing pager.stdin copies in the process
1546 os.dup2(stdoutfd, procutil.stdout.fileno())
1553 os.dup2(stdoutfd, procutil.stdout.fileno())
1547 os.dup2(stderrfd, procutil.stderr.fileno())
1554 os.dup2(stderrfd, procutil.stderr.fileno())
1548 pager.stdin.close()
1555 pager.stdin.close()
1549 pager.wait()
1556 pager.wait()
1550
1557
1551 return True
1558 return True
1552
1559
1553 @property
1560 @property
1554 def _exithandlers(self):
1561 def _exithandlers(self):
1555 return _reqexithandlers
1562 return _reqexithandlers
1556
1563
1557 def atexit(self, func, *args, **kwargs):
1564 def atexit(self, func, *args, **kwargs):
1558 """register a function to run after dispatching a request
1565 """register a function to run after dispatching a request
1559
1566
1560 Handlers do not stay registered across request boundaries."""
1567 Handlers do not stay registered across request boundaries."""
1561 self._exithandlers.append((func, args, kwargs))
1568 self._exithandlers.append((func, args, kwargs))
1562 return func
1569 return func
1563
1570
1564 def interface(self, feature: bytes) -> bytes:
1571 def interface(self, feature: bytes) -> bytes:
1565 """what interface to use for interactive console features?
1572 """what interface to use for interactive console features?
1566
1573
1567 The interface is controlled by the value of `ui.interface` but also by
1574 The interface is controlled by the value of `ui.interface` but also by
1568 the value of feature-specific configuration. For example:
1575 the value of feature-specific configuration. For example:
1569
1576
1570 ui.interface.histedit = text
1577 ui.interface.histedit = text
1571 ui.interface.chunkselector = curses
1578 ui.interface.chunkselector = curses
1572
1579
1573 Here the features are "histedit" and "chunkselector".
1580 Here the features are "histedit" and "chunkselector".
1574
1581
1575 The configuration above means that the default interfaces for commands
1582 The configuration above means that the default interfaces for commands
1576 is curses, the interface for histedit is text and the interface for
1583 is curses, the interface for histedit is text and the interface for
1577 selecting chunk is crecord (the best curses interface available).
1584 selecting chunk is crecord (the best curses interface available).
1578
1585
1579 Consider the following example:
1586 Consider the following example:
1580 ui.interface = curses
1587 ui.interface = curses
1581 ui.interface.histedit = text
1588 ui.interface.histedit = text
1582
1589
1583 Then histedit will use the text interface and chunkselector will use
1590 Then histedit will use the text interface and chunkselector will use
1584 the default curses interface (crecord at the moment).
1591 the default curses interface (crecord at the moment).
1585 """
1592 """
1586 alldefaults = frozenset([b"text", b"curses"])
1593 alldefaults = frozenset([b"text", b"curses"])
1587
1594
1588 featureinterfaces = {
1595 featureinterfaces = {
1589 b"chunkselector": [
1596 b"chunkselector": [
1590 b"text",
1597 b"text",
1591 b"curses",
1598 b"curses",
1592 ],
1599 ],
1593 b"histedit": [
1600 b"histedit": [
1594 b"text",
1601 b"text",
1595 b"curses",
1602 b"curses",
1596 ],
1603 ],
1597 }
1604 }
1598
1605
1599 # Feature-specific interface
1606 # Feature-specific interface
1600 if feature not in featureinterfaces.keys():
1607 if feature not in featureinterfaces.keys():
1601 # Programming error, not user error
1608 # Programming error, not user error
1602 raise ValueError(b"Unknown feature requested %s" % feature)
1609 raise ValueError(b"Unknown feature requested %s" % feature)
1603
1610
1604 availableinterfaces = frozenset(featureinterfaces[feature])
1611 availableinterfaces = frozenset(featureinterfaces[feature])
1605 if alldefaults > availableinterfaces:
1612 if alldefaults > availableinterfaces:
1606 # Programming error, not user error. We need a use case to
1613 # Programming error, not user error. We need a use case to
1607 # define the right thing to do here.
1614 # define the right thing to do here.
1608 raise ValueError(
1615 raise ValueError(
1609 b"Feature %s does not handle all default interfaces" % feature
1616 b"Feature %s does not handle all default interfaces" % feature
1610 )
1617 )
1611
1618
1612 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1619 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1613 return b"text"
1620 return b"text"
1614
1621
1615 # Default interface for all the features
1622 # Default interface for all the features
1616 defaultinterface = b"text"
1623 defaultinterface = b"text"
1617 i = self.config(b"ui", b"interface")
1624 i = self.config(b"ui", b"interface")
1618 if i in alldefaults:
1625 if i in alldefaults:
1619 defaultinterface = cast(bytes, i) # cast to help pytype
1626 defaultinterface = cast(bytes, i) # cast to help pytype
1620
1627
1621 choseninterface: bytes = defaultinterface
1628 choseninterface: bytes = defaultinterface
1622 f = self.config(b"ui", b"interface.%s" % feature)
1629 f = self.config(b"ui", b"interface.%s" % feature)
1623 if f in availableinterfaces:
1630 if f in availableinterfaces:
1624 choseninterface = cast(bytes, f) # cast to help pytype
1631 choseninterface = cast(bytes, f) # cast to help pytype
1625
1632
1626 if i is not None and defaultinterface != i:
1633 if i is not None and defaultinterface != i:
1627 if f is not None:
1634 if f is not None:
1628 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1635 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1629 else:
1636 else:
1630 self.warn(
1637 self.warn(
1631 _(b"invalid value for ui.interface: %s (using %s)\n")
1638 _(b"invalid value for ui.interface: %s (using %s)\n")
1632 % (i, choseninterface)
1639 % (i, choseninterface)
1633 )
1640 )
1634 if f is not None and choseninterface != f:
1641 if f is not None and choseninterface != f:
1635 self.warn(
1642 self.warn(
1636 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1643 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1637 % (feature, f, choseninterface)
1644 % (feature, f, choseninterface)
1638 )
1645 )
1639
1646
1640 return choseninterface
1647 return choseninterface
1641
1648
1642 def interactive(self):
1649 def interactive(self):
1643 """is interactive input allowed?
1650 """is interactive input allowed?
1644
1651
1645 An interactive session is a session where input can be reasonably read
1652 An interactive session is a session where input can be reasonably read
1646 from `sys.stdin'. If this function returns false, any attempt to read
1653 from `sys.stdin'. If this function returns false, any attempt to read
1647 from stdin should fail with an error, unless a sensible default has been
1654 from stdin should fail with an error, unless a sensible default has been
1648 specified.
1655 specified.
1649
1656
1650 Interactiveness is triggered by the value of the `ui.interactive'
1657 Interactiveness is triggered by the value of the `ui.interactive'
1651 configuration variable or - if it is unset - when `sys.stdin' points
1658 configuration variable or - if it is unset - when `sys.stdin' points
1652 to a terminal device.
1659 to a terminal device.
1653
1660
1654 This function refers to input only; for output, see `ui.formatted()'.
1661 This function refers to input only; for output, see `ui.formatted()'.
1655 """
1662 """
1656 i = self.configbool(b"ui", b"interactive")
1663 i = self.configbool(b"ui", b"interactive")
1657 if i is None:
1664 if i is None:
1658 # some environments replace stdin without implementing isatty
1665 # some environments replace stdin without implementing isatty
1659 # usually those are non-interactive
1666 # usually those are non-interactive
1660 return self._isatty(self._fin)
1667 return self._isatty(self._fin)
1661
1668
1662 return i
1669 return i
1663
1670
1664 def termwidth(self) -> int:
1671 def termwidth(self) -> int:
1665 """how wide is the terminal in columns?"""
1672 """how wide is the terminal in columns?"""
1666 if b'COLUMNS' in encoding.environ:
1673 if b'COLUMNS' in encoding.environ:
1667 try:
1674 try:
1668 return int(encoding.environ[b'COLUMNS'])
1675 return int(encoding.environ[b'COLUMNS'])
1669 except ValueError:
1676 except ValueError:
1670 pass
1677 pass
1671 return scmutil.termsize(self)[0]
1678 return scmutil.termsize(self)[0]
1672
1679
1673 def formatted(self):
1680 def formatted(self):
1674 """should formatted output be used?
1681 """should formatted output be used?
1675
1682
1676 It is often desirable to format the output to suite the output medium.
1683 It is often desirable to format the output to suite the output medium.
1677 Examples of this are truncating long lines or colorizing messages.
1684 Examples of this are truncating long lines or colorizing messages.
1678 However, this is not often not desirable when piping output into other
1685 However, this is not often not desirable when piping output into other
1679 utilities, e.g. `grep'.
1686 utilities, e.g. `grep'.
1680
1687
1681 Formatted output is triggered by the value of the `ui.formatted'
1688 Formatted output is triggered by the value of the `ui.formatted'
1682 configuration variable or - if it is unset - when `sys.stdout' points
1689 configuration variable or - if it is unset - when `sys.stdout' points
1683 to a terminal device. Please note that `ui.formatted' should be
1690 to a terminal device. Please note that `ui.formatted' should be
1684 considered an implementation detail; it is not intended for use outside
1691 considered an implementation detail; it is not intended for use outside
1685 Mercurial or its extensions.
1692 Mercurial or its extensions.
1686
1693
1687 This function refers to output only; for input, see `ui.interactive()'.
1694 This function refers to output only; for input, see `ui.interactive()'.
1688 This function always returns false when in plain mode, see `ui.plain()'.
1695 This function always returns false when in plain mode, see `ui.plain()'.
1689 """
1696 """
1690 if self.plain():
1697 if self.plain():
1691 return False
1698 return False
1692
1699
1693 i = self.configbool(b"ui", b"formatted")
1700 i = self.configbool(b"ui", b"formatted")
1694 if i is None:
1701 if i is None:
1695 # some environments replace stdout without implementing isatty
1702 # some environments replace stdout without implementing isatty
1696 # usually those are non-interactive
1703 # usually those are non-interactive
1697 return self._isatty(self._fout)
1704 return self._isatty(self._fout)
1698
1705
1699 return i
1706 return i
1700
1707
1701 def _readline(
1708 def _readline(
1702 self,
1709 self,
1703 prompt: bytes = b' ',
1710 prompt: bytes = b' ',
1704 promptopts: Optional[Dict[str, _MsgOpts]] = None,
1711 promptopts: Optional[Dict[str, _MsgOpts]] = None,
1705 ) -> bytes:
1712 ) -> bytes:
1706 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1713 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1707 # because they have to be text streams with *no buffering*. Instead,
1714 # because they have to be text streams with *no buffering*. Instead,
1708 # we use rawinput() only if call_readline() will be invoked by
1715 # we use rawinput() only if call_readline() will be invoked by
1709 # PyOS_Readline(), so no I/O will be made at Python layer.
1716 # PyOS_Readline(), so no I/O will be made at Python layer.
1710 usereadline = (
1717 usereadline = (
1711 self._isatty(self._fin)
1718 self._isatty(self._fin)
1712 and self._isatty(self._fout)
1719 and self._isatty(self._fout)
1713 and procutil.isstdin(self._fin)
1720 and procutil.isstdin(self._fin)
1714 and procutil.isstdout(self._fout)
1721 and procutil.isstdout(self._fout)
1715 )
1722 )
1716 if usereadline:
1723 if usereadline:
1717 try:
1724 try:
1718 # magically add command line editing support, where
1725 # magically add command line editing support, where
1719 # available
1726 # available
1720 import readline
1727 import readline
1721
1728
1722 # force demandimport to really load the module
1729 # force demandimport to really load the module
1723 readline.read_history_file
1730 readline.read_history_file
1724 # windows sometimes raises something other than ImportError
1731 # windows sometimes raises something other than ImportError
1725 except Exception:
1732 except Exception:
1726 usereadline = False
1733 usereadline = False
1727
1734
1728 if self._colormode == b'win32' or not usereadline:
1735 if self._colormode == b'win32' or not usereadline:
1729 if not promptopts:
1736 if not promptopts:
1730 promptopts = {}
1737 promptopts = {}
1731 self._writemsgnobuf(
1738 self._writemsgnobuf(
1732 self._fmsgout, prompt, type=b'prompt', **promptopts
1739 self._fmsgout, prompt, type=b'prompt', **promptopts
1733 )
1740 )
1734 self.flush()
1741 self.flush()
1735 prompt = b' '
1742 prompt = b' '
1736 else:
1743 else:
1737 prompt = self.label(prompt, b'ui.prompt') + b' '
1744 prompt = self.label(prompt, b'ui.prompt') + b' '
1738
1745
1739 # prompt ' ' must exist; otherwise readline may delete entire line
1746 # prompt ' ' must exist; otherwise readline may delete entire line
1740 # - http://bugs.python.org/issue12833
1747 # - http://bugs.python.org/issue12833
1741 with self.timeblockedsection(b'stdio'):
1748 with self.timeblockedsection(b'stdio'):
1742 if usereadline:
1749 if usereadline:
1743 self.flush()
1750 self.flush()
1744 prompt = encoding.strfromlocal(prompt)
1751 prompt = encoding.strfromlocal(prompt)
1745 line = encoding.strtolocal(input(prompt))
1752 line = encoding.strtolocal(input(prompt))
1746 # When stdin is in binary mode on Windows, it can cause
1753 # When stdin is in binary mode on Windows, it can cause
1747 # input() to emit an extra trailing carriage return
1754 # input() to emit an extra trailing carriage return
1748 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1755 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1749 line = line[:-1]
1756 line = line[:-1]
1750 else:
1757 else:
1751 self._fout.write(pycompat.bytestr(prompt))
1758 self._fout.write(pycompat.bytestr(prompt))
1752 self._fout.flush()
1759 self._fout.flush()
1753 line = self._fin.readline()
1760 line = self._fin.readline()
1754 if not line:
1761 if not line:
1755 raise EOFError
1762 raise EOFError
1756 line = line.rstrip(pycompat.oslinesep)
1763 line = line.rstrip(pycompat.oslinesep)
1757
1764
1758 return line
1765 return line
1759
1766
1760 if pycompat.TYPE_CHECKING:
1767 if pycompat.TYPE_CHECKING:
1761
1768
1762 @overload
1769 @overload
1763 def prompt(self, msg: bytes, default: bytes) -> bytes:
1770 def prompt(self, msg: bytes, default: bytes) -> bytes:
1764 pass
1771 pass
1765
1772
1766 @overload
1773 @overload
1767 def prompt(self, msg: bytes, default: None) -> Optional[bytes]:
1774 def prompt(self, msg: bytes, default: None) -> Optional[bytes]:
1768 pass
1775 pass
1769
1776
1770 def prompt(self, msg, default=b"y"):
1777 def prompt(self, msg, default=b"y"):
1771 """Prompt user with msg, read response.
1778 """Prompt user with msg, read response.
1772 If ui is not interactive, the default is returned.
1779 If ui is not interactive, the default is returned.
1773 """
1780 """
1774 return self._prompt(msg, default=default)
1781 return self._prompt(msg, default=default)
1775
1782
1776 if pycompat.TYPE_CHECKING:
1783 if pycompat.TYPE_CHECKING:
1777
1784
1778 @overload
1785 @overload
1779 def _prompt(
1786 def _prompt(
1780 self, msg: bytes, default: bytes, **opts: _MsgOpts
1787 self, msg: bytes, default: bytes, **opts: _MsgOpts
1781 ) -> bytes:
1788 ) -> bytes:
1782 pass
1789 pass
1783
1790
1784 @overload
1791 @overload
1785 def _prompt(
1792 def _prompt(
1786 self, msg: bytes, default: None, **opts: _MsgOpts
1793 self, msg: bytes, default: None, **opts: _MsgOpts
1787 ) -> Optional[bytes]:
1794 ) -> Optional[bytes]:
1788 pass
1795 pass
1789
1796
1790 def _prompt(self, msg, default=b'y', **opts):
1797 def _prompt(self, msg, default=b'y', **opts):
1791 opts = {**opts, 'default': default}
1798 opts = {**opts, 'default': default}
1792 if not self.interactive():
1799 if not self.interactive():
1793 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1800 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1794 self._writemsg(
1801 self._writemsg(
1795 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1802 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1796 )
1803 )
1797 return default
1804 return default
1798 try:
1805 try:
1799 r = self._readline(prompt=msg, promptopts=opts)
1806 r = self._readline(prompt=msg, promptopts=opts)
1800 if not r:
1807 if not r:
1801 r = default
1808 r = default
1802 if self.configbool(b'ui', b'promptecho'):
1809 if self.configbool(b'ui', b'promptecho'):
1803 self._writemsg(
1810 self._writemsg(
1804 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1811 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1805 )
1812 )
1806 return r
1813 return r
1807 except EOFError:
1814 except EOFError:
1808 raise error.ResponseExpected()
1815 raise error.ResponseExpected()
1809
1816
1810 @staticmethod
1817 @staticmethod
1811 def extractchoices(prompt: bytes) -> Tuple[bytes, List[_PromptChoice]]:
1818 def extractchoices(prompt: bytes) -> Tuple[bytes, List[_PromptChoice]]:
1812 """Extract prompt message and list of choices from specified prompt.
1819 """Extract prompt message and list of choices from specified prompt.
1813
1820
1814 This returns tuple "(message, choices)", and "choices" is the
1821 This returns tuple "(message, choices)", and "choices" is the
1815 list of tuple "(response character, text without &)".
1822 list of tuple "(response character, text without &)".
1816
1823
1817 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1824 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1818 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1825 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1819 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1826 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1820 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1827 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1821 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1828 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1822 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1829 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1823 """
1830 """
1824
1831
1825 # Sadly, the prompt string may have been built with a filename
1832 # Sadly, the prompt string may have been built with a filename
1826 # containing "$$" so let's try to find the first valid-looking
1833 # containing "$$" so let's try to find the first valid-looking
1827 # prompt to start parsing. Sadly, we also can't rely on
1834 # prompt to start parsing. Sadly, we also can't rely on
1828 # choices containing spaces, ASCII, or basically anything
1835 # choices containing spaces, ASCII, or basically anything
1829 # except an ampersand followed by a character.
1836 # except an ampersand followed by a character.
1830 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1837 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1831
1838
1832 assert m is not None # help pytype
1839 assert m is not None # help pytype
1833
1840
1834 msg = m.group(1)
1841 msg = m.group(1)
1835 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1842 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1836
1843
1837 def choicetuple(s):
1844 def choicetuple(s):
1838 ampidx = s.index(b'&')
1845 ampidx = s.index(b'&')
1839 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1846 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1840
1847
1841 return (msg, [choicetuple(s) for s in choices])
1848 return (msg, [choicetuple(s) for s in choices])
1842
1849
1843 def promptchoice(self, prompt: bytes, default: int = 0) -> int:
1850 def promptchoice(self, prompt: bytes, default: int = 0) -> int:
1844 """Prompt user with a message, read response, and ensure it matches
1851 """Prompt user with a message, read response, and ensure it matches
1845 one of the provided choices. The prompt is formatted as follows:
1852 one of the provided choices. The prompt is formatted as follows:
1846
1853
1847 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1854 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1848
1855
1849 The index of the choice is returned. Responses are case
1856 The index of the choice is returned. Responses are case
1850 insensitive. If ui is not interactive, the default is
1857 insensitive. If ui is not interactive, the default is
1851 returned.
1858 returned.
1852 """
1859 """
1853
1860
1854 msg, choices = self.extractchoices(prompt)
1861 msg, choices = self.extractchoices(prompt)
1855 resps = [r for r, t in choices]
1862 resps = [r for r, t in choices]
1856 while True:
1863 while True:
1857 r = self._prompt(msg, default=resps[default], choices=choices)
1864 r = self._prompt(msg, default=resps[default], choices=choices)
1858 if r.lower() in resps:
1865 if r.lower() in resps:
1859 return resps.index(r.lower())
1866 return resps.index(r.lower())
1860 # TODO: shouldn't it be a warning?
1867 # TODO: shouldn't it be a warning?
1861 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1868 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1862
1869
1863 def getpass(
1870 def getpass(
1864 self, prompt: Optional[bytes] = None, default: Optional[bytes] = None
1871 self, prompt: Optional[bytes] = None, default: Optional[bytes] = None
1865 ) -> Optional[bytes]:
1872 ) -> Optional[bytes]:
1866 if not self.interactive():
1873 if not self.interactive():
1867 return default
1874 return default
1868 try:
1875 try:
1869 self._writemsg(
1876 self._writemsg(
1870 self._fmsgerr,
1877 self._fmsgerr,
1871 prompt or _(b'password: '),
1878 prompt or _(b'password: '),
1872 type=b'prompt',
1879 type=b'prompt',
1873 password=True,
1880 password=True,
1874 )
1881 )
1875 # disable getpass() only if explicitly specified. it's still valid
1882 # disable getpass() only if explicitly specified. it's still valid
1876 # to interact with tty even if fin is not a tty.
1883 # to interact with tty even if fin is not a tty.
1877 with self.timeblockedsection(b'stdio'):
1884 with self.timeblockedsection(b'stdio'):
1878 if self.configbool(b'ui', b'nontty'):
1885 if self.configbool(b'ui', b'nontty'):
1879 l = self._fin.readline()
1886 l = self._fin.readline()
1880 if not l:
1887 if not l:
1881 raise EOFError
1888 raise EOFError
1882 return l.rstrip(b'\n')
1889 return l.rstrip(b'\n')
1883 else:
1890 else:
1884 return util.get_password()
1891 return util.get_password()
1885 except EOFError:
1892 except EOFError:
1886 raise error.ResponseExpected()
1893 raise error.ResponseExpected()
1887
1894
1888 def status(self, *msg: bytes, **opts: _MsgOpts) -> None:
1895 def status(self, *msg: bytes, **opts: _MsgOpts) -> None:
1889 """write status message to output (if ui.quiet is False)
1896 """write status message to output (if ui.quiet is False)
1890
1897
1891 This adds an output label of "ui.status".
1898 This adds an output label of "ui.status".
1892 """
1899 """
1893 if not self.quiet:
1900 if not self.quiet:
1894 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1901 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1895
1902
1896 def warn(self, *msg: bytes, **opts: _MsgOpts) -> None:
1903 def warn(self, *msg: bytes, **opts: _MsgOpts) -> None:
1897 """write warning message to output (stderr)
1904 """write warning message to output (stderr)
1898
1905
1899 This adds an output label of "ui.warning".
1906 This adds an output label of "ui.warning".
1900 """
1907 """
1901 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1908 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1902
1909
1903 def error(self, *msg: bytes, **opts: _MsgOpts) -> None:
1910 def error(self, *msg: bytes, **opts: _MsgOpts) -> None:
1904 """write error message to output (stderr)
1911 """write error message to output (stderr)
1905
1912
1906 This adds an output label of "ui.error".
1913 This adds an output label of "ui.error".
1907 """
1914 """
1908 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1915 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1909
1916
1910 def note(self, *msg: bytes, **opts: _MsgOpts) -> None:
1917 def note(self, *msg: bytes, **opts: _MsgOpts) -> None:
1911 """write note to output (if ui.verbose is True)
1918 """write note to output (if ui.verbose is True)
1912
1919
1913 This adds an output label of "ui.note".
1920 This adds an output label of "ui.note".
1914 """
1921 """
1915 if self.verbose:
1922 if self.verbose:
1916 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1923 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1917
1924
1918 def debug(self, *msg: bytes, **opts: _MsgOpts) -> None:
1925 def debug(self, *msg: bytes, **opts: _MsgOpts) -> None:
1919 """write debug message to output (if ui.debugflag is True)
1926 """write debug message to output (if ui.debugflag is True)
1920
1927
1921 This adds an output label of "ui.debug".
1928 This adds an output label of "ui.debug".
1922 """
1929 """
1923 if self.debugflag:
1930 if self.debugflag:
1924 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1931 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1925 self.log(b'debug', b'%s', b''.join(msg))
1932 self.log(b'debug', b'%s', b''.join(msg))
1926
1933
1927 # Aliases to defeat check-code.
1934 # Aliases to defeat check-code.
1928 statusnoi18n = status
1935 statusnoi18n = status
1929 notenoi18n = note
1936 notenoi18n = note
1930 warnnoi18n = warn
1937 warnnoi18n = warn
1931 writenoi18n = write
1938 writenoi18n = write
1932
1939
1933 def edit(
1940 def edit(
1934 self,
1941 self,
1935 text: bytes,
1942 text: bytes,
1936 user: bytes,
1943 user: bytes,
1937 extra: Optional[Dict[bytes, Any]] = None, # TODO: value type of bytes?
1944 extra: Optional[Dict[bytes, Any]] = None, # TODO: value type of bytes?
1938 editform=None,
1945 editform=None,
1939 pending=None,
1946 pending=None,
1940 repopath: Optional[bytes] = None,
1947 repopath: Optional[bytes] = None,
1941 action: Optional[bytes] = None,
1948 action: Optional[bytes] = None,
1942 ) -> bytes:
1949 ) -> bytes:
1943 if action is None:
1950 if action is None:
1944 self.develwarn(
1951 self.develwarn(
1945 b'action is None but will soon be a required '
1952 b'action is None but will soon be a required '
1946 b'parameter to ui.edit()'
1953 b'parameter to ui.edit()'
1947 )
1954 )
1948 extra_defaults = {
1955 extra_defaults = {
1949 b'prefix': b'editor',
1956 b'prefix': b'editor',
1950 b'suffix': b'.txt',
1957 b'suffix': b'.txt',
1951 }
1958 }
1952 if extra is not None:
1959 if extra is not None:
1953 if extra.get(b'suffix') is not None:
1960 if extra.get(b'suffix') is not None:
1954 self.develwarn(
1961 self.develwarn(
1955 b'extra.suffix is not None but will soon be '
1962 b'extra.suffix is not None but will soon be '
1956 b'ignored by ui.edit()'
1963 b'ignored by ui.edit()'
1957 )
1964 )
1958 extra_defaults.update(extra)
1965 extra_defaults.update(extra)
1959 extra = extra_defaults
1966 extra = extra_defaults
1960
1967
1961 if action == b'diff':
1968 if action == b'diff':
1962 suffix = b'.diff'
1969 suffix = b'.diff'
1963 elif action:
1970 elif action:
1964 suffix = b'.%s.hg.txt' % action
1971 suffix = b'.%s.hg.txt' % action
1965 else:
1972 else:
1966 suffix = extra[b'suffix']
1973 suffix = extra[b'suffix']
1967
1974
1968 rdir = None
1975 rdir = None
1969 if self.configbool(b'experimental', b'editortmpinhg'):
1976 if self.configbool(b'experimental', b'editortmpinhg'):
1970 rdir = repopath
1977 rdir = repopath
1971 (fd, name) = pycompat.mkstemp(
1978 (fd, name) = pycompat.mkstemp(
1972 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1979 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1973 )
1980 )
1974 try:
1981 try:
1975 with os.fdopen(fd, 'wb') as f:
1982 with os.fdopen(fd, 'wb') as f:
1976 f.write(util.tonativeeol(text))
1983 f.write(util.tonativeeol(text))
1977
1984
1978 environ = {b'HGUSER': user}
1985 environ = {b'HGUSER': user}
1979 if b'transplant_source' in extra:
1986 if b'transplant_source' in extra:
1980 environ.update(
1987 environ.update(
1981 {b'HGREVISION': hex(extra[b'transplant_source'])}
1988 {b'HGREVISION': hex(extra[b'transplant_source'])}
1982 )
1989 )
1983 for label in (b'intermediate-source', b'source', b'rebase_source'):
1990 for label in (b'intermediate-source', b'source', b'rebase_source'):
1984 if label in extra:
1991 if label in extra:
1985 environ.update({b'HGREVISION': extra[label]})
1992 environ.update({b'HGREVISION': extra[label]})
1986 break
1993 break
1987 if editform:
1994 if editform:
1988 environ.update({b'HGEDITFORM': editform})
1995 environ.update({b'HGEDITFORM': editform})
1989 if pending:
1996 if pending:
1990 environ.update({b'HG_PENDING': pending})
1997 environ.update({b'HG_PENDING': pending})
1991
1998
1992 editor = self.geteditor()
1999 editor = self.geteditor()
1993
2000
1994 self.system(
2001 self.system(
1995 b"%s \"%s\"" % (editor, name),
2002 b"%s \"%s\"" % (editor, name),
1996 environ=environ,
2003 environ=environ,
1997 onerr=error.CanceledError,
2004 onerr=error.CanceledError,
1998 errprefix=_(b"edit failed"),
2005 errprefix=_(b"edit failed"),
1999 blockedtag=b'editor',
2006 blockedtag=b'editor',
2000 )
2007 )
2001
2008
2002 with open(name, 'rb') as f:
2009 with open(name, 'rb') as f:
2003 t = util.fromnativeeol(f.read())
2010 t = util.fromnativeeol(f.read())
2004 finally:
2011 finally:
2005 os.unlink(name)
2012 os.unlink(name)
2006
2013
2007 return t
2014 return t
2008
2015
2009 def system(
2016 def system(
2010 self,
2017 self,
2011 cmd: bytes,
2018 cmd: bytes,
2012 environ=None,
2019 environ=None,
2013 cwd: Optional[bytes] = None,
2020 cwd: Optional[bytes] = None,
2014 onerr: Optional[Callable[[bytes], Exception]] = None,
2021 onerr: Optional[Callable[[bytes], Exception]] = None,
2015 errprefix: Optional[bytes] = None,
2022 errprefix: Optional[bytes] = None,
2016 blockedtag: Optional[bytes] = None,
2023 blockedtag: Optional[bytes] = None,
2017 ) -> int:
2024 ) -> int:
2018 """execute shell command with appropriate output stream. command
2025 """execute shell command with appropriate output stream. command
2019 output will be redirected if fout is not stdout.
2026 output will be redirected if fout is not stdout.
2020
2027
2021 if command fails and onerr is None, return status, else raise onerr
2028 if command fails and onerr is None, return status, else raise onerr
2022 object as exception.
2029 object as exception.
2023 """
2030 """
2024 if blockedtag is None:
2031 if blockedtag is None:
2025 # Long cmds tend to be because of an absolute path on cmd. Keep
2032 # Long cmds tend to be because of an absolute path on cmd. Keep
2026 # the tail end instead
2033 # the tail end instead
2027 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
2034 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
2028 blockedtag = b'unknown_system_' + cmdsuffix
2035 blockedtag = b'unknown_system_' + cmdsuffix
2029 out = self._fout
2036 out = self._fout
2030 if any(s[1] for s in self._bufferstates):
2037 if any(s[1] for s in self._bufferstates):
2031 out = self
2038 out = self
2032 with self.timeblockedsection(blockedtag):
2039 with self.timeblockedsection(blockedtag):
2033 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
2040 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
2034 if rc and onerr:
2041 if rc and onerr:
2035 errmsg = b'%s %s' % (
2042 errmsg = b'%s %s' % (
2036 procutil.shellsplit(cmd)[0],
2043 procutil.shellsplit(cmd)[0],
2037 procutil.explainexit(rc),
2044 procutil.explainexit(rc),
2038 )
2045 )
2039 if errprefix:
2046 if errprefix:
2040 errmsg = b'%s: %s' % (errprefix, errmsg)
2047 errmsg = b'%s: %s' % (errprefix, errmsg)
2041 raise onerr(errmsg)
2048 raise onerr(errmsg)
2042 return rc
2049 return rc
2043
2050
2044 def _runsystem(self, cmd: bytes, environ, cwd: Optional[bytes], out) -> int:
2051 def _runsystem(self, cmd: bytes, environ, cwd: Optional[bytes], out) -> int:
2045 """actually execute the given shell command (can be overridden by
2052 """actually execute the given shell command (can be overridden by
2046 extensions like chg)"""
2053 extensions like chg)"""
2047 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
2054 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
2048
2055
2049 def traceback(self, exc=None, force: bool = False):
2056 def traceback(self, exc=None, force: bool = False):
2050 """print exception traceback if traceback printing enabled or forced.
2057 """print exception traceback if traceback printing enabled or forced.
2051 only to call in exception handler. returns true if traceback
2058 only to call in exception handler. returns true if traceback
2052 printed."""
2059 printed."""
2053 if self.tracebackflag or force:
2060 if self.tracebackflag or force:
2054 if exc is None:
2061 if exc is None:
2055 exc = sys.exc_info()
2062 exc = sys.exc_info()
2056 cause = getattr(exc[1], 'cause', None)
2063 cause = getattr(exc[1], 'cause', None)
2057
2064
2058 if cause is not None:
2065 if cause is not None:
2059 causetb = traceback.format_tb(cause[2])
2066 causetb = traceback.format_tb(cause[2])
2060 exctb = traceback.format_tb(exc[2])
2067 exctb = traceback.format_tb(exc[2])
2061 exconly = traceback.format_exception_only(cause[0], cause[1])
2068 exconly = traceback.format_exception_only(cause[0], cause[1])
2062
2069
2063 # exclude frame where 'exc' was chained and rethrown from exctb
2070 # exclude frame where 'exc' was chained and rethrown from exctb
2064 self.write_err(
2071 self.write_err(
2065 b'Traceback (most recent call last):\n',
2072 b'Traceback (most recent call last):\n',
2066 encoding.strtolocal(''.join(exctb[:-1])),
2073 encoding.strtolocal(''.join(exctb[:-1])),
2067 encoding.strtolocal(''.join(causetb)),
2074 encoding.strtolocal(''.join(causetb)),
2068 encoding.strtolocal(''.join(exconly)),
2075 encoding.strtolocal(''.join(exconly)),
2069 )
2076 )
2070 else:
2077 else:
2071 output = traceback.format_exception(exc[0], exc[1], exc[2])
2078 output = traceback.format_exception(exc[0], exc[1], exc[2])
2072 self.write_err(encoding.strtolocal(''.join(output)))
2079 self.write_err(encoding.strtolocal(''.join(output)))
2073 return self.tracebackflag or force
2080 return self.tracebackflag or force
2074
2081
2075 def geteditor(self):
2082 def geteditor(self):
2076 '''return editor to use'''
2083 '''return editor to use'''
2077 if pycompat.sysplatform == b'plan9':
2084 if pycompat.sysplatform == b'plan9':
2078 # vi is the MIPS instruction simulator on Plan 9. We
2085 # vi is the MIPS instruction simulator on Plan 9. We
2079 # instead default to E to plumb commit messages to
2086 # instead default to E to plumb commit messages to
2080 # avoid confusion.
2087 # avoid confusion.
2081 editor = b'E'
2088 editor = b'E'
2082 elif pycompat.isdarwin:
2089 elif pycompat.isdarwin:
2083 # vi on darwin is POSIX compatible to a fault, and that includes
2090 # vi on darwin is POSIX compatible to a fault, and that includes
2084 # exiting non-zero if you make any mistake when running an ex
2091 # exiting non-zero if you make any mistake when running an ex
2085 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
2092 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
2086 # while s/vi/vim/ doesn't.
2093 # while s/vi/vim/ doesn't.
2087 editor = b'vim'
2094 editor = b'vim'
2088 else:
2095 else:
2089 editor = b'vi'
2096 editor = b'vi'
2090 return encoding.environ.get(b"HGEDITOR") or self.config(
2097 return encoding.environ.get(b"HGEDITOR") or self.config(
2091 b"ui", b"editor", editor
2098 b"ui", b"editor", editor
2092 )
2099 )
2093
2100
2094 @util.propertycache
2101 @util.propertycache
2095 def _progbar(self) -> Optional[progress.progbar]:
2102 def _progbar(self) -> Optional[progress.progbar]:
2096 """setup the progbar singleton to the ui object"""
2103 """setup the progbar singleton to the ui object"""
2097 if (
2104 if (
2098 self.quiet
2105 self.quiet
2099 or self.debugflag
2106 or self.debugflag
2100 or self.configbool(b'progress', b'disable')
2107 or self.configbool(b'progress', b'disable')
2101 or not progress.shouldprint(self)
2108 or not progress.shouldprint(self)
2102 ):
2109 ):
2103 return None
2110 return None
2104 return getprogbar(self)
2111 return getprogbar(self)
2105
2112
2106 def _progclear(self) -> None:
2113 def _progclear(self) -> None:
2107 """clear progress bar output if any. use it before any output"""
2114 """clear progress bar output if any. use it before any output"""
2108 if not haveprogbar(): # nothing loaded yet
2115 if not haveprogbar(): # nothing loaded yet
2109 return
2116 return
2110 if self._progbar is not None and self._progbar.printed:
2117 if self._progbar is not None and self._progbar.printed:
2111 self._progbar.clear()
2118 self._progbar.clear()
2112
2119
2113 def makeprogress(
2120 def makeprogress(
2114 self, topic: bytes, unit: bytes = b"", total: Optional[int] = None
2121 self, topic: bytes, unit: bytes = b"", total: Optional[int] = None
2115 ) -> scmutil.progress:
2122 ) -> scmutil.progress:
2116 """Create a progress helper for the specified topic"""
2123 """Create a progress helper for the specified topic"""
2117 if getattr(self._fmsgerr, 'structured', False):
2124 if getattr(self._fmsgerr, 'structured', False):
2118 # channel for machine-readable output with metadata, just send
2125 # channel for machine-readable output with metadata, just send
2119 # raw information
2126 # raw information
2120 # TODO: consider porting some useful information (e.g. estimated
2127 # TODO: consider porting some useful information (e.g. estimated
2121 # time) from progbar. we might want to support update delay to
2128 # time) from progbar. we might want to support update delay to
2122 # reduce the cost of transferring progress messages.
2129 # reduce the cost of transferring progress messages.
2123 def updatebar(topic, pos, item, unit, total):
2130 def updatebar(topic, pos, item, unit, total):
2124 self._fmsgerr.write(
2131 self._fmsgerr.write(
2125 None,
2132 None,
2126 type=b'progress',
2133 type=b'progress',
2127 topic=topic,
2134 topic=topic,
2128 pos=pos,
2135 pos=pos,
2129 item=item,
2136 item=item,
2130 unit=unit,
2137 unit=unit,
2131 total=total,
2138 total=total,
2132 )
2139 )
2133
2140
2134 elif self._progbar is not None:
2141 elif self._progbar is not None:
2135 updatebar = self._progbar.progress
2142 updatebar = self._progbar.progress
2136 else:
2143 else:
2137
2144
2138 def updatebar(topic, pos, item, unit, total):
2145 def updatebar(topic, pos, item, unit, total):
2139 pass
2146 pass
2140
2147
2141 return scmutil.progress(self, updatebar, topic, unit, total)
2148 return scmutil.progress(self, updatebar, topic, unit, total)
2142
2149
2143 def getlogger(self, name):
2150 def getlogger(self, name):
2144 """Returns a logger of the given name; or None if not registered"""
2151 """Returns a logger of the given name; or None if not registered"""
2145 return self._loggers.get(name)
2152 return self._loggers.get(name)
2146
2153
2147 def setlogger(self, name, logger) -> None:
2154 def setlogger(self, name, logger) -> None:
2148 """Install logger which can be identified later by the given name
2155 """Install logger which can be identified later by the given name
2149
2156
2150 More than one loggers can be registered. Use extension or module
2157 More than one loggers can be registered. Use extension or module
2151 name to uniquely identify the logger instance.
2158 name to uniquely identify the logger instance.
2152 """
2159 """
2153 self._loggers[name] = logger
2160 self._loggers[name] = logger
2154
2161
2155 def log(self, event, msgfmt, *msgargs, **opts) -> None:
2162 def log(self, event, msgfmt, *msgargs, **opts) -> None:
2156 """hook for logging facility extensions
2163 """hook for logging facility extensions
2157
2164
2158 event should be a readily-identifiable subsystem, which will
2165 event should be a readily-identifiable subsystem, which will
2159 allow filtering.
2166 allow filtering.
2160
2167
2161 msgfmt should be a newline-terminated format string to log, and
2168 msgfmt should be a newline-terminated format string to log, and
2162 *msgargs are %-formatted into it.
2169 *msgargs are %-formatted into it.
2163
2170
2164 **opts currently has no defined meanings.
2171 **opts currently has no defined meanings.
2165 """
2172 """
2166 if not self._loggers:
2173 if not self._loggers:
2167 return
2174 return
2168 activeloggers = [l for l in self._loggers.values() if l.tracked(event)]
2175 activeloggers = [l for l in self._loggers.values() if l.tracked(event)]
2169 if not activeloggers:
2176 if not activeloggers:
2170 return
2177 return
2171 msg = msgfmt % msgargs
2178 msg = msgfmt % msgargs
2172 opts = pycompat.byteskwargs(opts)
2179 opts = pycompat.byteskwargs(opts)
2173 # guard against recursion from e.g. ui.debug()
2180 # guard against recursion from e.g. ui.debug()
2174 registeredloggers = self._loggers
2181 registeredloggers = self._loggers
2175 self._loggers = {}
2182 self._loggers = {}
2176 try:
2183 try:
2177 for logger in activeloggers:
2184 for logger in activeloggers:
2178 logger.log(self, event, msg, opts)
2185 logger.log(self, event, msg, opts)
2179 finally:
2186 finally:
2180 self._loggers = registeredloggers
2187 self._loggers = registeredloggers
2181
2188
2182 def label(self, msg: bytes, label: bytes) -> bytes:
2189 def label(self, msg: bytes, label: bytes) -> bytes:
2183 """style msg based on supplied label
2190 """style msg based on supplied label
2184
2191
2185 If some color mode is enabled, this will add the necessary control
2192 If some color mode is enabled, this will add the necessary control
2186 characters to apply such color. In addition, 'debug' color mode adds
2193 characters to apply such color. In addition, 'debug' color mode adds
2187 markup showing which label affects a piece of text.
2194 markup showing which label affects a piece of text.
2188
2195
2189 ui.write(s, 'label') is equivalent to
2196 ui.write(s, 'label') is equivalent to
2190 ui.write(ui.label(s, 'label')).
2197 ui.write(ui.label(s, 'label')).
2191 """
2198 """
2192 if self._colormode is not None:
2199 if self._colormode is not None:
2193 return color.colorlabel(self, msg, label)
2200 return color.colorlabel(self, msg, label)
2194 return msg
2201 return msg
2195
2202
2196 def develwarn(
2203 def develwarn(
2197 self, msg: bytes, stacklevel: int = 1, config: Optional[bytes] = None
2204 self, msg: bytes, stacklevel: int = 1, config: Optional[bytes] = None
2198 ) -> None:
2205 ) -> None:
2199 """issue a developer warning message
2206 """issue a developer warning message
2200
2207
2201 Use 'stacklevel' to report the offender some layers further up in the
2208 Use 'stacklevel' to report the offender some layers further up in the
2202 stack.
2209 stack.
2203 """
2210 """
2204 if not self.configbool(b'devel', b'all-warnings'):
2211 if not self.configbool(b'devel', b'all-warnings'):
2205 if config is None or not self.configbool(b'devel', config):
2212 if config is None or not self.configbool(b'devel', config):
2206 return
2213 return
2207 msg = b'devel-warn: ' + msg
2214 msg = b'devel-warn: ' + msg
2208 stacklevel += 1 # get in develwarn
2215 stacklevel += 1 # get in develwarn
2209 if self.tracebackflag:
2216 if self.tracebackflag:
2210 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2217 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2211 self.log(
2218 self.log(
2212 b'develwarn',
2219 b'develwarn',
2213 b'%s at:\n%s'
2220 b'%s at:\n%s'
2214 % (msg, b''.join(util.getstackframes(stacklevel))),
2221 % (msg, b''.join(util.getstackframes(stacklevel))),
2215 )
2222 )
2216 else:
2223 else:
2217 curframe = inspect.currentframe()
2224 curframe = inspect.currentframe()
2218 calframe = inspect.getouterframes(curframe, 2)
2225 calframe = inspect.getouterframes(curframe, 2)
2219 fname, lineno, fmsg = calframe[stacklevel][1:4]
2226 fname, lineno, fmsg = calframe[stacklevel][1:4]
2220 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2227 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2221 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2228 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2222 self.log(
2229 self.log(
2223 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2230 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2224 )
2231 )
2225
2232
2226 # avoid cycles
2233 # avoid cycles
2227 del curframe
2234 del curframe
2228 del calframe
2235 del calframe
2229
2236
2230 def deprecwarn(
2237 def deprecwarn(
2231 self,
2238 self,
2232 msg: bytes,
2239 msg: bytes,
2233 version: bytes,
2240 version: bytes,
2234 stacklevel: int = 2,
2241 stacklevel: int = 2,
2235 ) -> None:
2242 ) -> None:
2236 """issue a deprecation warning
2243 """issue a deprecation warning
2237
2244
2238 - msg: message explaining what is deprecated and how to upgrade,
2245 - msg: message explaining what is deprecated and how to upgrade,
2239 - version: last version where the API will be supported,
2246 - version: last version where the API will be supported,
2240 """
2247 """
2241 if not (
2248 if not (
2242 self.configbool(b'devel', b'all-warnings')
2249 self.configbool(b'devel', b'all-warnings')
2243 or self.configbool(b'devel', b'deprec-warn')
2250 or self.configbool(b'devel', b'deprec-warn')
2244 ):
2251 ):
2245 return
2252 return
2246 msg += (
2253 msg += (
2247 b"\n(compatibility will be dropped after Mercurial-%s,"
2254 b"\n(compatibility will be dropped after Mercurial-%s,"
2248 b" update your code.)"
2255 b" update your code.)"
2249 ) % version
2256 ) % version
2250 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2257 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2251
2258
2252 def exportableenviron(self):
2259 def exportableenviron(self):
2253 """The environment variables that are safe to export, e.g. through
2260 """The environment variables that are safe to export, e.g. through
2254 hgweb.
2261 hgweb.
2255 """
2262 """
2256 return self._exportableenviron
2263 return self._exportableenviron
2257
2264
2258 @contextlib.contextmanager
2265 @contextlib.contextmanager
2259 def configoverride(self, overrides: _ConfigItems, source: bytes = b""):
2266 def configoverride(self, overrides: _ConfigItems, source: bytes = b""):
2260 """Context manager for temporary config overrides
2267 """Context manager for temporary config overrides
2261 `overrides` must be a dict of the following structure:
2268 `overrides` must be a dict of the following structure:
2262 {(section, name) : value}"""
2269 {(section, name) : value}"""
2263 backups = {}
2270 backups = {}
2264 try:
2271 try:
2265 for (section, name), value in overrides.items():
2272 for (section, name), value in overrides.items():
2266 backups[(section, name)] = self.backupconfig(section, name)
2273 backups[(section, name)] = self.backupconfig(section, name)
2267 self.setconfig(section, name, value, source)
2274 self.setconfig(section, name, value, source)
2268 yield
2275 yield
2269 finally:
2276 finally:
2270 for __, backup in backups.items():
2277 for __, backup in backups.items():
2271 self.restoreconfig(backup)
2278 self.restoreconfig(backup)
2272 # just restoring ui.quiet config to the previous value is not enough
2279 # just restoring ui.quiet config to the previous value is not enough
2273 # as it does not update ui.quiet class member
2280 # as it does not update ui.quiet class member
2274 if (b'ui', b'quiet') in overrides:
2281 if (b'ui', b'quiet') in overrides:
2275 self.fixconfig(section=b'ui')
2282 self.fixconfig(section=b'ui')
2276
2283
2277 def estimatememory(self) -> Optional[int]:
2284 def estimatememory(self) -> Optional[int]:
2278 """Provide an estimate for the available system memory in Bytes.
2285 """Provide an estimate for the available system memory in Bytes.
2279
2286
2280 This can be overriden via ui.available-memory. It returns None, if
2287 This can be overriden via ui.available-memory. It returns None, if
2281 no estimate can be computed.
2288 no estimate can be computed.
2282 """
2289 """
2283 value = self.config(b'ui', b'available-memory')
2290 value = self.config(b'ui', b'available-memory')
2284 if value is not None:
2291 if value is not None:
2285 try:
2292 try:
2286 return util.sizetoint(value)
2293 return util.sizetoint(value)
2287 except error.ParseError:
2294 except error.ParseError:
2288 raise error.ConfigError(
2295 raise error.ConfigError(
2289 _(b"ui.available-memory value is invalid ('%s')") % value
2296 _(b"ui.available-memory value is invalid ('%s')") % value
2290 )
2297 )
2291 return util._estimatememory()
2298 return util._estimatememory()
2292
2299
2293
2300
2294 # we instantiate one globally shared progress bar to avoid
2301 # we instantiate one globally shared progress bar to avoid
2295 # competing progress bars when multiple UI objects get created
2302 # competing progress bars when multiple UI objects get created
2296 _progresssingleton: Optional[progress.progbar] = None
2303 _progresssingleton: Optional[progress.progbar] = None
2297
2304
2298
2305
2299 def getprogbar(ui: ui) -> progress.progbar:
2306 def getprogbar(ui: ui) -> progress.progbar:
2300 global _progresssingleton
2307 global _progresssingleton
2301 if _progresssingleton is None:
2308 if _progresssingleton is None:
2302 # passing 'ui' object to the singleton is fishy,
2309 # passing 'ui' object to the singleton is fishy,
2303 # this is how the extension used to work but feel free to rework it.
2310 # this is how the extension used to work but feel free to rework it.
2304 _progresssingleton = progress.progbar(ui)
2311 _progresssingleton = progress.progbar(ui)
2305 return _progresssingleton
2312 return _progresssingleton
2306
2313
2307
2314
2308 def haveprogbar() -> bool:
2315 def haveprogbar() -> bool:
2309 return _progresssingleton is not None
2316 return _progresssingleton is not None
2310
2317
2311
2318
2312 def _selectmsgdests(ui: ui):
2319 def _selectmsgdests(ui: ui):
2313 name = ui.config(b'ui', b'message-output')
2320 name = ui.config(b'ui', b'message-output')
2314 if name == b'channel':
2321 if name == b'channel':
2315 if ui.fmsg:
2322 if ui.fmsg:
2316 return ui.fmsg, ui.fmsg
2323 return ui.fmsg, ui.fmsg
2317 else:
2324 else:
2318 # fall back to ferr if channel isn't ready so that status/error
2325 # fall back to ferr if channel isn't ready so that status/error
2319 # messages can be printed
2326 # messages can be printed
2320 return ui.ferr, ui.ferr
2327 return ui.ferr, ui.ferr
2321 if name == b'stdio':
2328 if name == b'stdio':
2322 return ui.fout, ui.ferr
2329 return ui.fout, ui.ferr
2323 if name == b'stderr':
2330 if name == b'stderr':
2324 return ui.ferr, ui.ferr
2331 return ui.ferr, ui.ferr
2325 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2332 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2326
2333
2327
2334
2328 def _writemsgwith(write, dest, *args: bytes, **opts: _MsgOpts) -> None:
2335 def _writemsgwith(write, dest, *args: bytes, **opts: _MsgOpts) -> None:
2329 """Write ui message with the given ui._write*() function
2336 """Write ui message with the given ui._write*() function
2330
2337
2331 The specified message type is translated to 'ui.<type>' label if the dest
2338 The specified message type is translated to 'ui.<type>' label if the dest
2332 isn't a structured channel, so that the message will be colorized.
2339 isn't a structured channel, so that the message will be colorized.
2333 """
2340 """
2334 # TODO: maybe change 'type' to a mandatory option
2341 # TODO: maybe change 'type' to a mandatory option
2335 if 'type' in opts and not getattr(dest, 'structured', False):
2342 if 'type' in opts and not getattr(dest, 'structured', False):
2336 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2343 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2337 write(dest, *args, **opts)
2344 write(dest, *args, **opts)
@@ -1,669 +1,692 b''
1 //! Code for parsing default Mercurial config items.
1 //! Code for parsing default Mercurial config items.
2 use itertools::Itertools;
2 use itertools::Itertools;
3 use serde::Deserialize;
3 use serde::Deserialize;
4
4
5 use crate::{errors::HgError, exit_codes, FastHashMap};
5 use crate::{errors::HgError, exit_codes, FastHashMap};
6
6
7 /// Corresponds to the structure of `mercurial/configitems.toml`.
7 /// Corresponds to the structure of `mercurial/configitems.toml`.
8 #[derive(Debug, Deserialize)]
8 #[derive(Debug, Deserialize)]
9 pub struct ConfigItems {
9 pub struct ConfigItems {
10 items: Vec<DefaultConfigItem>,
10 items: Vec<DefaultConfigItem>,
11 templates: FastHashMap<String, Vec<TemplateItem>>,
11 templates: FastHashMap<String, Vec<TemplateItem>>,
12 #[serde(rename = "template-applications")]
12 #[serde(rename = "template-applications")]
13 template_applications: Vec<TemplateApplication>,
13 template_applications: Vec<TemplateApplication>,
14 }
14 }
15
15
16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
17 #[derive(Clone, Debug, PartialEq, Deserialize)]
17 #[derive(Clone, Debug, PartialEq, Deserialize)]
18 #[serde(try_from = "RawDefaultConfigItem")]
18 #[serde(try_from = "RawDefaultConfigItem")]
19 pub struct DefaultConfigItem {
19 pub struct DefaultConfigItem {
20 /// Section of the config the item is in (e.g. `[merge-tools]`)
20 /// Section of the config the item is in (e.g. `[merge-tools]`)
21 section: String,
21 section: String,
22 /// Name of the item (e.g. `meld.gui`)
22 /// Name of the item (e.g. `meld.gui`)
23 name: String,
23 name: String,
24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
25 default: Option<DefaultConfigItemType>,
25 default: Option<DefaultConfigItemType>,
26 /// If the config option is generic (e.g. `merge-tools.*`), defines
26 /// If the config option is generic (e.g. `merge-tools.*`), defines
27 /// the priority of this item relative to other generic items.
27 /// the priority of this item relative to other generic items.
28 /// If we're looking for <pattern>, then all generic items within the same
28 /// If we're looking for <pattern>, then all generic items within the same
29 /// section will be sorted by order of priority, and the first regex match
29 /// section will be sorted by order of priority, and the first regex match
30 /// against `name` is returned.
30 /// against `name` is returned.
31 #[serde(default)]
31 #[serde(default)]
32 priority: Option<isize>,
32 priority: Option<isize>,
33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
34 /// option that is aliased to this one.
34 /// option that is aliased to this one.
35 #[serde(default)]
35 #[serde(default)]
36 alias: Vec<(String, String)>,
36 alias: Vec<(String, String)>,
37 /// Whether the config item is marked as experimental
37 /// Whether the config item is marked as experimental
38 #[serde(default)]
38 #[serde(default)]
39 experimental: bool,
39 experimental: bool,
40 /// The (possibly empty) docstring for the item
40 /// The (possibly empty) docstring for the item
41 #[serde(default)]
41 #[serde(default)]
42 documentation: String,
42 documentation: String,
43 /// Whether the item is part of an in-core extension. This allows us to
44 /// hide them if the extension is not enabled, to preserve legacy
45 /// behavior.
46 #[serde(default)]
47 in_core_extension: Option<String>,
43 }
48 }
44
49
45 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
50 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
46 /// an intermediate step in deserialization.
51 /// an intermediate step in deserialization.
47 #[derive(Clone, Debug, Deserialize)]
52 #[derive(Clone, Debug, Deserialize)]
48 struct RawDefaultConfigItem {
53 struct RawDefaultConfigItem {
49 section: String,
54 section: String,
50 name: String,
55 name: String,
51 default: Option<toml::Value>,
56 default: Option<toml::Value>,
52 #[serde(rename = "default-type")]
57 #[serde(rename = "default-type")]
53 default_type: Option<String>,
58 default_type: Option<String>,
54 #[serde(default)]
59 #[serde(default)]
55 priority: isize,
60 priority: isize,
56 #[serde(default)]
61 #[serde(default)]
57 generic: bool,
62 generic: bool,
58 #[serde(default)]
63 #[serde(default)]
59 alias: Vec<(String, String)>,
64 alias: Vec<(String, String)>,
60 #[serde(default)]
65 #[serde(default)]
61 experimental: bool,
66 experimental: bool,
62 #[serde(default)]
67 #[serde(default)]
63 documentation: String,
68 documentation: String,
69 #[serde(default)]
70 in_core_extension: Option<String>,
64 }
71 }
65
72
66 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
73 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
67 type Error = HgError;
74 type Error = HgError;
68
75
69 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
76 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
70 Ok(Self {
77 Ok(Self {
71 section: value.section,
78 section: value.section,
72 name: value.name,
79 name: value.name,
73 default: raw_default_to_concrete(
80 default: raw_default_to_concrete(
74 value.default_type,
81 value.default_type,
75 value.default,
82 value.default,
76 )?,
83 )?,
77 priority: if value.generic {
84 priority: if value.generic {
78 Some(value.priority)
85 Some(value.priority)
79 } else {
86 } else {
80 None
87 None
81 },
88 },
82 alias: value.alias,
89 alias: value.alias,
83 experimental: value.experimental,
90 experimental: value.experimental,
84 documentation: value.documentation,
91 documentation: value.documentation,
92 in_core_extension: value.in_core_extension,
85 })
93 })
86 }
94 }
87 }
95 }
88
96
89 impl DefaultConfigItem {
97 impl DefaultConfigItem {
90 fn is_generic(&self) -> bool {
98 fn is_generic(&self) -> bool {
91 self.priority.is_some()
99 self.priority.is_some()
92 }
100 }
101
102 pub fn in_core_extension(&self) -> Option<&str> {
103 self.in_core_extension.as_deref()
104 }
105
106 pub fn section(&self) -> &str {
107 self.section.as_ref()
108 }
93 }
109 }
94
110
95 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
111 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
96 type Error = HgError;
112 type Error = HgError;
97
113
98 fn try_from(
114 fn try_from(
99 value: &'a DefaultConfigItem,
115 value: &'a DefaultConfigItem,
100 ) -> Result<Option<&'a str>, Self::Error> {
116 ) -> Result<Option<&'a str>, Self::Error> {
101 match &value.default {
117 match &value.default {
102 Some(default) => {
118 Some(default) => {
103 let err = HgError::abort(
119 let err = HgError::abort(
104 format!(
120 format!(
105 "programming error: wrong query on config item '{}.{}'",
121 "programming error: wrong query on config item '{}.{}'",
106 value.section,
122 value.section,
107 value.name
123 value.name
108 ),
124 ),
109 exit_codes::ABORT,
125 exit_codes::ABORT,
110 Some(format!(
126 Some(format!(
111 "asked for '&str', type of default is '{}'",
127 "asked for '&str', type of default is '{}'",
112 default.type_str()
128 default.type_str()
113 )),
129 )),
114 );
130 );
115 match default {
131 match default {
116 DefaultConfigItemType::Primitive(toml::Value::String(
132 DefaultConfigItemType::Primitive(toml::Value::String(
117 s,
133 s,
118 )) => Ok(Some(s)),
134 )) => Ok(Some(s)),
119 _ => Err(err),
135 _ => Err(err),
120 }
136 }
121 }
137 }
122 None => Ok(None),
138 None => Ok(None),
123 }
139 }
124 }
140 }
125 }
141 }
126
142
127 impl TryFrom<&DefaultConfigItem> for Option<bool> {
143 impl TryFrom<&DefaultConfigItem> for Option<bool> {
128 type Error = HgError;
144 type Error = HgError;
129
145
130 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
146 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
131 match &value.default {
147 match &value.default {
132 Some(default) => {
148 Some(default) => {
133 let err = HgError::abort(
149 let err = HgError::abort(
134 format!(
150 format!(
135 "programming error: wrong query on config item '{}.{}'",
151 "programming error: wrong query on config item '{}.{}'",
136 value.section,
152 value.section,
137 value.name
153 value.name
138 ),
154 ),
139 exit_codes::ABORT,
155 exit_codes::ABORT,
140 Some(format!(
156 Some(format!(
141 "asked for 'bool', type of default is '{}'",
157 "asked for 'bool', type of default is '{}'",
142 default.type_str()
158 default.type_str()
143 )),
159 )),
144 );
160 );
145 match default {
161 match default {
146 DefaultConfigItemType::Primitive(
162 DefaultConfigItemType::Primitive(
147 toml::Value::Boolean(b),
163 toml::Value::Boolean(b),
148 ) => Ok(Some(*b)),
164 ) => Ok(Some(*b)),
149 _ => Err(err),
165 _ => Err(err),
150 }
166 }
151 }
167 }
152 None => Ok(Some(false)),
168 None => Ok(Some(false)),
153 }
169 }
154 }
170 }
155 }
171 }
156
172
157 impl TryFrom<&DefaultConfigItem> for Option<u32> {
173 impl TryFrom<&DefaultConfigItem> for Option<u32> {
158 type Error = HgError;
174 type Error = HgError;
159
175
160 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
176 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
161 match &value.default {
177 match &value.default {
162 Some(default) => {
178 Some(default) => {
163 let err = HgError::abort(
179 let err = HgError::abort(
164 format!(
180 format!(
165 "programming error: wrong query on config item '{}.{}'",
181 "programming error: wrong query on config item '{}.{}'",
166 value.section,
182 value.section,
167 value.name
183 value.name
168 ),
184 ),
169 exit_codes::ABORT,
185 exit_codes::ABORT,
170 Some(format!(
186 Some(format!(
171 "asked for 'u32', type of default is '{}'",
187 "asked for 'u32', type of default is '{}'",
172 default.type_str()
188 default.type_str()
173 )),
189 )),
174 );
190 );
175 match default {
191 match default {
176 DefaultConfigItemType::Primitive(
192 DefaultConfigItemType::Primitive(
177 toml::Value::Integer(b),
193 toml::Value::Integer(b),
178 ) => {
194 ) => {
179 Ok(Some((*b).try_into().expect("TOML integer to u32")))
195 Ok(Some((*b).try_into().expect("TOML integer to u32")))
180 }
196 }
181 _ => Err(err),
197 _ => Err(err),
182 }
198 }
183 }
199 }
184 None => Ok(None),
200 None => Ok(None),
185 }
201 }
186 }
202 }
187 }
203 }
188
204
189 impl TryFrom<&DefaultConfigItem> for Option<u64> {
205 impl TryFrom<&DefaultConfigItem> for Option<u64> {
190 type Error = HgError;
206 type Error = HgError;
191
207
192 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
208 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
193 match &value.default {
209 match &value.default {
194 Some(default) => {
210 Some(default) => {
195 let err = HgError::abort(
211 let err = HgError::abort(
196 format!(
212 format!(
197 "programming error: wrong query on config item '{}.{}'",
213 "programming error: wrong query on config item '{}.{}'",
198 value.section,
214 value.section,
199 value.name
215 value.name
200 ),
216 ),
201 exit_codes::ABORT,
217 exit_codes::ABORT,
202 Some(format!(
218 Some(format!(
203 "asked for 'u64', type of default is '{}'",
219 "asked for 'u64', type of default is '{}'",
204 default.type_str()
220 default.type_str()
205 )),
221 )),
206 );
222 );
207 match default {
223 match default {
208 DefaultConfigItemType::Primitive(
224 DefaultConfigItemType::Primitive(
209 toml::Value::Integer(b),
225 toml::Value::Integer(b),
210 ) => {
226 ) => {
211 Ok(Some((*b).try_into().expect("TOML integer to u64")))
227 Ok(Some((*b).try_into().expect("TOML integer to u64")))
212 }
228 }
213 _ => Err(err),
229 _ => Err(err),
214 }
230 }
215 }
231 }
216 None => Ok(None),
232 None => Ok(None),
217 }
233 }
218 }
234 }
219 }
235 }
220
236
221 /// Allows abstracting over more complex default values than just primitives.
237 /// Allows abstracting over more complex default values than just primitives.
222 /// The former `configitems.py` contained some dynamic code that is encoded
238 /// The former `configitems.py` contained some dynamic code that is encoded
223 /// in this enum.
239 /// in this enum.
224 #[derive(Debug, PartialEq, Clone, Deserialize)]
240 #[derive(Debug, PartialEq, Clone, Deserialize)]
225 pub enum DefaultConfigItemType {
241 pub enum DefaultConfigItemType {
226 /// Some primitive type (string, integer, boolean)
242 /// Some primitive type (string, integer, boolean)
227 Primitive(toml::Value),
243 Primitive(toml::Value),
228 /// A dynamic value that will be given by the code at runtime
244 /// A dynamic value that will be given by the code at runtime
229 Dynamic,
245 Dynamic,
230 /// An lazily-returned array (possibly only relevant in the Python impl)
246 /// An lazily-returned array (possibly only relevant in the Python impl)
231 /// Example: `lambda: [b"zstd", b"zlib"]`
247 /// Example: `lambda: [b"zstd", b"zlib"]`
232 Lambda(Vec<String>),
248 Lambda(Vec<String>),
233 /// For now, a special case for `web.encoding` that points to the
249 /// For now, a special case for `web.encoding` that points to the
234 /// `encoding.encoding` module in the Python impl so that local encoding
250 /// `encoding.encoding` module in the Python impl so that local encoding
235 /// is correctly resolved at runtime
251 /// is correctly resolved at runtime
236 LazyModule(String),
252 LazyModule(String),
237 ListType,
253 ListType,
238 }
254 }
239
255
240 impl DefaultConfigItemType {
256 impl DefaultConfigItemType {
241 pub fn type_str(&self) -> &str {
257 pub fn type_str(&self) -> &str {
242 match self {
258 match self {
243 DefaultConfigItemType::Primitive(primitive) => {
259 DefaultConfigItemType::Primitive(primitive) => {
244 primitive.type_str()
260 primitive.type_str()
245 }
261 }
246 DefaultConfigItemType::Dynamic => "dynamic",
262 DefaultConfigItemType::Dynamic => "dynamic",
247 DefaultConfigItemType::Lambda(_) => "lambda",
263 DefaultConfigItemType::Lambda(_) => "lambda",
248 DefaultConfigItemType::LazyModule(_) => "lazy_module",
264 DefaultConfigItemType::LazyModule(_) => "lazy_module",
249 DefaultConfigItemType::ListType => "list_type",
265 DefaultConfigItemType::ListType => "list_type",
250 }
266 }
251 }
267 }
252 }
268 }
253
269
254 /// Most of the fields are shared with [`DefaultConfigItem`].
270 /// Most of the fields are shared with [`DefaultConfigItem`].
255 #[derive(Debug, Clone, Deserialize)]
271 #[derive(Debug, Clone, Deserialize)]
256 #[serde(try_from = "RawTemplateItem")]
272 #[serde(try_from = "RawTemplateItem")]
257 struct TemplateItem {
273 struct TemplateItem {
258 suffix: String,
274 suffix: String,
259 default: Option<DefaultConfigItemType>,
275 default: Option<DefaultConfigItemType>,
260 priority: Option<isize>,
276 priority: Option<isize>,
261 #[serde(default)]
277 #[serde(default)]
262 alias: Vec<(String, String)>,
278 alias: Vec<(String, String)>,
263 #[serde(default)]
279 #[serde(default)]
264 experimental: bool,
280 experimental: bool,
265 #[serde(default)]
281 #[serde(default)]
266 documentation: String,
282 documentation: String,
267 }
283 }
268
284
269 /// Corresponds to the raw (i.e. on disk) representation of a template item.
285 /// Corresponds to the raw (i.e. on disk) representation of a template item.
270 /// Used as an intermediate step in deserialization.
286 /// Used as an intermediate step in deserialization.
271 #[derive(Clone, Debug, Deserialize)]
287 #[derive(Clone, Debug, Deserialize)]
272 struct RawTemplateItem {
288 struct RawTemplateItem {
273 suffix: String,
289 suffix: String,
274 default: Option<toml::Value>,
290 default: Option<toml::Value>,
275 #[serde(rename = "default-type")]
291 #[serde(rename = "default-type")]
276 default_type: Option<String>,
292 default_type: Option<String>,
277 #[serde(default)]
293 #[serde(default)]
278 priority: isize,
294 priority: isize,
279 #[serde(default)]
295 #[serde(default)]
280 generic: bool,
296 generic: bool,
281 #[serde(default)]
297 #[serde(default)]
282 alias: Vec<(String, String)>,
298 alias: Vec<(String, String)>,
283 #[serde(default)]
299 #[serde(default)]
284 experimental: bool,
300 experimental: bool,
285 #[serde(default)]
301 #[serde(default)]
286 documentation: String,
302 documentation: String,
287 }
303 }
288
304
289 impl TemplateItem {
305 impl TemplateItem {
290 fn into_default_item(
306 fn into_default_item(
291 self,
307 self,
292 application: TemplateApplication,
308 application: TemplateApplication,
293 ) -> DefaultConfigItem {
309 ) -> DefaultConfigItem {
294 DefaultConfigItem {
310 DefaultConfigItem {
295 section: application.section,
311 section: application.section,
296 name: application
312 name: application
297 .prefix
313 .prefix
298 .map(|prefix| format!("{}.{}", prefix, self.suffix))
314 .map(|prefix| format!("{}.{}", prefix, self.suffix))
299 .unwrap_or(self.suffix),
315 .unwrap_or(self.suffix),
300 default: self.default,
316 default: self.default,
301 priority: self.priority,
317 priority: self.priority,
302 alias: self.alias,
318 alias: self.alias,
303 experimental: self.experimental,
319 experimental: self.experimental,
304 documentation: self.documentation,
320 documentation: self.documentation,
321 in_core_extension: None,
305 }
322 }
306 }
323 }
307 }
324 }
308
325
309 impl TryFrom<RawTemplateItem> for TemplateItem {
326 impl TryFrom<RawTemplateItem> for TemplateItem {
310 type Error = HgError;
327 type Error = HgError;
311
328
312 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
329 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
313 Ok(Self {
330 Ok(Self {
314 suffix: value.suffix,
331 suffix: value.suffix,
315 default: raw_default_to_concrete(
332 default: raw_default_to_concrete(
316 value.default_type,
333 value.default_type,
317 value.default,
334 value.default,
318 )?,
335 )?,
319 priority: if value.generic {
336 priority: if value.generic {
320 Some(value.priority)
337 Some(value.priority)
321 } else {
338 } else {
322 None
339 None
323 },
340 },
324 alias: value.alias,
341 alias: value.alias,
325 experimental: value.experimental,
342 experimental: value.experimental,
326 documentation: value.documentation,
343 documentation: value.documentation,
327 })
344 })
328 }
345 }
329 }
346 }
330
347
331 /// Transforms the on-disk string-based representation of complex default types
348 /// Transforms the on-disk string-based representation of complex default types
332 /// to the concrete [`DefaultconfigItemType`].
349 /// to the concrete [`DefaultconfigItemType`].
333 fn raw_default_to_concrete(
350 fn raw_default_to_concrete(
334 default_type: Option<String>,
351 default_type: Option<String>,
335 default: Option<toml::Value>,
352 default: Option<toml::Value>,
336 ) -> Result<Option<DefaultConfigItemType>, HgError> {
353 ) -> Result<Option<DefaultConfigItemType>, HgError> {
337 Ok(match default_type.as_deref() {
354 Ok(match default_type.as_deref() {
338 None => default.as_ref().map(|default| {
355 None => default.as_ref().map(|default| {
339 DefaultConfigItemType::Primitive(default.to_owned())
356 DefaultConfigItemType::Primitive(default.to_owned())
340 }),
357 }),
341 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
358 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
342 Some("list_type") => Some(DefaultConfigItemType::ListType),
359 Some("list_type") => Some(DefaultConfigItemType::ListType),
343 Some("lambda") => match &default {
360 Some("lambda") => match &default {
344 Some(default) => Some(DefaultConfigItemType::Lambda(
361 Some(default) => Some(DefaultConfigItemType::Lambda(
345 default.to_owned().try_into().map_err(|e| {
362 default.to_owned().try_into().map_err(|e| {
346 HgError::abort(
363 HgError::abort(
347 e.to_string(),
364 e.to_string(),
348 exit_codes::ABORT,
365 exit_codes::ABORT,
349 Some("Check 'mercurial/configitems.toml'".into()),
366 Some("Check 'mercurial/configitems.toml'".into()),
350 )
367 )
351 })?,
368 })?,
352 )),
369 )),
353 None => {
370 None => {
354 return Err(HgError::abort(
371 return Err(HgError::abort(
355 "lambda defined with no return value".to_string(),
372 "lambda defined with no return value".to_string(),
356 exit_codes::ABORT,
373 exit_codes::ABORT,
357 Some("Check 'mercurial/configitems.toml'".into()),
374 Some("Check 'mercurial/configitems.toml'".into()),
358 ))
375 ))
359 }
376 }
360 },
377 },
361 Some("lazy_module") => match &default {
378 Some("lazy_module") => match &default {
362 Some(default) => {
379 Some(default) => {
363 Some(DefaultConfigItemType::LazyModule(match default {
380 Some(DefaultConfigItemType::LazyModule(match default {
364 toml::Value::String(module) => module.to_owned(),
381 toml::Value::String(module) => module.to_owned(),
365 _ => {
382 _ => {
366 return Err(HgError::abort(
383 return Err(HgError::abort(
367 "lazy_module module name should be a string"
384 "lazy_module module name should be a string"
368 .to_string(),
385 .to_string(),
369 exit_codes::ABORT,
386 exit_codes::ABORT,
370 Some("Check 'mercurial/configitems.toml'".into()),
387 Some("Check 'mercurial/configitems.toml'".into()),
371 ))
388 ))
372 }
389 }
373 }))
390 }))
374 }
391 }
375 None => {
392 None => {
376 return Err(HgError::abort(
393 return Err(HgError::abort(
377 "lazy_module should have a default value".to_string(),
394 "lazy_module should have a default value".to_string(),
378 exit_codes::ABORT,
395 exit_codes::ABORT,
379 Some("Check 'mercurial/configitems.toml'".into()),
396 Some("Check 'mercurial/configitems.toml'".into()),
380 ))
397 ))
381 }
398 }
382 },
399 },
383 Some(invalid) => {
400 Some(invalid) => {
384 return Err(HgError::abort(
401 return Err(HgError::abort(
385 format!("invalid default_type '{}'", invalid),
402 format!("invalid default_type '{}'", invalid),
386 exit_codes::ABORT,
403 exit_codes::ABORT,
387 Some("Check 'mercurial/configitems.toml'".into()),
404 Some("Check 'mercurial/configitems.toml'".into()),
388 ))
405 ))
389 }
406 }
390 })
407 })
391 }
408 }
392
409
393 #[derive(Debug, Clone, Deserialize)]
410 #[derive(Debug, Clone, Deserialize)]
394 struct TemplateApplication {
411 struct TemplateApplication {
395 template: String,
412 template: String,
396 section: String,
413 section: String,
397 #[serde(default)]
414 #[serde(default)]
398 prefix: Option<String>,
415 prefix: Option<String>,
399 }
416 }
400
417
401 /// Represents the (dynamic) set of default core Mercurial config items from
418 /// Represents the (dynamic) set of default core Mercurial config items from
402 /// `mercurial/configitems.toml`.
419 /// `mercurial/configitems.toml`.
403 #[derive(Clone, Debug, Default)]
420 #[derive(Clone, Debug, Default)]
404 pub struct DefaultConfig {
421 pub struct DefaultConfig {
405 /// Mapping of section -> (mapping of name -> item)
422 /// Mapping of section -> (mapping of name -> item)
406 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
423 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
407 }
424 }
408
425
409 impl DefaultConfig {
426 impl DefaultConfig {
410 pub fn empty() -> DefaultConfig {
427 pub fn empty() -> DefaultConfig {
411 Self {
428 Self {
412 items: Default::default(),
429 items: Default::default(),
413 }
430 }
414 }
431 }
415
432
416 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
433 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
417 #[logging_timer::time("trace")]
434 #[logging_timer::time("trace")]
418 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
435 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
419 let mut from_file: ConfigItems =
436 let mut from_file: ConfigItems =
420 toml::from_str(contents).map_err(|e| {
437 toml::from_str(contents).map_err(|e| {
421 HgError::abort(
438 HgError::abort(
422 e.to_string(),
439 e.to_string(),
423 exit_codes::ABORT,
440 exit_codes::ABORT,
424 Some("Check 'mercurial/configitems.toml'".into()),
441 Some("Check 'mercurial/configitems.toml'".into()),
425 )
442 )
426 })?;
443 })?;
427
444
428 let mut flat_items = from_file.items;
445 let mut flat_items = from_file.items;
429
446
430 for application in from_file.template_applications.drain(..) {
447 for application in from_file.template_applications.drain(..) {
431 match from_file.templates.get(&application.template) {
448 match from_file.templates.get(&application.template) {
432 None => return Err(
449 None => return Err(
433 HgError::abort(
450 HgError::abort(
434 format!(
451 format!(
435 "template application refers to undefined template '{}'",
452 "template application refers to undefined template '{}'",
436 application.template
453 application.template
437 ),
454 ),
438 exit_codes::ABORT,
455 exit_codes::ABORT,
439 Some("Check 'mercurial/configitems.toml'".into())
456 Some("Check 'mercurial/configitems.toml'".into())
440 )
457 )
441 ),
458 ),
442 Some(template_items) => {
459 Some(template_items) => {
443 for template_item in template_items {
460 for template_item in template_items {
444 flat_items.push(
461 flat_items.push(
445 template_item
462 template_item
446 .clone()
463 .clone()
447 .into_default_item(application.clone()),
464 .into_default_item(application.clone()),
448 )
465 )
449 }
466 }
450 }
467 }
451 };
468 };
452 }
469 }
453
470
454 let items = flat_items.into_iter().fold(
471 let items = flat_items.into_iter().fold(
455 FastHashMap::default(),
472 FastHashMap::default(),
456 |mut acc, item| {
473 |mut acc, item| {
457 acc.entry(item.section.to_owned())
474 acc.entry(item.section.to_owned())
458 .or_insert_with(|| {
475 .or_insert_with(|| {
459 let mut section = FastHashMap::default();
476 let mut section = FastHashMap::default();
460 section.insert(item.name.to_owned(), item.to_owned());
477 section.insert(item.name.to_owned(), item.to_owned());
461 section
478 section
462 })
479 })
463 .insert(item.name.to_owned(), item);
480 .insert(item.name.to_owned(), item);
464 acc
481 acc
465 },
482 },
466 );
483 );
467
484
468 Ok(Self { items })
485 Ok(Self { items })
469 }
486 }
470
487
471 /// Return the default config item that matches `section` and `item`.
488 /// Return the default config item that matches `section` and `item`.
472 pub fn get(
489 pub fn get(
473 &self,
490 &self,
474 section: &[u8],
491 section: &[u8],
475 item: &[u8],
492 item: &[u8],
476 ) -> Option<&DefaultConfigItem> {
493 ) -> Option<&DefaultConfigItem> {
477 // Core items must be valid UTF-8
494 // Core items must be valid UTF-8
478 let section = String::from_utf8_lossy(section);
495 let section = String::from_utf8_lossy(section);
479 let section_map = self.items.get(section.as_ref())?;
496 let section_map = self.items.get(section.as_ref())?;
480 let item_name_lossy = String::from_utf8_lossy(item);
497 let item_name_lossy = String::from_utf8_lossy(item);
481 match section_map.get(item_name_lossy.as_ref()) {
498 match section_map.get(item_name_lossy.as_ref()) {
482 Some(item) => Some(item),
499 Some(item) => Some(item),
483 None => {
500 None => {
484 for generic_item in section_map
501 for generic_item in section_map
485 .values()
502 .values()
486 .filter(|item| item.is_generic())
503 .filter(|item| item.is_generic())
487 .sorted_by_key(|item| match item.priority {
504 .sorted_by_key(|item| match item.priority {
488 Some(priority) => (priority, &item.name),
505 Some(priority) => (priority, &item.name),
489 _ => unreachable!(),
506 _ => unreachable!(),
490 })
507 })
491 {
508 {
492 if regex::bytes::Regex::new(&generic_item.name)
509 if regex::bytes::Regex::new(&generic_item.name)
493 .expect("invalid regex in configitems")
510 .expect("invalid regex in configitems")
494 .is_match(item)
511 .is_match(item)
495 {
512 {
496 return Some(generic_item);
513 return Some(generic_item);
497 }
514 }
498 }
515 }
499 None
516 None
500 }
517 }
501 }
518 }
502 }
519 }
503 }
520 }
504
521
505 #[cfg(test)]
522 #[cfg(test)]
506 mod tests {
523 mod tests {
507 use crate::config::config_items::{
524 use crate::config::config_items::{
508 DefaultConfigItem, DefaultConfigItemType,
525 DefaultConfigItem, DefaultConfigItemType,
509 };
526 };
510
527
511 use super::DefaultConfig;
528 use super::DefaultConfig;
512
529
513 #[test]
530 #[test]
514 fn test_config_read() {
531 fn test_config_read() {
515 let contents = r#"
532 let contents = r#"
516 [[items]]
533 [[items]]
517 section = "alias"
534 section = "alias"
518 name = "abcd.*"
535 name = "abcd.*"
519 default = 3
536 default = 3
520 generic = true
537 generic = true
521 priority = -1
538 priority = -1
522
539
523 [[items]]
540 [[items]]
524 section = "alias"
541 section = "alias"
525 name = ".*"
542 name = ".*"
526 default-type = "dynamic"
543 default-type = "dynamic"
527 generic = true
544 generic = true
528
545
529 [[items]]
546 [[items]]
530 section = "cmdserver"
547 section = "cmdserver"
531 name = "track-log"
548 name = "track-log"
532 default-type = "lambda"
549 default-type = "lambda"
533 default = [ "chgserver", "cmdserver", "repocache",]
550 default = [ "chgserver", "cmdserver", "repocache",]
534
551
535 [[items]]
552 [[items]]
536 section = "chgserver"
553 section = "chgserver"
537 name = "idletimeout"
554 name = "idletimeout"
538 default = 3600
555 default = 3600
539
556
540 [[items]]
557 [[items]]
541 section = "cmdserver"
558 section = "cmdserver"
542 name = "message-encodings"
559 name = "message-encodings"
543 default-type = "list_type"
560 default-type = "list_type"
544
561
545 [[items]]
562 [[items]]
546 section = "web"
563 section = "web"
547 name = "encoding"
564 name = "encoding"
548 default-type = "lazy_module"
565 default-type = "lazy_module"
549 default = "encoding.encoding"
566 default = "encoding.encoding"
550
567
551 [[items]]
568 [[items]]
552 section = "command-templates"
569 section = "command-templates"
553 name = "graphnode"
570 name = "graphnode"
554 alias = [["ui", "graphnodetemplate"]]
571 alias = [["ui", "graphnodetemplate"]]
555 documentation = """This is a docstring.
572 documentation = """This is a docstring.
556 This is another line \
573 This is another line \
557 but this is not."""
574 but this is not."""
558
575
559 [[items]]
576 [[items]]
560 section = "censor"
577 section = "censor"
561 name = "policy"
578 name = "policy"
562 default = "abort"
579 default = "abort"
563 experimental = true
580 experimental = true
564
581
565 [[template-applications]]
582 [[template-applications]]
566 template = "diff-options"
583 template = "diff-options"
567 section = "commands"
584 section = "commands"
568 prefix = "revert.interactive"
585 prefix = "revert.interactive"
569
586
570 [[template-applications]]
587 [[template-applications]]
571 template = "diff-options"
588 template = "diff-options"
572 section = "diff"
589 section = "diff"
573
590
574 [templates]
591 [templates]
575 [[templates.diff-options]]
592 [[templates.diff-options]]
576 suffix = "nodates"
593 suffix = "nodates"
577 default = false
594 default = false
578
595
579 [[templates.diff-options]]
596 [[templates.diff-options]]
580 suffix = "showfunc"
597 suffix = "showfunc"
581 default = false
598 default = false
582
599
583 [[templates.diff-options]]
600 [[templates.diff-options]]
584 suffix = "unified"
601 suffix = "unified"
585 "#;
602 "#;
586 let res = DefaultConfig::from_contents(contents);
603 let res = DefaultConfig::from_contents(contents);
587 let config = match res {
604 let config = match res {
588 Ok(config) => config,
605 Ok(config) => config,
589 Err(e) => panic!("{}", e),
606 Err(e) => panic!("{}", e),
590 };
607 };
591 let expected = DefaultConfigItem {
608 let expected = DefaultConfigItem {
592 section: "censor".into(),
609 section: "censor".into(),
593 name: "policy".into(),
610 name: "policy".into(),
594 default: Some(DefaultConfigItemType::Primitive("abort".into())),
611 default: Some(DefaultConfigItemType::Primitive("abort".into())),
595 priority: None,
612 priority: None,
596 alias: vec![],
613 alias: vec![],
597 experimental: true,
614 experimental: true,
598 documentation: "".into(),
615 documentation: "".into(),
616 in_core_extension: None,
599 };
617 };
600 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
618 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
601
619
602 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
620 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
603 // `abcd.*` has priority, so it should match first.
621 // `abcd.*` has priority, so it should match first.
604 let expected = DefaultConfigItem {
622 let expected = DefaultConfigItem {
605 section: "alias".into(),
623 section: "alias".into(),
606 name: "abcd.*".into(),
624 name: "abcd.*".into(),
607 default: Some(DefaultConfigItemType::Primitive(3.into())),
625 default: Some(DefaultConfigItemType::Primitive(3.into())),
608 priority: Some(-1),
626 priority: Some(-1),
609 alias: vec![],
627 alias: vec![],
610 experimental: false,
628 experimental: false,
611 documentation: "".into(),
629 documentation: "".into(),
630 in_core_extension: None,
612 };
631 };
613 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
632 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
614
633
615 //... but if it doesn't, we should fallback to `.*`
634 //... but if it doesn't, we should fallback to `.*`
616 let expected = DefaultConfigItem {
635 let expected = DefaultConfigItem {
617 section: "alias".into(),
636 section: "alias".into(),
618 name: ".*".into(),
637 name: ".*".into(),
619 default: Some(DefaultConfigItemType::Dynamic),
638 default: Some(DefaultConfigItemType::Dynamic),
620 priority: Some(0),
639 priority: Some(0),
621 alias: vec![],
640 alias: vec![],
622 experimental: false,
641 experimental: false,
623 documentation: "".into(),
642 documentation: "".into(),
643 in_core_extension: None,
624 };
644 };
625 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
645 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
626
646
627 let expected = DefaultConfigItem {
647 let expected = DefaultConfigItem {
628 section: "chgserver".into(),
648 section: "chgserver".into(),
629 name: "idletimeout".into(),
649 name: "idletimeout".into(),
630 default: Some(DefaultConfigItemType::Primitive(3600.into())),
650 default: Some(DefaultConfigItemType::Primitive(3600.into())),
631 priority: None,
651 priority: None,
632 alias: vec![],
652 alias: vec![],
633 experimental: false,
653 experimental: false,
634 documentation: "".into(),
654 documentation: "".into(),
655 in_core_extension: None,
635 };
656 };
636 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
657 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
637
658
638 let expected = DefaultConfigItem {
659 let expected = DefaultConfigItem {
639 section: "cmdserver".into(),
660 section: "cmdserver".into(),
640 name: "track-log".into(),
661 name: "track-log".into(),
641 default: Some(DefaultConfigItemType::Lambda(vec![
662 default: Some(DefaultConfigItemType::Lambda(vec![
642 "chgserver".into(),
663 "chgserver".into(),
643 "cmdserver".into(),
664 "cmdserver".into(),
644 "repocache".into(),
665 "repocache".into(),
645 ])),
666 ])),
646 priority: None,
667 priority: None,
647 alias: vec![],
668 alias: vec![],
648 experimental: false,
669 experimental: false,
649 documentation: "".into(),
670 documentation: "".into(),
671 in_core_extension: None,
650 };
672 };
651 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
673 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
652
674
653 let expected = DefaultConfigItem {
675 let expected = DefaultConfigItem {
654 section: "command-templates".into(),
676 section: "command-templates".into(),
655 name: "graphnode".into(),
677 name: "graphnode".into(),
656 default: None,
678 default: None,
657 priority: None,
679 priority: None,
658 alias: vec![("ui".into(), "graphnodetemplate".into())],
680 alias: vec![("ui".into(), "graphnodetemplate".into())],
659 experimental: false,
681 experimental: false,
660 documentation:
682 documentation:
661 "This is a docstring.\nThis is another line but this is not."
683 "This is a docstring.\nThis is another line but this is not."
662 .into(),
684 .into(),
685 in_core_extension: None,
663 };
686 };
664 assert_eq!(
687 assert_eq!(
665 config.get(b"command-templates", b"graphnode"),
688 config.get(b"command-templates", b"graphnode"),
666 Some(&expected)
689 Some(&expected)
667 );
690 );
668 }
691 }
669 }
692 }
@@ -1,728 +1,738 b''
1 // config.rs
1 // config.rs
2 //
2 //
3 // Copyright 2020
3 // Copyright 2020
4 // Valentin Gatien-Baron,
4 // Valentin Gatien-Baron,
5 // Raphaël Gomès <rgomes@octobus.net>
5 // Raphaël Gomès <rgomes@octobus.net>
6 //
6 //
7 // This software may be used and distributed according to the terms of the
7 // This software may be used and distributed according to the terms of the
8 // GNU General Public License version 2 or any later version.
8 // GNU General Public License version 2 or any later version.
9
9
10 //! Mercurial config parsing and interfaces.
10 //! Mercurial config parsing and interfaces.
11
11
12 pub mod config_items;
12 pub mod config_items;
13 mod layer;
13 mod layer;
14 mod plain_info;
14 mod plain_info;
15 mod values;
15 mod values;
16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
17 use lazy_static::lazy_static;
17 use lazy_static::lazy_static;
18 pub use plain_info::PlainInfo;
18 pub use plain_info::PlainInfo;
19
19
20 use self::config_items::DefaultConfig;
20 use self::config_items::DefaultConfig;
21 use self::config_items::DefaultConfigItem;
21 use self::config_items::DefaultConfigItem;
22 use self::layer::ConfigLayer;
22 use self::layer::ConfigLayer;
23 use self::layer::ConfigValue;
23 use self::layer::ConfigValue;
24 use crate::errors::HgError;
24 use crate::errors::HgError;
25 use crate::errors::{HgResultExt, IoResultExt};
25 use crate::errors::{HgResultExt, IoResultExt};
26 use crate::utils::files::get_bytes_from_os_str;
26 use crate::utils::files::get_bytes_from_os_str;
27 use format_bytes::{write_bytes, DisplayBytes};
27 use format_bytes::{write_bytes, DisplayBytes};
28 use std::collections::HashSet;
28 use std::collections::HashSet;
29 use std::env;
29 use std::env;
30 use std::fmt;
30 use std::fmt;
31 use std::path::{Path, PathBuf};
31 use std::path::{Path, PathBuf};
32 use std::str;
32 use std::str;
33
33
34 lazy_static! {
34 lazy_static! {
35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
36 DefaultConfig::from_contents(include_str!(
36 DefaultConfig::from_contents(include_str!(
37 "../../../../mercurial/configitems.toml"
37 "../../../../mercurial/configitems.toml"
38 ))
38 ))
39 };
39 };
40 }
40 }
41
41
42 /// Holds the config values for the current repository
42 /// Holds the config values for the current repository
43 /// TODO update this docstring once we support more sources
43 /// TODO update this docstring once we support more sources
44 #[derive(Clone)]
44 #[derive(Clone)]
45 pub struct Config {
45 pub struct Config {
46 layers: Vec<layer::ConfigLayer>,
46 layers: Vec<layer::ConfigLayer>,
47 plain: PlainInfo,
47 plain: PlainInfo,
48 }
48 }
49
49
50 impl DisplayBytes for Config {
50 impl DisplayBytes for Config {
51 fn display_bytes(
51 fn display_bytes(
52 &self,
52 &self,
53 out: &mut dyn std::io::Write,
53 out: &mut dyn std::io::Write,
54 ) -> std::io::Result<()> {
54 ) -> std::io::Result<()> {
55 for (index, layer) in self.layers.iter().rev().enumerate() {
55 for (index, layer) in self.layers.iter().rev().enumerate() {
56 write_bytes!(
56 write_bytes!(
57 out,
57 out,
58 b"==== Layer {} (trusted: {}) ====\n{}",
58 b"==== Layer {} (trusted: {}) ====\n{}",
59 index,
59 index,
60 if layer.trusted {
60 if layer.trusted {
61 &b"yes"[..]
61 &b"yes"[..]
62 } else {
62 } else {
63 &b"no"[..]
63 &b"no"[..]
64 },
64 },
65 layer
65 layer
66 )?;
66 )?;
67 }
67 }
68 Ok(())
68 Ok(())
69 }
69 }
70 }
70 }
71
71
72 pub enum ConfigSource {
72 pub enum ConfigSource {
73 /// Absolute path to a config file
73 /// Absolute path to a config file
74 AbsPath(PathBuf),
74 AbsPath(PathBuf),
75 /// Already parsed (from the CLI, env, Python resources, etc.)
75 /// Already parsed (from the CLI, env, Python resources, etc.)
76 Parsed(layer::ConfigLayer),
76 Parsed(layer::ConfigLayer),
77 }
77 }
78
78
79 #[derive(Debug)]
79 #[derive(Debug)]
80 pub struct ConfigValueParseErrorDetails {
80 pub struct ConfigValueParseErrorDetails {
81 pub origin: ConfigOrigin,
81 pub origin: ConfigOrigin,
82 pub line: Option<usize>,
82 pub line: Option<usize>,
83 pub section: Vec<u8>,
83 pub section: Vec<u8>,
84 pub item: Vec<u8>,
84 pub item: Vec<u8>,
85 pub value: Vec<u8>,
85 pub value: Vec<u8>,
86 pub expected_type: &'static str,
86 pub expected_type: &'static str,
87 }
87 }
88
88
89 // boxed to avoid very large Result types
89 // boxed to avoid very large Result types
90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
91
91
92 impl fmt::Display for ConfigValueParseError {
92 impl fmt::Display for ConfigValueParseError {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 // TODO: add origin and line number information, here and in
94 // TODO: add origin and line number information, here and in
95 // corresponding python code
95 // corresponding python code
96 write!(
96 write!(
97 f,
97 f,
98 "config error: {}.{} is not a {} ('{}')",
98 "config error: {}.{} is not a {} ('{}')",
99 String::from_utf8_lossy(&self.section),
99 String::from_utf8_lossy(&self.section),
100 String::from_utf8_lossy(&self.item),
100 String::from_utf8_lossy(&self.item),
101 self.expected_type,
101 self.expected_type,
102 String::from_utf8_lossy(&self.value)
102 String::from_utf8_lossy(&self.value)
103 )
103 )
104 }
104 }
105 }
105 }
106
106
107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
109 // duplication with [_applyconfig] in [ui.py],
109 // duplication with [_applyconfig] in [ui.py],
110 if !plain.is_plain() {
110 if !plain.is_plain() {
111 return false;
111 return false;
112 }
112 }
113 if section == b"alias" {
113 if section == b"alias" {
114 return plain.plainalias();
114 return plain.plainalias();
115 }
115 }
116 if section == b"revsetalias" {
116 if section == b"revsetalias" {
117 return plain.plainrevsetalias();
117 return plain.plainrevsetalias();
118 }
118 }
119 if section == b"templatealias" {
119 if section == b"templatealias" {
120 return plain.plaintemplatealias();
120 return plain.plaintemplatealias();
121 }
121 }
122 if section == b"ui" {
122 if section == b"ui" {
123 let to_delete: &[&[u8]] = &[
123 let to_delete: &[&[u8]] = &[
124 b"debug",
124 b"debug",
125 b"fallbackencoding",
125 b"fallbackencoding",
126 b"quiet",
126 b"quiet",
127 b"slash",
127 b"slash",
128 b"logtemplate",
128 b"logtemplate",
129 b"message-output",
129 b"message-output",
130 b"statuscopies",
130 b"statuscopies",
131 b"style",
131 b"style",
132 b"traceback",
132 b"traceback",
133 b"verbose",
133 b"verbose",
134 ];
134 ];
135 return to_delete.contains(&item);
135 return to_delete.contains(&item);
136 }
136 }
137 let sections_to_delete: &[&[u8]] =
137 let sections_to_delete: &[&[u8]] =
138 &[b"defaults", b"commands", b"command-templates"];
138 &[b"defaults", b"commands", b"command-templates"];
139 sections_to_delete.contains(&section)
139 sections_to_delete.contains(&section)
140 }
140 }
141
141
142 impl Config {
142 impl Config {
143 /// The configuration to use when printing configuration-loading errors
143 /// The configuration to use when printing configuration-loading errors
144 pub fn empty() -> Self {
144 pub fn empty() -> Self {
145 Self {
145 Self {
146 layers: Vec::new(),
146 layers: Vec::new(),
147 plain: PlainInfo::empty(),
147 plain: PlainInfo::empty(),
148 }
148 }
149 }
149 }
150
150
151 /// Load system and user configuration from various files.
151 /// Load system and user configuration from various files.
152 ///
152 ///
153 /// This is also affected by some environment variables.
153 /// This is also affected by some environment variables.
154 pub fn load_non_repo() -> Result<Self, ConfigError> {
154 pub fn load_non_repo() -> Result<Self, ConfigError> {
155 let mut config = Self::empty();
155 let mut config = Self::empty();
156 let opt_rc_path = env::var_os("HGRCPATH");
156 let opt_rc_path = env::var_os("HGRCPATH");
157 // HGRCPATH replaces system config
157 // HGRCPATH replaces system config
158 if opt_rc_path.is_none() {
158 if opt_rc_path.is_none() {
159 config.add_system_config()?
159 config.add_system_config()?
160 }
160 }
161
161
162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
165
165
166 // These are set by `run-tests.py --rhg` to enable fallback for the
166 // These are set by `run-tests.py --rhg` to enable fallback for the
167 // entire test suite. Alternatives would be setting configuration
167 // entire test suite. Alternatives would be setting configuration
168 // through `$HGRCPATH` but some tests override that, or changing the
168 // through `$HGRCPATH` but some tests override that, or changing the
169 // `hg` shell alias to include `--config` but that disrupts tests that
169 // `hg` shell alias to include `--config` but that disrupts tests that
170 // print command lines and check expected output.
170 // print command lines and check expected output.
171 config.add_for_environment_variable(
171 config.add_for_environment_variable(
172 "RHG_ON_UNSUPPORTED",
172 "RHG_ON_UNSUPPORTED",
173 b"rhg",
173 b"rhg",
174 b"on-unsupported",
174 b"on-unsupported",
175 );
175 );
176 config.add_for_environment_variable(
176 config.add_for_environment_variable(
177 "RHG_FALLBACK_EXECUTABLE",
177 "RHG_FALLBACK_EXECUTABLE",
178 b"rhg",
178 b"rhg",
179 b"fallback-executable",
179 b"fallback-executable",
180 );
180 );
181
181
182 // HGRCPATH replaces user config
182 // HGRCPATH replaces user config
183 if opt_rc_path.is_none() {
183 if opt_rc_path.is_none() {
184 config.add_user_config()?
184 config.add_user_config()?
185 }
185 }
186 if let Some(rc_path) = &opt_rc_path {
186 if let Some(rc_path) = &opt_rc_path {
187 for path in env::split_paths(rc_path) {
187 for path in env::split_paths(rc_path) {
188 if !path.as_os_str().is_empty() {
188 if !path.as_os_str().is_empty() {
189 if path.is_dir() {
189 if path.is_dir() {
190 config.add_trusted_dir(&path)?
190 config.add_trusted_dir(&path)?
191 } else {
191 } else {
192 config.add_trusted_file(&path)?
192 config.add_trusted_file(&path)?
193 }
193 }
194 }
194 }
195 }
195 }
196 }
196 }
197 Ok(config)
197 Ok(config)
198 }
198 }
199
199
200 pub fn load_cli_args(
200 pub fn load_cli_args(
201 &mut self,
201 &mut self,
202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
203 color_arg: Option<Vec<u8>>,
203 color_arg: Option<Vec<u8>>,
204 ) -> Result<(), ConfigError> {
204 ) -> Result<(), ConfigError> {
205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
206 self.layers.push(layer)
206 self.layers.push(layer)
207 }
207 }
208 if let Some(arg) = color_arg {
208 if let Some(arg) = color_arg {
209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
211 self.layers.push(layer)
211 self.layers.push(layer)
212 }
212 }
213 Ok(())
213 Ok(())
214 }
214 }
215
215
216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
217 if let Some(entries) = std::fs::read_dir(path)
217 if let Some(entries) = std::fs::read_dir(path)
218 .when_reading_file(path)
218 .when_reading_file(path)
219 .io_not_found_as_none()?
219 .io_not_found_as_none()?
220 {
220 {
221 let mut file_paths = entries
221 let mut file_paths = entries
222 .map(|result| {
222 .map(|result| {
223 result.when_reading_file(path).map(|entry| entry.path())
223 result.when_reading_file(path).map(|entry| entry.path())
224 })
224 })
225 .collect::<Result<Vec<_>, _>>()?;
225 .collect::<Result<Vec<_>, _>>()?;
226 file_paths.sort();
226 file_paths.sort();
227 for file_path in &file_paths {
227 for file_path in &file_paths {
228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
229 self.add_trusted_file(file_path)?
229 self.add_trusted_file(file_path)?
230 }
230 }
231 }
231 }
232 }
232 }
233 Ok(())
233 Ok(())
234 }
234 }
235
235
236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
237 if let Some(data) = std::fs::read(path)
237 if let Some(data) = std::fs::read(path)
238 .when_reading_file(path)
238 .when_reading_file(path)
239 .io_not_found_as_none()?
239 .io_not_found_as_none()?
240 {
240 {
241 self.layers.extend(ConfigLayer::parse(path, &data)?)
241 self.layers.extend(ConfigLayer::parse(path, &data)?)
242 }
242 }
243 Ok(())
243 Ok(())
244 }
244 }
245
245
246 fn add_for_environment_variable(
246 fn add_for_environment_variable(
247 &mut self,
247 &mut self,
248 var: &str,
248 var: &str,
249 section: &[u8],
249 section: &[u8],
250 key: &[u8],
250 key: &[u8],
251 ) {
251 ) {
252 if let Some(value) = env::var_os(var) {
252 if let Some(value) = env::var_os(var) {
253 let origin = layer::ConfigOrigin::Environment(var.into());
253 let origin = layer::ConfigOrigin::Environment(var.into());
254 let mut layer = ConfigLayer::new(origin);
254 let mut layer = ConfigLayer::new(origin);
255 layer.add(
255 layer.add(
256 section.to_owned(),
256 section.to_owned(),
257 key.to_owned(),
257 key.to_owned(),
258 get_bytes_from_os_str(value),
258 get_bytes_from_os_str(value),
259 None,
259 None,
260 );
260 );
261 self.layers.push(layer)
261 self.layers.push(layer)
262 }
262 }
263 }
263 }
264
264
265 #[cfg(unix)] // TODO: other platforms
265 #[cfg(unix)] // TODO: other platforms
266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
268 let etc = prefix.join("etc").join("mercurial");
268 let etc = prefix.join("etc").join("mercurial");
269 self.add_trusted_file(&etc.join("hgrc"))?;
269 self.add_trusted_file(&etc.join("hgrc"))?;
270 self.add_trusted_dir(&etc.join("hgrc.d"))
270 self.add_trusted_dir(&etc.join("hgrc.d"))
271 };
271 };
272 let root = Path::new("/");
272 let root = Path::new("/");
273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
274 // instead? TODO: can this be a relative path?
274 // instead? TODO: can this be a relative path?
275 let hg = crate::utils::current_exe()?;
275 let hg = crate::utils::current_exe()?;
276 // TODO: this order (per-installation then per-system) matches
276 // TODO: this order (per-installation then per-system) matches
277 // `systemrcpath()` in `mercurial/scmposix.py`, but
277 // `systemrcpath()` in `mercurial/scmposix.py`, but
278 // `mercurial/helptext/config.txt` suggests it should be reversed
278 // `mercurial/helptext/config.txt` suggests it should be reversed
279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
280 if installation_prefix != root {
280 if installation_prefix != root {
281 add_for_prefix(installation_prefix)?
281 add_for_prefix(installation_prefix)?
282 }
282 }
283 }
283 }
284 add_for_prefix(root)?;
284 add_for_prefix(root)?;
285 Ok(())
285 Ok(())
286 }
286 }
287
287
288 #[cfg(unix)] // TODO: other plateforms
288 #[cfg(unix)] // TODO: other plateforms
289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
290 let opt_home = home::home_dir();
290 let opt_home = home::home_dir();
291 if let Some(home) = &opt_home {
291 if let Some(home) = &opt_home {
292 self.add_trusted_file(&home.join(".hgrc"))?
292 self.add_trusted_file(&home.join(".hgrc"))?
293 }
293 }
294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
295 if !darwin {
295 if !darwin {
296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
297 .map(PathBuf::from)
297 .map(PathBuf::from)
298 .or_else(|| opt_home.map(|home| home.join(".config")))
298 .or_else(|| opt_home.map(|home| home.join(".config")))
299 {
299 {
300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
301 }
301 }
302 }
302 }
303 Ok(())
303 Ok(())
304 }
304 }
305
305
306 /// Loads in order, which means that the precedence is the same
306 /// Loads in order, which means that the precedence is the same
307 /// as the order of `sources`.
307 /// as the order of `sources`.
308 pub fn load_from_explicit_sources(
308 pub fn load_from_explicit_sources(
309 sources: Vec<ConfigSource>,
309 sources: Vec<ConfigSource>,
310 ) -> Result<Self, ConfigError> {
310 ) -> Result<Self, ConfigError> {
311 let mut layers = vec![];
311 let mut layers = vec![];
312
312
313 for source in sources.into_iter() {
313 for source in sources.into_iter() {
314 match source {
314 match source {
315 ConfigSource::Parsed(c) => layers.push(c),
315 ConfigSource::Parsed(c) => layers.push(c),
316 ConfigSource::AbsPath(c) => {
316 ConfigSource::AbsPath(c) => {
317 // TODO check if it should be trusted
317 // TODO check if it should be trusted
318 // mercurial/ui.py:427
318 // mercurial/ui.py:427
319 let data = match std::fs::read(&c) {
319 let data = match std::fs::read(&c) {
320 Err(_) => continue, // same as the python code
320 Err(_) => continue, // same as the python code
321 Ok(data) => data,
321 Ok(data) => data,
322 };
322 };
323 layers.extend(ConfigLayer::parse(&c, &data)?)
323 layers.extend(ConfigLayer::parse(&c, &data)?)
324 }
324 }
325 }
325 }
326 }
326 }
327
327
328 Ok(Config {
328 Ok(Config {
329 layers,
329 layers,
330 plain: PlainInfo::empty(),
330 plain: PlainInfo::empty(),
331 })
331 })
332 }
332 }
333
333
334 /// Loads the per-repository config into a new `Config` which is combined
334 /// Loads the per-repository config into a new `Config` which is combined
335 /// with `self`.
335 /// with `self`.
336 pub(crate) fn combine_with_repo(
336 pub(crate) fn combine_with_repo(
337 &self,
337 &self,
338 repo_config_files: &[PathBuf],
338 repo_config_files: &[PathBuf],
339 ) -> Result<Self, ConfigError> {
339 ) -> Result<Self, ConfigError> {
340 let (cli_layers, other_layers) = self
340 let (cli_layers, other_layers) = self
341 .layers
341 .layers
342 .iter()
342 .iter()
343 .cloned()
343 .cloned()
344 .partition(ConfigLayer::is_from_command_line);
344 .partition(ConfigLayer::is_from_command_line);
345
345
346 let mut repo_config = Self {
346 let mut repo_config = Self {
347 layers: other_layers,
347 layers: other_layers,
348 plain: PlainInfo::empty(),
348 plain: PlainInfo::empty(),
349 };
349 };
350 for path in repo_config_files {
350 for path in repo_config_files {
351 // TODO: check if this file should be trusted:
351 // TODO: check if this file should be trusted:
352 // `mercurial/ui.py:427`
352 // `mercurial/ui.py:427`
353 repo_config.add_trusted_file(path)?;
353 repo_config.add_trusted_file(path)?;
354 }
354 }
355 repo_config.layers.extend(cli_layers);
355 repo_config.layers.extend(cli_layers);
356 Ok(repo_config)
356 Ok(repo_config)
357 }
357 }
358
358
359 pub fn apply_plain(&mut self, plain: PlainInfo) {
359 pub fn apply_plain(&mut self, plain: PlainInfo) {
360 self.plain = plain;
360 self.plain = plain;
361 }
361 }
362
362
363 /// Returns the default value for the given config item, if any.
363 /// Returns the default value for the given config item, if any.
364 pub fn get_default(
364 pub fn get_default(
365 &self,
365 &self,
366 section: &[u8],
366 section: &[u8],
367 item: &[u8],
367 item: &[u8],
368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
370 HgError::abort(
370 HgError::abort(
371 e.to_string(),
371 e.to_string(),
372 crate::exit_codes::ABORT,
372 crate::exit_codes::ABORT,
373 Some("`mercurial/configitems.toml` is not valid".into()),
373 Some("`mercurial/configitems.toml` is not valid".into()),
374 )
374 )
375 })?;
375 })?;
376 Ok(default_config.get(section, item))
376 let default_opt = default_config.get(section, item);
377 Ok(default_opt.filter(|default| {
378 default
379 .in_core_extension()
380 .map(|extension| {
381 // Only return the default for an in-core extension item
382 // if said extension is enabled
383 self.is_extension_enabled(extension.as_bytes())
384 })
385 .unwrap_or(true)
386 }))
377 }
387 }
378
388
379 fn get_parse<'config, T: 'config>(
389 fn get_parse<'config, T: 'config>(
380 &'config self,
390 &'config self,
381 section: &[u8],
391 section: &[u8],
382 item: &[u8],
392 item: &[u8],
383 expected_type: &'static str,
393 expected_type: &'static str,
384 parse: impl Fn(&'config [u8]) -> Option<T>,
394 parse: impl Fn(&'config [u8]) -> Option<T>,
385 fallback_to_default: bool,
395 fallback_to_default: bool,
386 ) -> Result<Option<T>, HgError>
396 ) -> Result<Option<T>, HgError>
387 where
397 where
388 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
398 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
389 {
399 {
390 match self.get_inner(section, item) {
400 match self.get_inner(section, item) {
391 Some((layer, v)) => match parse(&v.bytes) {
401 Some((layer, v)) => match parse(&v.bytes) {
392 Some(b) => Ok(Some(b)),
402 Some(b) => Ok(Some(b)),
393 None => Err(Box::new(ConfigValueParseErrorDetails {
403 None => Err(Box::new(ConfigValueParseErrorDetails {
394 origin: layer.origin.to_owned(),
404 origin: layer.origin.to_owned(),
395 line: v.line,
405 line: v.line,
396 value: v.bytes.to_owned(),
406 value: v.bytes.to_owned(),
397 section: section.to_owned(),
407 section: section.to_owned(),
398 item: item.to_owned(),
408 item: item.to_owned(),
399 expected_type,
409 expected_type,
400 })
410 })
401 .into()),
411 .into()),
402 },
412 },
403 None => {
413 None => {
404 if !fallback_to_default {
414 if !fallback_to_default {
405 return Ok(None);
415 return Ok(None);
406 }
416 }
407 match self.get_default(section, item)? {
417 match self.get_default(section, item)? {
408 Some(default) => Ok(default.try_into()?),
418 Some(default) => Ok(default.try_into()?),
409 None => Ok(None),
419 None => Ok(None),
410 }
420 }
411 }
421 }
412 }
422 }
413 }
423 }
414
424
415 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
425 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
416 /// Otherwise, returns an `Ok(value)` if found, or `None`.
426 /// Otherwise, returns an `Ok(value)` if found, or `None`.
417 pub fn get_str(
427 pub fn get_str(
418 &self,
428 &self,
419 section: &[u8],
429 section: &[u8],
420 item: &[u8],
430 item: &[u8],
421 ) -> Result<Option<&str>, HgError> {
431 ) -> Result<Option<&str>, HgError> {
422 self.get_parse(
432 self.get_parse(
423 section,
433 section,
424 item,
434 item,
425 "ASCII or UTF-8 string",
435 "ASCII or UTF-8 string",
426 |value| str::from_utf8(value).ok(),
436 |value| str::from_utf8(value).ok(),
427 true,
437 true,
428 )
438 )
429 }
439 }
430
440
431 /// Same as `get_str`, but doesn't fall back to the default `configitem`
441 /// Same as `get_str`, but doesn't fall back to the default `configitem`
432 /// if not defined in the user config.
442 /// if not defined in the user config.
433 pub fn get_str_no_default(
443 pub fn get_str_no_default(
434 &self,
444 &self,
435 section: &[u8],
445 section: &[u8],
436 item: &[u8],
446 item: &[u8],
437 ) -> Result<Option<&str>, HgError> {
447 ) -> Result<Option<&str>, HgError> {
438 self.get_parse(
448 self.get_parse(
439 section,
449 section,
440 item,
450 item,
441 "ASCII or UTF-8 string",
451 "ASCII or UTF-8 string",
442 |value| str::from_utf8(value).ok(),
452 |value| str::from_utf8(value).ok(),
443 false,
453 false,
444 )
454 )
445 }
455 }
446
456
447 /// Returns an `Err` if the first value found is not a valid unsigned
457 /// Returns an `Err` if the first value found is not a valid unsigned
448 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
458 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
449 pub fn get_u32(
459 pub fn get_u32(
450 &self,
460 &self,
451 section: &[u8],
461 section: &[u8],
452 item: &[u8],
462 item: &[u8],
453 ) -> Result<Option<u32>, HgError> {
463 ) -> Result<Option<u32>, HgError> {
454 self.get_parse(
464 self.get_parse(
455 section,
465 section,
456 item,
466 item,
457 "valid integer",
467 "valid integer",
458 |value| str::from_utf8(value).ok()?.parse().ok(),
468 |value| str::from_utf8(value).ok()?.parse().ok(),
459 true,
469 true,
460 )
470 )
461 }
471 }
462
472
463 /// Returns an `Err` if the first value found is not a valid file size
473 /// Returns an `Err` if the first value found is not a valid file size
464 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
474 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
465 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
475 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
466 pub fn get_byte_size(
476 pub fn get_byte_size(
467 &self,
477 &self,
468 section: &[u8],
478 section: &[u8],
469 item: &[u8],
479 item: &[u8],
470 ) -> Result<Option<u64>, HgError> {
480 ) -> Result<Option<u64>, HgError> {
471 self.get_parse(
481 self.get_parse(
472 section,
482 section,
473 item,
483 item,
474 "byte quantity",
484 "byte quantity",
475 values::parse_byte_size,
485 values::parse_byte_size,
476 true,
486 true,
477 )
487 )
478 }
488 }
479
489
480 /// Returns an `Err` if the first value found is not a valid boolean.
490 /// Returns an `Err` if the first value found is not a valid boolean.
481 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
491 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
482 /// found, or `None`.
492 /// found, or `None`.
483 pub fn get_option(
493 pub fn get_option(
484 &self,
494 &self,
485 section: &[u8],
495 section: &[u8],
486 item: &[u8],
496 item: &[u8],
487 ) -> Result<Option<bool>, HgError> {
497 ) -> Result<Option<bool>, HgError> {
488 self.get_parse(section, item, "boolean", values::parse_bool, true)
498 self.get_parse(section, item, "boolean", values::parse_bool, true)
489 }
499 }
490
500
491 /// Same as `get_option`, but doesn't fall back to the default `configitem`
501 /// Same as `get_option`, but doesn't fall back to the default `configitem`
492 /// if not defined in the user config.
502 /// if not defined in the user config.
493 pub fn get_option_no_default(
503 pub fn get_option_no_default(
494 &self,
504 &self,
495 section: &[u8],
505 section: &[u8],
496 item: &[u8],
506 item: &[u8],
497 ) -> Result<Option<bool>, HgError> {
507 ) -> Result<Option<bool>, HgError> {
498 self.get_parse(section, item, "boolean", values::parse_bool, false)
508 self.get_parse(section, item, "boolean", values::parse_bool, false)
499 }
509 }
500
510
501 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
511 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
502 /// if the value is not found, an `Err` if it's not a valid boolean.
512 /// if the value is not found, an `Err` if it's not a valid boolean.
503 pub fn get_bool(
513 pub fn get_bool(
504 &self,
514 &self,
505 section: &[u8],
515 section: &[u8],
506 item: &[u8],
516 item: &[u8],
507 ) -> Result<bool, HgError> {
517 ) -> Result<bool, HgError> {
508 Ok(self.get_option(section, item)?.unwrap_or(false))
518 Ok(self.get_option(section, item)?.unwrap_or(false))
509 }
519 }
510
520
511 /// Same as `get_bool`, but doesn't fall back to the default `configitem`
521 /// Same as `get_bool`, but doesn't fall back to the default `configitem`
512 /// if not defined in the user config.
522 /// if not defined in the user config.
513 pub fn get_bool_no_default(
523 pub fn get_bool_no_default(
514 &self,
524 &self,
515 section: &[u8],
525 section: &[u8],
516 item: &[u8],
526 item: &[u8],
517 ) -> Result<bool, HgError> {
527 ) -> Result<bool, HgError> {
518 Ok(self.get_option_no_default(section, item)?.unwrap_or(false))
528 Ok(self.get_option_no_default(section, item)?.unwrap_or(false))
519 }
529 }
520
530
521 /// Returns `true` if the extension is enabled, `false` otherwise
531 /// Returns `true` if the extension is enabled, `false` otherwise
522 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
532 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
523 let value = self.get(b"extensions", extension);
533 let value = self.get(b"extensions", extension);
524 match value {
534 match value {
525 Some(c) => !c.starts_with(b"!"),
535 Some(c) => !c.starts_with(b"!"),
526 None => false,
536 None => false,
527 }
537 }
528 }
538 }
529
539
530 /// If there is an `item` value in `section`, parse and return a list of
540 /// If there is an `item` value in `section`, parse and return a list of
531 /// byte strings.
541 /// byte strings.
532 pub fn get_list(
542 pub fn get_list(
533 &self,
543 &self,
534 section: &[u8],
544 section: &[u8],
535 item: &[u8],
545 item: &[u8],
536 ) -> Option<Vec<Vec<u8>>> {
546 ) -> Option<Vec<Vec<u8>>> {
537 self.get(section, item).map(values::parse_list)
547 self.get(section, item).map(values::parse_list)
538 }
548 }
539
549
540 /// Returns the raw value bytes of the first one found, or `None`.
550 /// Returns the raw value bytes of the first one found, or `None`.
541 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
551 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
542 self.get_inner(section, item)
552 self.get_inner(section, item)
543 .map(|(_, value)| value.bytes.as_ref())
553 .map(|(_, value)| value.bytes.as_ref())
544 }
554 }
545
555
546 /// Returns the raw value bytes of the first one found, or `None`.
556 /// Returns the raw value bytes of the first one found, or `None`.
547 pub fn get_with_origin(
557 pub fn get_with_origin(
548 &self,
558 &self,
549 section: &[u8],
559 section: &[u8],
550 item: &[u8],
560 item: &[u8],
551 ) -> Option<(&[u8], &ConfigOrigin)> {
561 ) -> Option<(&[u8], &ConfigOrigin)> {
552 self.get_inner(section, item)
562 self.get_inner(section, item)
553 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
563 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
554 }
564 }
555
565
556 /// Returns the layer and the value of the first one found, or `None`.
566 /// Returns the layer and the value of the first one found, or `None`.
557 fn get_inner(
567 fn get_inner(
558 &self,
568 &self,
559 section: &[u8],
569 section: &[u8],
560 item: &[u8],
570 item: &[u8],
561 ) -> Option<(&ConfigLayer, &ConfigValue)> {
571 ) -> Option<(&ConfigLayer, &ConfigValue)> {
562 // Filter out the config items that are hidden by [PLAIN].
572 // Filter out the config items that are hidden by [PLAIN].
563 // This differs from python hg where we delete them from the config.
573 // This differs from python hg where we delete them from the config.
564 let should_ignore = should_ignore(&self.plain, section, item);
574 let should_ignore = should_ignore(&self.plain, section, item);
565 for layer in self.layers.iter().rev() {
575 for layer in self.layers.iter().rev() {
566 if !layer.trusted {
576 if !layer.trusted {
567 continue;
577 continue;
568 }
578 }
569 //The [PLAIN] config should not affect the defaults.
579 //The [PLAIN] config should not affect the defaults.
570 //
580 //
571 // However, PLAIN should also affect the "tweaked" defaults (unless
581 // However, PLAIN should also affect the "tweaked" defaults (unless
572 // "tweakdefault" is part of "HGPLAINEXCEPT").
582 // "tweakdefault" is part of "HGPLAINEXCEPT").
573 //
583 //
574 // In practice the tweak-default layer is only added when it is
584 // In practice the tweak-default layer is only added when it is
575 // relevant, so we can safely always take it into
585 // relevant, so we can safely always take it into
576 // account here.
586 // account here.
577 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
587 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
578 {
588 {
579 continue;
589 continue;
580 }
590 }
581 if let Some(v) = layer.get(section, item) {
591 if let Some(v) = layer.get(section, item) {
582 return Some((layer, v));
592 return Some((layer, v));
583 }
593 }
584 }
594 }
585 None
595 None
586 }
596 }
587
597
588 /// Return all keys defined for the given section
598 /// Return all keys defined for the given section
589 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
599 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
590 self.layers
600 self.layers
591 .iter()
601 .iter()
592 .flat_map(|layer| layer.iter_keys(section))
602 .flat_map(|layer| layer.iter_keys(section))
593 .collect()
603 .collect()
594 }
604 }
595
605
596 /// Returns whether any key is defined in the given section
606 /// Returns whether any key is defined in the given section
597 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
607 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
598 self.layers
608 self.layers
599 .iter()
609 .iter()
600 .any(|layer| layer.has_non_empty_section(section))
610 .any(|layer| layer.has_non_empty_section(section))
601 }
611 }
602
612
603 /// Yields (key, value) pairs for everything in the given section
613 /// Yields (key, value) pairs for everything in the given section
604 pub fn iter_section<'a>(
614 pub fn iter_section<'a>(
605 &'a self,
615 &'a self,
606 section: &'a [u8],
616 section: &'a [u8],
607 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
617 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
608 // Deduplicate keys redefined in multiple layers
618 // Deduplicate keys redefined in multiple layers
609 let mut keys_already_seen = HashSet::new();
619 let mut keys_already_seen = HashSet::new();
610 let mut key_is_new =
620 let mut key_is_new =
611 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
621 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
612 keys_already_seen.insert(key)
622 keys_already_seen.insert(key)
613 };
623 };
614 // This is similar to `flat_map` + `filter_map`, except with a single
624 // This is similar to `flat_map` + `filter_map`, except with a single
615 // closure that owns `key_is_new` (and therefore the
625 // closure that owns `key_is_new` (and therefore the
616 // `keys_already_seen` set):
626 // `keys_already_seen` set):
617 let mut layer_iters = self
627 let mut layer_iters = self
618 .layers
628 .layers
619 .iter()
629 .iter()
620 .rev()
630 .rev()
621 .map(move |layer| layer.iter_section(section))
631 .map(move |layer| layer.iter_section(section))
622 .peekable();
632 .peekable();
623 std::iter::from_fn(move || loop {
633 std::iter::from_fn(move || loop {
624 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
634 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
625 return Some(pair);
635 return Some(pair);
626 } else {
636 } else {
627 layer_iters.next();
637 layer_iters.next();
628 }
638 }
629 })
639 })
630 }
640 }
631
641
632 /// Get raw values bytes from all layers (even untrusted ones) in order
642 /// Get raw values bytes from all layers (even untrusted ones) in order
633 /// of precedence.
643 /// of precedence.
634 #[cfg(test)]
644 #[cfg(test)]
635 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
645 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
636 let mut res = vec![];
646 let mut res = vec![];
637 for layer in self.layers.iter().rev() {
647 for layer in self.layers.iter().rev() {
638 if let Some(v) = layer.get(section, item) {
648 if let Some(v) = layer.get(section, item) {
639 res.push(v.bytes.as_ref());
649 res.push(v.bytes.as_ref());
640 }
650 }
641 }
651 }
642 res
652 res
643 }
653 }
644
654
645 // a config layer that's introduced by ui.tweakdefaults
655 // a config layer that's introduced by ui.tweakdefaults
646 fn tweakdefaults_layer() -> ConfigLayer {
656 fn tweakdefaults_layer() -> ConfigLayer {
647 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
657 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
648
658
649 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
659 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
650 layer.add(
660 layer.add(
651 section[..].into(),
661 section[..].into(),
652 item[..].into(),
662 item[..].into(),
653 value[..].into(),
663 value[..].into(),
654 None,
664 None,
655 );
665 );
656 };
666 };
657 // duplication of [tweakrc] from [ui.py]
667 // duplication of [tweakrc] from [ui.py]
658 add(b"ui", b"rollback", b"False");
668 add(b"ui", b"rollback", b"False");
659 add(b"ui", b"statuscopies", b"yes");
669 add(b"ui", b"statuscopies", b"yes");
660 add(b"ui", b"interface", b"curses");
670 add(b"ui", b"interface", b"curses");
661 add(b"ui", b"relative-paths", b"yes");
671 add(b"ui", b"relative-paths", b"yes");
662 add(b"commands", b"grep.all-files", b"True");
672 add(b"commands", b"grep.all-files", b"True");
663 add(b"commands", b"update.check", b"noconflict");
673 add(b"commands", b"update.check", b"noconflict");
664 add(b"commands", b"status.verbose", b"True");
674 add(b"commands", b"status.verbose", b"True");
665 add(b"commands", b"resolve.explicit-re-merge", b"True");
675 add(b"commands", b"resolve.explicit-re-merge", b"True");
666 add(b"git", b"git", b"1");
676 add(b"git", b"git", b"1");
667 add(b"git", b"showfunc", b"1");
677 add(b"git", b"showfunc", b"1");
668 add(b"git", b"word-diff", b"1");
678 add(b"git", b"word-diff", b"1");
669 layer
679 layer
670 }
680 }
671
681
672 // introduce the tweaked defaults as implied by ui.tweakdefaults
682 // introduce the tweaked defaults as implied by ui.tweakdefaults
673 pub fn tweakdefaults(&mut self) {
683 pub fn tweakdefaults(&mut self) {
674 self.layers.insert(0, Config::tweakdefaults_layer());
684 self.layers.insert(0, Config::tweakdefaults_layer());
675 }
685 }
676 }
686 }
677
687
678 #[cfg(test)]
688 #[cfg(test)]
679 mod tests {
689 mod tests {
680 use super::*;
690 use super::*;
681 use pretty_assertions::assert_eq;
691 use pretty_assertions::assert_eq;
682 use std::fs::File;
692 use std::fs::File;
683 use std::io::Write;
693 use std::io::Write;
684
694
685 #[test]
695 #[test]
686 fn test_include_layer_ordering() {
696 fn test_include_layer_ordering() {
687 let tmpdir = tempfile::tempdir().unwrap();
697 let tmpdir = tempfile::tempdir().unwrap();
688 let tmpdir_path = tmpdir.path();
698 let tmpdir_path = tmpdir.path();
689 let mut included_file =
699 let mut included_file =
690 File::create(&tmpdir_path.join("included.rc")).unwrap();
700 File::create(&tmpdir_path.join("included.rc")).unwrap();
691
701
692 included_file.write_all(b"[section]\nitem=value1").unwrap();
702 included_file.write_all(b"[section]\nitem=value1").unwrap();
693 let base_config_path = tmpdir_path.join("base.rc");
703 let base_config_path = tmpdir_path.join("base.rc");
694 let mut config_file = File::create(&base_config_path).unwrap();
704 let mut config_file = File::create(&base_config_path).unwrap();
695 let data =
705 let data =
696 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
706 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
697 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
707 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
698 config_file.write_all(data).unwrap();
708 config_file.write_all(data).unwrap();
699
709
700 let sources = vec![ConfigSource::AbsPath(base_config_path)];
710 let sources = vec![ConfigSource::AbsPath(base_config_path)];
701 let config = Config::load_from_explicit_sources(sources)
711 let config = Config::load_from_explicit_sources(sources)
702 .expect("expected valid config");
712 .expect("expected valid config");
703
713
704 let (_, value) = config.get_inner(b"section", b"item").unwrap();
714 let (_, value) = config.get_inner(b"section", b"item").unwrap();
705 assert_eq!(
715 assert_eq!(
706 value,
716 value,
707 &ConfigValue {
717 &ConfigValue {
708 bytes: b"value2".to_vec(),
718 bytes: b"value2".to_vec(),
709 line: Some(4)
719 line: Some(4)
710 }
720 }
711 );
721 );
712
722
713 let value = config.get(b"section", b"item").unwrap();
723 let value = config.get(b"section", b"item").unwrap();
714 assert_eq!(value, b"value2",);
724 assert_eq!(value, b"value2",);
715 assert_eq!(
725 assert_eq!(
716 config.get_all(b"section", b"item"),
726 config.get_all(b"section", b"item"),
717 [b"value2", b"value1", b"value0"]
727 [b"value2", b"value1", b"value0"]
718 );
728 );
719
729
720 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
730 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
721 assert_eq!(
731 assert_eq!(
722 config.get_byte_size(b"section2", b"size").unwrap(),
732 config.get_byte_size(b"section2", b"size").unwrap(),
723 Some(1024 + 512)
733 Some(1024 + 512)
724 );
734 );
725 assert!(config.get_u32(b"section2", b"not-count").is_err());
735 assert!(config.get_u32(b"section2", b"not-count").is_err());
726 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
736 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
727 }
737 }
728 }
738 }
@@ -1,171 +1,178 b''
1 //! Logging for repository events, including commands run in the repository.
1 //! Logging for repository events, including commands run in the repository.
2
2
3 use crate::CliInvocation;
3 use crate::CliInvocation;
4 use format_bytes::format_bytes;
4 use format_bytes::format_bytes;
5 use hg::errors::HgError;
5 use hg::errors::HgError;
6 use hg::repo::Repo;
6 use hg::repo::Repo;
7 use hg::utils::{files::get_bytes_from_os_str, shell_quote};
7 use hg::utils::{files::get_bytes_from_os_str, shell_quote};
8 use std::ffi::OsString;
8 use std::ffi::OsString;
9
9
10 const ONE_MEBIBYTE: u64 = 1 << 20;
11
12 // TODO: somehow keep defaults in sync with `configitem` in `hgext/blackbox.py`
13 const DEFAULT_MAX_SIZE: u64 = ONE_MEBIBYTE;
14 const DEFAULT_MAX_FILES: u32 = 7;
15
16 // Python does not support %.3f, only %f
10 // Python does not support %.3f, only %f
17 const DEFAULT_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.3f";
11 const DEFAULT_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.3f";
18
12
19 type DateTime = chrono::DateTime<chrono::Local>;
13 type DateTime = chrono::DateTime<chrono::Local>;
20
14
21 pub struct ProcessStartTime {
15 pub struct ProcessStartTime {
22 /// For measuring duration
16 /// For measuring duration
23 monotonic_clock: std::time::Instant,
17 monotonic_clock: std::time::Instant,
24 /// For formatting with year, month, day, etc.
18 /// For formatting with year, month, day, etc.
25 calendar_based: DateTime,
19 calendar_based: DateTime,
26 }
20 }
27
21
28 impl ProcessStartTime {
22 impl ProcessStartTime {
29 pub fn now() -> Self {
23 pub fn now() -> Self {
30 Self {
24 Self {
31 monotonic_clock: std::time::Instant::now(),
25 monotonic_clock: std::time::Instant::now(),
32 calendar_based: chrono::Local::now(),
26 calendar_based: chrono::Local::now(),
33 }
27 }
34 }
28 }
35 }
29 }
36
30
37 pub struct Blackbox<'a> {
31 pub struct Blackbox<'a> {
38 process_start_time: &'a ProcessStartTime,
32 process_start_time: &'a ProcessStartTime,
39 /// Do nothing if this is `None`
33 /// Do nothing if this is `None`
40 configured: Option<ConfiguredBlackbox<'a>>,
34 configured: Option<ConfiguredBlackbox<'a>>,
41 }
35 }
42
36
43 struct ConfiguredBlackbox<'a> {
37 struct ConfiguredBlackbox<'a> {
44 repo: &'a Repo,
38 repo: &'a Repo,
45 max_size: u64,
39 max_size: u64,
46 max_files: u32,
40 max_files: u32,
47 date_format: &'a str,
41 date_format: &'a str,
48 }
42 }
49
43
50 impl<'a> Blackbox<'a> {
44 impl<'a> Blackbox<'a> {
51 pub fn new(
45 pub fn new(
52 invocation: &'a CliInvocation<'a>,
46 invocation: &'a CliInvocation<'a>,
53 process_start_time: &'a ProcessStartTime,
47 process_start_time: &'a ProcessStartTime,
54 ) -> Result<Self, HgError> {
48 ) -> Result<Self, HgError> {
55 let configured = if let Ok(repo) = invocation.repo {
49 let configured = if let Ok(repo) = invocation.repo {
56 if invocation.config.get(b"extensions", b"blackbox").is_none() {
50 if invocation.config.get(b"extensions", b"blackbox").is_none() {
57 // The extension is not enabled
51 // The extension is not enabled
58 None
52 None
59 } else {
53 } else {
60 Some(ConfiguredBlackbox {
54 Some(ConfiguredBlackbox {
61 repo,
55 repo,
62 max_size: invocation
56 max_size: invocation
63 .config
57 .config
64 .get_byte_size(b"blackbox", b"maxsize")?
58 .get_byte_size(b"blackbox", b"maxsize")?
65 .unwrap_or(DEFAULT_MAX_SIZE),
59 .expect(
60 "blackbox.maxsize should have a default value",
61 ),
66 max_files: invocation
62 max_files: invocation
67 .config
63 .config
68 .get_u32(b"blackbox", b"maxfiles")?
64 .get_u32(b"blackbox", b"maxfiles")?
69 .unwrap_or(DEFAULT_MAX_FILES),
65 .expect(
66 "blackbox.maxfiles should have a default value",
67 ),
70 date_format: invocation
68 date_format: invocation
71 .config
69 .config
72 .get_str(b"blackbox", b"date-format")?
70 .get_str(b"blackbox", b"date-format")?
73 .unwrap_or(DEFAULT_DATE_FORMAT),
71 .map(|f| {
72 if f.is_empty() {
73 DEFAULT_DATE_FORMAT
74 } else {
75 f
76 }
77 })
78 .expect(
79 "blackbox.date-format should have a default value",
80 ),
74 })
81 })
75 }
82 }
76 } else {
83 } else {
77 // Without a local repository there’s no `.hg/blackbox.log` to
84 // Without a local repository there’s no `.hg/blackbox.log` to
78 // write to.
85 // write to.
79 None
86 None
80 };
87 };
81 Ok(Self {
88 Ok(Self {
82 process_start_time,
89 process_start_time,
83 configured,
90 configured,
84 })
91 })
85 }
92 }
86
93
87 pub fn log_command_start<'arg>(
94 pub fn log_command_start<'arg>(
88 &self,
95 &self,
89 argv: impl Iterator<Item = &'arg OsString>,
96 argv: impl Iterator<Item = &'arg OsString>,
90 ) {
97 ) {
91 if let Some(configured) = &self.configured {
98 if let Some(configured) = &self.configured {
92 let message = format_bytes!(b"(rust) {}", format_cli_args(argv));
99 let message = format_bytes!(b"(rust) {}", format_cli_args(argv));
93 configured.log(&self.process_start_time.calendar_based, &message);
100 configured.log(&self.process_start_time.calendar_based, &message);
94 }
101 }
95 }
102 }
96
103
97 pub fn log_command_end<'arg>(
104 pub fn log_command_end<'arg>(
98 &self,
105 &self,
99 argv: impl Iterator<Item = &'arg OsString>,
106 argv: impl Iterator<Item = &'arg OsString>,
100 exit_code: i32,
107 exit_code: i32,
101 ) {
108 ) {
102 if let Some(configured) = &self.configured {
109 if let Some(configured) = &self.configured {
103 let now = chrono::Local::now();
110 let now = chrono::Local::now();
104 let duration = self
111 let duration = self
105 .process_start_time
112 .process_start_time
106 .monotonic_clock
113 .monotonic_clock
107 .elapsed()
114 .elapsed()
108 .as_secs_f64();
115 .as_secs_f64();
109 let message = format_bytes!(
116 let message = format_bytes!(
110 b"(rust) {} exited {} after {} seconds",
117 b"(rust) {} exited {} after {} seconds",
111 format_cli_args(argv),
118 format_cli_args(argv),
112 exit_code,
119 exit_code,
113 format_bytes::Utf8(format_args!("{:.03}", duration))
120 format_bytes::Utf8(format_args!("{:.03}", duration))
114 );
121 );
115 configured.log(&now, &message);
122 configured.log(&now, &message);
116 }
123 }
117 }
124 }
118 }
125 }
119
126
120 impl ConfiguredBlackbox<'_> {
127 impl ConfiguredBlackbox<'_> {
121 fn log(&self, date_time: &DateTime, message: &[u8]) {
128 fn log(&self, date_time: &DateTime, message: &[u8]) {
122 let date = format_bytes::Utf8(date_time.format(self.date_format));
129 let date = format_bytes::Utf8(date_time.format(self.date_format));
123 let user = get_bytes_from_os_str(whoami::username_os());
130 let user = get_bytes_from_os_str(whoami::username_os());
124 let rev = format_bytes::Utf8(match self.repo.dirstate_parents() {
131 let rev = format_bytes::Utf8(match self.repo.dirstate_parents() {
125 Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => {
132 Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => {
126 format!("{:x}", parents.p1)
133 format!("{:x}", parents.p1)
127 }
134 }
128 Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2),
135 Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2),
129 Err(_dirstate_corruption_error) => {
136 Err(_dirstate_corruption_error) => {
130 // TODO: log a non-fatal warning to stderr
137 // TODO: log a non-fatal warning to stderr
131 "???".to_owned()
138 "???".to_owned()
132 }
139 }
133 });
140 });
134 let pid = std::process::id();
141 let pid = std::process::id();
135 let line = format_bytes!(
142 let line = format_bytes!(
136 b"{} {} @{} ({})> {}\n",
143 b"{} {} @{} ({})> {}\n",
137 date,
144 date,
138 user,
145 user,
139 rev,
146 rev,
140 pid,
147 pid,
141 message
148 message
142 );
149 );
143 let result =
150 let result =
144 hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log")
151 hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log")
145 .max_size(Some(self.max_size))
152 .max_size(Some(self.max_size))
146 .max_files(self.max_files)
153 .max_files(self.max_files)
147 .write(&line);
154 .write(&line);
148 match result {
155 match result {
149 Ok(()) => {}
156 Ok(()) => {}
150 Err(_io_error) => {
157 Err(_io_error) => {
151 // TODO: log a non-fatal warning to stderr
158 // TODO: log a non-fatal warning to stderr
152 }
159 }
153 }
160 }
154 }
161 }
155 }
162 }
156
163
157 fn format_cli_args<'a>(
164 fn format_cli_args<'a>(
158 mut args: impl Iterator<Item = &'a OsString>,
165 mut args: impl Iterator<Item = &'a OsString>,
159 ) -> Vec<u8> {
166 ) -> Vec<u8> {
160 let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable
167 let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable
161 let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg)));
168 let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg)));
162 let mut formatted = Vec::new();
169 let mut formatted = Vec::new();
163 if let Some(arg) = args.next() {
170 if let Some(arg) = args.next() {
164 formatted.extend(arg)
171 formatted.extend(arg)
165 }
172 }
166 for arg in args {
173 for arg in args {
167 formatted.push(b' ');
174 formatted.push(b' ');
168 formatted.extend(arg)
175 formatted.extend(arg)
169 }
176 }
170 formatted
177 formatted
171 }
178 }
General Comments 0
You need to be logged in to leave comments. Login now