##// 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 1 # blackbox.py - log repository events to a file for post-mortem debugging
2 2 #
3 3 # Copyright 2010 Nicolas Dumazet
4 4 # Copyright 2013 Facebook, Inc.
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """log repository events to a blackbox for debugging
10 10
11 11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 12 The events that get logged can be configured via the blackbox.track and
13 13 blackbox.ignore config keys.
14 14
15 15 Examples::
16 16
17 17 [blackbox]
18 18 track = *
19 19 ignore = pythonhook
20 20 # dirty is *EXPENSIVE* (slow);
21 21 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
22 22 dirty = True
23 23 # record the source of log messages
24 24 logsource = True
25 25
26 26 [blackbox]
27 27 track = command, commandfinish, commandexception, exthook, pythonhook
28 28
29 29 [blackbox]
30 30 track = incoming
31 31
32 32 [blackbox]
33 33 # limit the size of a log file
34 34 maxsize = 1.5 MB
35 35 # rotate up to N log files when the current one gets too big
36 36 maxfiles = 3
37 37
38 38 [blackbox]
39 39 # Include microseconds in log entries with %f (see Python function
40 40 # datetime.datetime.strftime)
41 41 date-format = %Y-%m-%d @ %H:%M:%S.%f
42 42
43 43 """
44 44
45 45
46 46 import re
47 47
48 48 from mercurial.i18n import _
49 49 from mercurial.node import hex
50 50
51 51 from mercurial import (
52 52 encoding,
53 53 loggingutil,
54 54 registrar,
55 55 )
56 56 from mercurial.utils import (
57 57 dateutil,
58 58 procutil,
59 59 )
60 60
61 61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 63 # be specifying the version(s) of Mercurial they are tested with, or
64 64 # leave the attribute unspecified.
65 65 testedwith = b'ships-with-hg-core'
66 66
67 67 cmdtable = {}
68 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 70 _lastlogger = loggingutil.proxylogger()
106 71
107 72
108 73 class blackboxlogger:
109 74 def __init__(self, ui, repo):
110 75 self._repo = repo
111 76 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
112 77 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
113 78 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
114 79 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
115 80 self._inlog = False
116 81
117 82 def tracked(self, event):
118 83 return (
119 84 b'*' in self._trackedevents and event not in self._ignoredevents
120 85 ) or event in self._trackedevents
121 86
122 87 def log(self, ui, event, msg, opts):
123 88 # self._log() -> ctx.dirty() may create new subrepo instance, which
124 89 # ui is derived from baseui. So the recursion guard in ui.log()
125 90 # doesn't work as it's local to the ui instance.
126 91 if self._inlog:
127 92 return
128 93 self._inlog = True
129 94 try:
130 95 self._log(ui, event, msg, opts)
131 96 finally:
132 97 self._inlog = False
133 98
134 99 def _log(self, ui, event, msg, opts):
135 100 default = ui.configdate(b'devel', b'default-date')
136 101 dateformat = ui.config(b'blackbox', b'date-format')
137 102 if dateformat:
138 103 date = dateutil.datestr(default, dateformat)
139 104 else:
140 105 # We want to display milliseconds (more precision seems
141 106 # unnecessary). Since %.3f is not supported, use %f and truncate
142 107 # microseconds.
143 108 date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3]
144 109 user = procutil.getuser()
145 110 pid = b'%d' % procutil.getpid()
146 111 changed = b''
147 112 ctx = self._repo[None]
148 113 parents = ctx.parents()
149 114 rev = b'+'.join([hex(p.node()) for p in parents])
150 115 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
151 116 missing=True, merge=False, branch=False
152 117 ):
153 118 changed = b'+'
154 119 if ui.configbool(b'blackbox', b'logsource'):
155 120 src = b' [%s]' % event
156 121 else:
157 122 src = b''
158 123 try:
159 124 fmt = b'%s %s @%s%s (%s)%s> %s'
160 125 args = (date, user, rev, changed, pid, src, msg)
161 126 with loggingutil.openlogfile(
162 127 ui,
163 128 self._repo.vfs,
164 129 name=b'blackbox.log',
165 130 maxfiles=self._maxfiles,
166 131 maxsize=self._maxsize,
167 132 ) as fp:
168 133 fp.write(fmt % args)
169 134 except (IOError, OSError) as err:
170 135 # deactivate this to avoid failed logging again
171 136 self._trackedevents.clear()
172 137 ui.debug(
173 138 b'warning: cannot write to blackbox.log: %s\n'
174 139 % encoding.strtolocal(err.strerror)
175 140 )
176 141 return
177 142 _lastlogger.logger = self
178 143
179 144
180 145 def uipopulate(ui):
181 146 ui.setlogger(b'blackbox', _lastlogger)
182 147
183 148
184 149 def reposetup(ui, repo):
185 150 # During 'hg pull' a httppeer repo is created to represent the remote repo.
186 151 # It doesn't have a .hg directory to put a blackbox in, so we don't do
187 152 # the blackbox setup for it.
188 153 if not repo.local():
189 154 return
190 155
191 156 # Since blackbox.log is stored in the repo directory, the logger should be
192 157 # instantiated per repository.
193 158 logger = blackboxlogger(ui, repo)
194 159 ui.setlogger(b'blackbox', logger)
195 160
196 161 # Set _lastlogger even if ui.log is not called. This gives blackbox a
197 162 # fallback place to log
198 163 if _lastlogger.logger is None:
199 164 _lastlogger.logger = logger
200 165
201 166 repo._wlockfreeprefix.add(b'blackbox.log')
202 167
203 168
204 169 @command(
205 170 b'blackbox',
206 171 [
207 172 (b'l', b'limit', 10, _(b'the number of events to show')),
208 173 ],
209 174 _(b'hg blackbox [OPTION]...'),
210 175 helpcategory=command.CATEGORY_MAINTENANCE,
211 176 helpbasic=True,
212 177 )
213 178 def blackbox(ui, repo, *revs, **opts):
214 179 """view the recent repository events"""
215 180
216 181 if not repo.vfs.exists(b'blackbox.log'):
217 182 return
218 183
219 184 limit = opts.get('limit')
220 185 assert limit is not None # help pytype
221 186
222 187 fp = repo.vfs(b'blackbox.log', b'r')
223 188 lines = fp.read().split(b'\n')
224 189
225 190 count = 0
226 191 output = []
227 192 for line in reversed(lines):
228 193 if count >= limit:
229 194 break
230 195
231 196 # count the commands by matching lines like:
232 197 # 2013/01/23 19:13:36 root>
233 198 # 2013/01/23 19:13:36 root (1234)>
234 199 # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)>
235 200 # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)>
236 201 if re.match(
237 202 br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line
238 203 ):
239 204 count += 1
240 205 output.append(line)
241 206
242 207 ui.status(b'\n'.join(reversed(output)))
@@ -1,212 +1,214 b''
1 1 # configitems.py - centralized declaration of configuration option
2 2 #
3 3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import functools
10 10 import re
11 11
12 12 from .utils import resourceutil
13 13
14 14 from . import (
15 15 encoding,
16 16 error,
17 17 )
18 18
19 19 try:
20 20 import tomllib # pytype: disable=import-error
21 21
22 22 tomllib.load # trigger lazy import
23 23 except ModuleNotFoundError:
24 24 # Python <3.11 compat
25 25 from .thirdparty import tomli as tomllib
26 26
27 27
28 28 def loadconfigtable(ui, extname, configtable):
29 29 """update config item known to the ui with the extension ones"""
30 30 for section, items in sorted(configtable.items()):
31 31 knownitems = ui._knownconfig.setdefault(section, itemregister())
32 32 knownkeys = set(knownitems)
33 33 newkeys = set(items)
34 34 for key in sorted(knownkeys & newkeys):
35 35 msg = b"extension '%s' overwrites config item '%s.%s'"
36 36 msg %= (extname, section, key)
37 37 ui.develwarn(msg, config=b'warn-config')
38 38
39 39 knownitems.update(items)
40 40
41 41
42 42 class configitem:
43 43 """represent a known config item
44 44
45 45 :section: the official config section where to find this item,
46 46 :name: the official name within the section,
47 47 :default: default value for this item,
48 48 :alias: optional list of tuples as alternatives,
49 49 :generic: this is a generic definition, match name using regular expression.
50 50 """
51 51
52 52 def __init__(
53 53 self,
54 54 section,
55 55 name,
56 56 default=None,
57 57 alias=(),
58 58 generic=False,
59 59 priority=0,
60 60 experimental=False,
61 61 documentation="",
62 in_core_extension=None,
62 63 ):
63 64 self.section = section
64 65 self.name = name
65 66 self.default = default
66 67 self.documentation = documentation
67 68 self.alias = list(alias)
68 69 self.generic = generic
69 70 self.priority = priority
70 71 self.experimental = experimental
71 72 self._re = None
73 self.in_core_extension = in_core_extension
72 74 if generic:
73 75 self._re = re.compile(self.name)
74 76
75 77
76 78 class itemregister(dict):
77 79 """A specialized dictionary that can handle wild-card selection"""
78 80
79 81 def __init__(self):
80 82 super(itemregister, self).__init__()
81 83 self._generics = set()
82 84
83 85 def update(self, other):
84 86 super(itemregister, self).update(other)
85 87 self._generics.update(other._generics)
86 88
87 89 def __setitem__(self, key, item):
88 90 super(itemregister, self).__setitem__(key, item)
89 91 if item.generic:
90 92 self._generics.add(item)
91 93
92 94 def get(self, key):
93 95 baseitem = super(itemregister, self).get(key)
94 96 if baseitem is not None and not baseitem.generic:
95 97 return baseitem
96 98
97 99 # search for a matching generic item
98 100 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
99 101 for item in generics:
100 102 # we use 'match' instead of 'search' to make the matching simpler
101 103 # for people unfamiliar with regular expression. Having the match
102 104 # rooted to the start of the string will produce less surprising
103 105 # result for user writing simple regex for sub-attribute.
104 106 #
105 107 # For example using "color\..*" match produces an unsurprising
106 108 # result, while using search could suddenly match apparently
107 109 # unrelated configuration that happens to contains "color."
108 110 # anywhere. This is a tradeoff where we favor requiring ".*" on
109 111 # some match to avoid the need to prefix most pattern with "^".
110 112 # The "^" seems more error prone.
111 113 if item._re.match(key):
112 114 return item
113 115
114 116 return None
115 117
116 118
117 119 def sanitize_item(item):
118 120 """Apply the transformations that are encoded on top of the pure data"""
119 121
120 122 # Set the special defaults
121 123 default_type_key = "default-type"
122 124 default_type = item.pop(default_type_key, None)
123 125 if default_type == "dynamic":
124 126 item["default"] = dynamicdefault
125 127 elif default_type == "list_type":
126 128 item["default"] = list
127 129 elif default_type == "lambda":
128 130 assert isinstance(item["default"], list)
129 131 default = [e.encode() for e in item["default"]]
130 132 item["default"] = lambda: default
131 133 elif default_type == "lazy_module":
132 134 item["default"] = lambda: encoding.encoding
133 135 else:
134 136 if default_type is not None:
135 137 msg = "invalid default config type %r for '%s.%s'"
136 138 msg %= (default_type, item["section"], item["name"])
137 139 raise error.ProgrammingError(msg)
138 140
139 141 # config expects bytes
140 142 alias = item.get("alias")
141 143 if alias:
142 144 item["alias"] = [(k.encode(), v.encode()) for (k, v) in alias]
143 145 if isinstance(item.get("default"), str):
144 146 item["default"] = item["default"].encode()
145 147 item["section"] = item["section"].encode()
146 148 item["name"] = item["name"].encode()
147 149
148 150
149 151 def read_configitems_file():
150 152 """Returns the deserialized TOML structure from the configitems file"""
151 153 with resourceutil.open_resource(b"mercurial", b"configitems.toml") as fp:
152 154 return tomllib.load(fp)
153 155
154 156
155 157 def configitems_from_toml(items):
156 158 """Register the configitems from the *deserialized* toml file"""
157 159 for item in items["items"]:
158 160 sanitize_item(item)
159 161 coreconfigitem(**item)
160 162
161 163 templates = items["templates"]
162 164
163 165 for application in items["template-applications"]:
164 166 template_items = templates[application["template"]]
165 167
166 168 for template_item in template_items:
167 169 item = template_item.copy()
168 170 prefix = application.get("prefix", "")
169 171 item["section"] = application["section"]
170 172 if prefix:
171 173 item["name"] = f'{prefix}.{item["suffix"]}'
172 174 else:
173 175 item["name"] = item["suffix"]
174 176
175 177 sanitize_item(item)
176 178 item.pop("suffix", None)
177 179 coreconfigitem(**item)
178 180
179 181
180 182 def import_configitems_from_file():
181 183 as_toml = read_configitems_file()
182 184 configitems_from_toml(as_toml)
183 185
184 186
185 187 coreitems = {}
186 188
187 189
188 190 def _register(configtable, *args, **kwargs):
189 191 item = configitem(*args, **kwargs)
190 192 section = configtable.setdefault(item.section, itemregister())
191 193 if item.name in section:
192 194 msg = b"duplicated config item registration for '%s.%s'"
193 195 raise error.ProgrammingError(msg % (item.section, item.name))
194 196 section[item.name] = item
195 197
196 198
197 199 # special value for case where the default is derived from other values
198 200 dynamicdefault = object()
199 201
200 202 # Registering actual config items
201 203
202 204
203 205 def getitemregister(configtable):
204 206 f = functools.partial(_register, configtable)
205 207 # export pseudo enum as configitem.*
206 208 f.dynamicdefault = dynamicdefault
207 209 return f
208 210
209 211
210 212 coreconfigitem = getitemregister(coreitems)
211 213
212 214 import_configitems_from_file()
@@ -1,2759 +1,2807 b''
1 1 # configitems.toml - centralized declaration of configuration options
2 2 #
3 3 # This file contains declarations of the core Mercurial configuration options.
4 4 #
5 5 # # Structure
6 6 #
7 7 # items: array of config items
8 8 # templates: mapping of template name to template declaration
9 9 # template-applications: array of template applications
10 10 #
11 11 # # Elements
12 12 #
13 13 # ## Item
14 14 #
15 15 # Declares a core Mercurial option.
16 16 #
17 17 # - section: string (required)
18 18 # - name: string (required)
19 19 # - default-type: boolean, changes how `default` is read
20 20 # - default: any
21 21 # - generic: boolean
22 22 # - priority: integer, only if `generic` is true
23 23 # - alias: list of 2-tuples of strings
24 24 # - experimental: boolean
25 25 # - documentation: string
26 # - in_core_extension: string
26 27 #
27 28 # ## Template
28 29 #
29 30 # Declares a group of options to be re-used for multiple sections.
30 31 #
31 32 # - all the same fields as `Item`, except `section` and `name`
32 33 # - `suffix` (string, required)
33 34 #
34 35 # ## Template applications
35 36 #
36 37 # Uses a `Template` to instanciate its options in a given section.
37 38 #
38 39 # - template: string (required, must match a `Template` name)
39 40 # - section: string (required)
40 41
41 42 [[items]]
42 43 section = "alias"
43 44 name = ".*"
44 45 default-type = "dynamic"
45 46 generic = true
46 47
47 48 [[items]]
48 49 section = "auth"
49 50 name = "cookiefile"
50 51
51 52 # bookmarks.pushing: internal hack for discovery
52 53 [[items]]
53 54 section = "bookmarks"
54 55 name = "pushing"
55 56 default-type = "list_type"
56 57
57 58 # bundle.mainreporoot: internal hack for bundlerepo
58 59 [[items]]
59 60 section = "bundle"
60 61 name = "mainreporoot"
61 62 default = ""
62 63
63 64 [[items]]
64 65 section = "censor"
65 66 name = "policy"
66 67 default = "abort"
67 68 experimental = true
68 69
69 70 [[items]]
70 71 section = "chgserver"
71 72 name = "idletimeout"
72 73 default = 3600
73 74
74 75 [[items]]
75 76 section = "chgserver"
76 77 name = "skiphash"
77 78 default = false
78 79
79 80 [[items]]
80 81 section = "cmdserver"
81 82 name = "log"
82 83
83 84 [[items]]
84 85 section = "cmdserver"
85 86 name = "max-log-files"
86 87 default = 7
87 88
88 89 [[items]]
89 90 section = "cmdserver"
90 91 name = "max-log-size"
91 92 default = "1 MB"
92 93
93 94 [[items]]
94 95 section = "cmdserver"
95 96 name = "max-repo-cache"
96 97 default = 0
97 98 experimental = true
98 99
99 100 [[items]]
100 101 section = "cmdserver"
101 102 name = "message-encodings"
102 103 default-type = "list_type"
103 104
104 105 [[items]]
105 106 section = "cmdserver"
106 107 name = "shutdown-on-interrupt"
107 108 default = true
108 109
109 110 [[items]]
110 111 section = "cmdserver"
111 112 name = "track-log"
112 113 default-type = "lambda"
113 114 default = [ "chgserver", "cmdserver", "repocache",]
114 115
115 116 [[items]]
116 117 section = "color"
117 118 name = ".*"
118 119 generic = true
119 120
120 121 [[items]]
121 122 section = "color"
122 123 name = "mode"
123 124 default = "auto"
124 125
125 126 [[items]]
126 127 section = "color"
127 128 name = "pagermode"
128 129 default-type = "dynamic"
129 130
130 131 [[items]]
131 132 section = "command-templates"
132 133 name = "graphnode"
133 134 alias = [["ui", "graphnodetemplate"]]
134 135
135 136 [[items]]
136 137 section = "command-templates"
137 138 name = "log"
138 139 alias = [["ui", "logtemplate"]]
139 140
140 141 [[items]]
141 142 section = "command-templates"
142 143 name = "mergemarker"
143 144 default = '{node|short} {ifeq(tags, "tip", "", ifeq(tags, "", "", "{tags} "))}{if(bookmarks, "{bookmarks} ")}{ifeq(branch, "default", "", "{branch} ")}- {author|user}: {desc|firstline}'
144 145 alias = [["ui", "mergemarkertemplate"]]
145 146
146 147 [[items]]
147 148 section = "command-templates"
148 149 name = "oneline-summary"
149 150
150 151 [[items]]
151 152 section = "command-templates"
152 153 name = "oneline-summary.*"
153 154 default-type = "dynamic"
154 155 generic = true
155 156
156 157 [[items]]
157 158 section = "command-templates"
158 159 name = "pre-merge-tool-output"
159 160 alias = [["ui", "pre-merge-tool-output-template"]]
160 161
161 162 [[items]]
162 163 section = "commands"
163 164 name = "commit.post-status"
164 165 default = false
165 166
166 167 [[items]]
167 168 section = "commands"
168 169 name = "grep.all-files"
169 170 default = false
170 171 experimental = true
171 172
172 173 [[items]]
173 174 section = "commands"
174 175 name = "merge.require-rev"
175 176 default = false
176 177
177 178 [[items]]
178 179 section = "commands"
179 180 name = "push.require-revs"
180 181 default = false
181 182
182 183 # Rebase related configuration moved to core because other extension are doing
183 184 # strange things. For example, shelve import the extensions to reuse some bit
184 185 # without formally loading it.
185 186 [[items]]
186 187 section = "commands"
187 188 name = "rebase.requiredest"
188 189 default = false
189 190
190 191 [[items]]
191 192 section = "commands"
192 193 name = "resolve.confirm"
193 194 default = false
194 195
195 196 [[items]]
196 197 section = "commands"
197 198 name = "resolve.explicit-re-merge"
198 199 default = false
199 200
200 201 [[items]]
201 202 section = "commands"
202 203 name = "resolve.mark-check"
203 204 default = "none"
204 205
205 206 [[items]]
206 207 section = "commands"
207 208 name = "show.aliasprefix"
208 209 default-type = "list_type"
209 210
210 211 [[items]]
211 212 section = "commands"
212 213 name = "status.relative"
213 214 default = false
214 215
215 216 [[items]]
216 217 section = "commands"
217 218 name = "status.skipstates"
218 219 default = []
219 220 experimental = true
220 221
221 222 [[items]]
222 223 section = "commands"
223 224 name = "status.terse"
224 225 default = ""
225 226
226 227 [[items]]
227 228 section = "commands"
228 229 name = "status.verbose"
229 230 default = false
230 231
231 232 [[items]]
232 233 section = "commands"
233 234 name = "update.check"
234 235
235 236 [[items]]
236 237 section = "commands"
237 238 name = "update.requiredest"
238 239 default = false
239 240
240 241 [[items]]
241 242 section = "committemplate"
242 243 name = ".*"
243 244 generic = true
244 245
245 246 [[items]]
246 247 section = "convert"
247 248 name = "bzr.saverev"
248 249 default = true
249 250
250 251 [[items]]
251 252 section = "convert"
252 253 name = "cvsps.cache"
253 254 default = true
254 255
255 256 [[items]]
256 257 section = "convert"
257 258 name = "cvsps.fuzz"
258 259 default = 60
259 260
260 261 [[items]]
261 262 section = "convert"
262 263 name = "cvsps.logencoding"
263 264
264 265 [[items]]
265 266 section = "convert"
266 267 name = "cvsps.mergefrom"
267 268
268 269 [[items]]
269 270 section = "convert"
270 271 name = "cvsps.mergeto"
271 272
272 273 [[items]]
273 274 section = "convert"
274 275 name = "git.committeractions"
275 276 default-type = "lambda"
276 277 default = [ "messagedifferent",]
277 278
278 279 [[items]]
279 280 section = "convert"
280 281 name = "git.extrakeys"
281 282 default-type = "list_type"
282 283
283 284 [[items]]
284 285 section = "convert"
285 286 name = "git.findcopiesharder"
286 287 default = false
287 288
288 289 [[items]]
289 290 section = "convert"
290 291 name = "git.remoteprefix"
291 292 default = "remote"
292 293
293 294 [[items]]
294 295 section = "convert"
295 296 name = "git.renamelimit"
296 297 default = 400
297 298
298 299 [[items]]
299 300 section = "convert"
300 301 name = "git.saverev"
301 302 default = true
302 303
303 304 [[items]]
304 305 section = "convert"
305 306 name = "git.similarity"
306 307 default = 50
307 308
308 309 [[items]]
309 310 section = "convert"
310 311 name = "git.skipsubmodules"
311 312 default = false
312 313
313 314 [[items]]
314 315 section = "convert"
315 316 name = "hg.clonebranches"
316 317 default = false
317 318
318 319 [[items]]
319 320 section = "convert"
320 321 name = "hg.ignoreerrors"
321 322 default = false
322 323
323 324 [[items]]
324 325 section = "convert"
325 326 name = "hg.preserve-hash"
326 327 default = false
327 328
328 329 [[items]]
329 330 section = "convert"
330 331 name = "hg.revs"
331 332
332 333 [[items]]
333 334 section = "convert"
334 335 name = "hg.saverev"
335 336 default = false
336 337
337 338 [[items]]
338 339 section = "convert"
339 340 name = "hg.sourcename"
340 341
341 342 [[items]]
342 343 section = "convert"
343 344 name = "hg.startrev"
344 345
345 346 [[items]]
346 347 section = "convert"
347 348 name = "hg.tagsbranch"
348 349 default = "default"
349 350
350 351 [[items]]
351 352 section = "convert"
352 353 name = "hg.usebranchnames"
353 354 default = true
354 355
355 356 [[items]]
356 357 section = "convert"
357 358 name = "ignoreancestorcheck"
358 359 default = false
359 360 experimental = true
360 361
361 362 [[items]]
362 363 section = "convert"
363 364 name = "localtimezone"
364 365 default = false
365 366
366 367 [[items]]
367 368 section = "convert"
368 369 name = "p4.encoding"
369 370 default-type = "dynamic"
370 371
371 372 [[items]]
372 373 section = "convert"
373 374 name = "p4.startrev"
374 375 default = 0
375 376
376 377 [[items]]
377 378 section = "convert"
378 379 name = "skiptags"
379 380 default = false
380 381
381 382 [[items]]
382 383 section = "convert"
383 384 name = "svn.branches"
384 385
385 386 [[items]]
386 387 section = "convert"
387 388 name = "svn.dangerous-set-commit-dates"
388 389 default = false
389 390
390 391 [[items]]
391 392 section = "convert"
392 393 name = "svn.debugsvnlog"
393 394 default = true
394 395
395 396 [[items]]
396 397 section = "convert"
397 398 name = "svn.startrev"
398 399 default = 0
399 400
400 401 [[items]]
401 402 section = "convert"
402 403 name = "svn.tags"
403 404
404 405 [[items]]
405 406 section = "convert"
406 407 name = "svn.trunk"
407 408
408 409 [[items]]
409 410 section = "debug"
410 411 name = "bundling-stats"
411 412 default = false
412 413 documentation = "Display extra information about the bundling process."
413 414
414 415 [[items]]
415 416 section = "debug"
416 417 name = "dirstate.delaywrite"
417 418 default = 0
418 419
419 420 [[items]]
420 421 section = "debug"
421 422 name = "revlog.debug-delta"
422 423 default = false
423 424
424 425 [[items]]
425 426 section = "debug"
426 427 name = "revlog.verifyposition.changelog"
427 428 default = ""
428 429
429 430 [[items]]
430 431 section = "debug"
431 432 name = "unbundling-stats"
432 433 default = false
433 434 documentation = "Display extra information about the unbundling process."
434 435
435 436 [[items]]
436 437 section = "defaults"
437 438 name = ".*"
438 439 generic = true
439 440
440 441 [[items]]
441 442 section = "devel"
442 443 name = "all-warnings"
443 444 default = false
444 445
445 446 [[items]]
446 447 section = "devel"
447 448 name = "bundle.delta"
448 449 default = ""
449 450
450 451 [[items]]
451 452 section = "devel"
452 453 name = "bundle2.debug"
453 454 default = false
454 455
455 456 [[items]]
456 457 section = "devel"
457 458 name = "cache-vfs"
458 459
459 460 [[items]]
460 461 section = "devel"
461 462 name = "check-locks"
462 463 default = false
463 464
464 465 [[items]]
465 466 section = "devel"
466 467 name = "check-relroot"
467 468 default = false
468 469
469 470 [[items]]
470 471 section = "devel"
471 472 name = "copy-tracing.multi-thread"
472 473 default = true
473 474
474 475 # Track copy information for all files, not just "added" ones (very slow)
475 476 [[items]]
476 477 section = "devel"
477 478 name = "copy-tracing.trace-all-files"
478 479 default = false
479 480
480 481 [[items]]
481 482 section = "devel"
482 483 name = "debug.abort-update"
483 484 default = false
484 485 documentation = """If true, then any merge with the working copy, \
485 486 e.g. [hg update], will be aborted after figuring out what needs to be done, \
486 487 but before spawning the parallel worker."""
487 488
488 489 [[items]]
489 490 section = "devel"
490 491 name = "debug.copies"
491 492 default = false
492 493
493 494 [[items]]
494 495 section = "devel"
495 496 name = "debug.extensions"
496 497 default = false
497 498
498 499 [[items]]
499 500 section = "devel"
500 501 name = "debug.peer-request"
501 502 default = false
502 503
503 504 [[items]]
504 505 section = "devel"
505 506 name = "debug.repo-filters"
506 507 default = false
507 508
508 509 [[items]]
509 510 section = "devel"
510 511 name = "default-date"
511 512
512 513 [[items]]
513 514 section = "devel"
514 515 name = "deprec-warn"
515 516 default = false
516 517
517 518 # possible values:
518 519 # - auto (the default)
519 520 # - force-append
520 521 # - force-new
521 522 [[items]]
522 523 section = "devel"
523 524 name = "dirstate.v2.data_update_mode"
524 525 default = "auto"
525 526
526 527 [[items]]
527 528 section = "devel"
528 529 name = "disableloaddefaultcerts"
529 530 default = false
530 531
531 532 [[items]]
532 533 section = "devel"
533 534 name = "discovery.exchange-heads"
534 535 default = true
535 536 documentation = """If false, the discovery will not start with remote \
536 537 head fetching and local head querying."""
537 538
538 539 [[items]]
539 540 section = "devel"
540 541 name = "discovery.grow-sample"
541 542 default = true
542 543 documentation = """If false, the sample size used in set discovery \
543 544 will not be increased through the process."""
544 545
545 546 [[items]]
546 547 section = "devel"
547 548 name = "discovery.grow-sample.dynamic"
548 549 default = true
549 550 documentation = """If true, the default, the sample size is adapted to the shape \
550 551 of the undecided set. It is set to the max of:
551 552 `<target-size>, len(roots(undecided)), len(heads(undecided))`"""
552 553
553 554 [[items]]
554 555 section = "devel"
555 556 name = "discovery.grow-sample.rate"
556 557 default = 1.05
557 558 documentation = "Controls the rate at which the sample grows."
558 559
559 560 [[items]]
560 561 section = "devel"
561 562 name = "discovery.randomize"
562 563 default = true
563 564 documentation = """If false, random samplings during discovery are deterministic. \
564 565 It is meant for integration tests."""
565 566
566 567 [[items]]
567 568 section = "devel"
568 569 name = "discovery.sample-size"
569 570 default = 200
570 571 documentation = "Controls the initial size of the discovery sample."
571 572
572 573 [[items]]
573 574 section = "devel"
574 575 name = "discovery.sample-size.initial"
575 576 default = 100
576 577 documentation = "Controls the initial size of the discovery for initial change."
577 578
578 579 [[items]]
579 580 section = "devel"
580 581 name = "legacy.exchange"
581 582 default-type = "list_type"
582 583
583 584 [[items]]
584 585 section = "devel"
585 586 name = "persistent-nodemap"
586 587 default = false
587 588 documentation = """When true, revlogs use a special reference version of the \
588 589 nodemap, that is not performant but is "known" to behave properly."""
589 590
590 591 [[items]]
591 592 section = "devel"
592 593 name = "server-insecure-exact-protocol"
593 594 default = ""
594 595
595 596 [[items]]
596 597 section = "devel"
597 598 name = "servercafile"
598 599 default = ""
599 600
600 601 [[items]]
601 602 section = "devel"
602 603 name = "serverexactprotocol"
603 604 default = ""
604 605
605 606 [[items]]
606 607 section = "devel"
607 608 name = "serverrequirecert"
608 609 default = false
609 610
610 611 [[items]]
611 612 section = "devel"
612 613 name = "strip-obsmarkers"
613 614 default = true
614 615
615 616 [[items]]
616 617 section = 'devel'
617 618 name = 'sync.status.pre-dirstate-write-file'
618 619 documentation = """
619 620 Makes the status algorithm wait for the existence of this file \
620 621 (or until a timeout of `devel.sync.status.pre-dirstate-write-file-timeout` \
621 622 seconds) before taking the lock and writing the dirstate. \
622 623 Status signals that it's ready to wait by creating a file \
623 624 with the same name + `.waiting`. \
624 625 Useful when testing race conditions."""
625 626
626 627 [[items]]
627 628 section = 'devel'
628 629 name = 'sync.status.pre-dirstate-write-file-timeout'
629 630 default=2
630 631
631 632 [[items]]
632 633 section = 'devel'
633 634 name = 'sync.dirstate.post-docket-read-file'
634 635
635 636 [[items]]
636 637 section = 'devel'
637 638 name = 'sync.dirstate.post-docket-read-file-timeout'
638 639 default=2
639 640
640 641 [[items]]
641 642 section = 'devel'
642 643 name = 'sync.dirstate.pre-read-file'
643 644
644 645 [[items]]
645 646 section = 'devel'
646 647 name = 'sync.dirstate.pre-read-file-timeout'
647 648 default=2
648 649
649 650 [[items]]
650 651 section = "devel"
651 652 name = "user.obsmarker"
652 653
653 654 [[items]]
654 655 section = "devel"
655 656 name = "warn-config"
656 657
657 658 [[items]]
658 659 section = "devel"
659 660 name = "warn-config-default"
660 661
661 662 [[items]]
662 663 section = "devel"
663 664 name = "warn-config-unknown"
664 665
665 666 [[items]]
666 667 section = "devel"
667 668 name = "warn-empty-changegroup"
668 669 default = false
669 670
670 671 [[items]]
671 672 section = "diff"
672 673 name = "merge"
673 674 default = false
674 675 experimental = true
675 676
676 677 [[items]]
677 678 section = "email"
678 679 name = "bcc"
679 680
680 681 [[items]]
681 682 section = "email"
682 683 name = "cc"
683 684
684 685 [[items]]
685 686 section = "email"
686 687 name = "charsets"
687 688 default-type = "list_type"
688 689
689 690 [[items]]
690 691 section = "email"
691 692 name = "from"
692 693
693 694 [[items]]
694 695 section = "email"
695 696 name = "method"
696 697 default = "smtp"
697 698
698 699 [[items]]
699 700 section = "email"
700 701 name = "reply-to"
701 702
702 703 [[items]]
703 704 section = "email"
704 705 name = "to"
705 706
706 707 [[items]]
707 708 section = "experimental"
708 709 name = "archivemetatemplate"
709 710 default-type = "dynamic"
710 711
711 712 [[items]]
712 713 section = "experimental"
713 714 name = "auto-publish"
714 715 default = "publish"
715 716
716 717 [[items]]
717 718 section = "experimental"
718 719 name = "bundle-phases"
719 720 default = false
720 721
721 722 [[items]]
722 723 section = "experimental"
723 724 name = "bundle2-advertise"
724 725 default = true
725 726
726 727 [[items]]
727 728 section = "experimental"
728 729 name = "bundle2-output-capture"
729 730 default = false
730 731
731 732 [[items]]
732 733 section = "experimental"
733 734 name = "bundle2.pushback"
734 735 default = false
735 736
736 737 [[items]]
737 738 section = "experimental"
738 739 name = "bundle2lazylocking"
739 740 default = false
740 741
741 742 [[items]]
742 743 section = "experimental"
743 744 name = "bundlecomplevel"
744 745
745 746 [[items]]
746 747 section = "experimental"
747 748 name = "bundlecomplevel.bzip2"
748 749
749 750 [[items]]
750 751 section = "experimental"
751 752 name = "bundlecomplevel.gzip"
752 753
753 754 [[items]]
754 755 section = "experimental"
755 756 name = "bundlecomplevel.none"
756 757
757 758 [[items]]
758 759 section = "experimental"
759 760 name = "bundlecomplevel.zstd"
760 761
761 762 [[items]]
762 763 section = "experimental"
763 764 name = "bundlecompthreads"
764 765
765 766 [[items]]
766 767 section = "experimental"
767 768 name = "bundlecompthreads.bzip2"
768 769
769 770 [[items]]
770 771 section = "experimental"
771 772 name = "bundlecompthreads.gzip"
772 773
773 774 [[items]]
774 775 section = "experimental"
775 776 name = "bundlecompthreads.none"
776 777
777 778 [[items]]
778 779 section = "experimental"
779 780 name = "bundlecompthreads.zstd"
780 781
781 782 [[items]]
782 783 section = "experimental"
783 784 name = "changegroup3"
784 785 default = true
785 786
786 787 [[items]]
787 788 section = "experimental"
788 789 name = "changegroup4"
789 790 default = false
790 791
791 792 # might remove rank configuration once the computation has no impact
792 793 [[items]]
793 794 section = "experimental"
794 795 name = "changelog-v2.compute-rank"
795 796 default = true
796 797
797 798 [[items]]
798 799 section = "experimental"
799 800 name = "cleanup-as-archived"
800 801 default = false
801 802
802 803 [[items]]
803 804 section = "experimental"
804 805 name = "clientcompressionengines"
805 806 default-type = "list_type"
806 807
807 808 [[items]]
808 809 section = "experimental"
809 810 name = "copies.read-from"
810 811 default = "filelog-only"
811 812
812 813 [[items]]
813 814 section = "experimental"
814 815 name = "copies.write-to"
815 816 default = "filelog-only"
816 817
817 818 [[items]]
818 819 section = "experimental"
819 820 name = "copytrace"
820 821 default = "on"
821 822
822 823 [[items]]
823 824 section = "experimental"
824 825 name = "copytrace.movecandidateslimit"
825 826 default = 100
826 827
827 828 [[items]]
828 829 section = "experimental"
829 830 name = "copytrace.sourcecommitlimit"
830 831 default = 100
831 832
832 833 [[items]]
833 834 section = "experimental"
834 835 name = "crecordtest"
835 836
836 837 [[items]]
837 838 section = "experimental"
838 839 name = "directaccess"
839 840 default = false
840 841
841 842 [[items]]
842 843 section = "experimental"
843 844 name = "directaccess.revnums"
844 845 default = false
845 846
846 847 [[items]]
847 848 section = "experimental"
848 849 name = "editortmpinhg"
849 850 default = false
850 851
851 852 [[items]]
852 853 section = "experimental"
853 854 name = "evolution"
854 855 default-type = "list_type"
855 856
856 857 [[items]]
857 858 section = "experimental"
858 859 name = "evolution.allowdivergence"
859 860 default = false
860 861 alias = [["experimental", "allowdivergence"]]
861 862
862 863 [[items]]
863 864 section = "experimental"
864 865 name = "evolution.allowunstable"
865 866
866 867 [[items]]
867 868 section = "experimental"
868 869 name = "evolution.bundle-obsmarker"
869 870 default = false
870 871
871 872 [[items]]
872 873 section = "experimental"
873 874 name = "evolution.bundle-obsmarker:mandatory"
874 875 default = true
875 876
876 877 [[items]]
877 878 section = "experimental"
878 879 name = "evolution.createmarkers"
879 880
880 881 [[items]]
881 882 section = "experimental"
882 883 name = "evolution.effect-flags"
883 884 default = true
884 885 alias = [["experimental", "effect-flags"]]
885 886
886 887 [[items]]
887 888 section = "experimental"
888 889 name = "evolution.exchange"
889 890
890 891 [[items]]
891 892 section = "experimental"
892 893 name = "evolution.report-instabilities"
893 894 default = true
894 895
895 896 [[items]]
896 897 section = "experimental"
897 898 name = "evolution.track-operation"
898 899 default = true
899 900
900 901 [[items]]
901 902 section = "experimental"
902 903 name = "exportableenviron"
903 904 default-type = "list_type"
904 905
905 906 [[items]]
906 907 section = "experimental"
907 908 name = "extendedheader.index"
908 909
909 910 [[items]]
910 911 section = "experimental"
911 912 name = "extendedheader.similarity"
912 913 default = false
913 914
914 915 [[items]]
915 916 section = "experimental"
916 917 name = "extra-filter-revs"
917 918 documentation = """Repo-level config to prevent a revset from being visible.
918 919 The target use case is to use `share` to expose different subsets of the same \
919 920 repository, especially server side. See also `server.view`."""
920 921
921 922 [[items]]
922 923 section = "experimental"
923 924 name = "graphshorten"
924 925 default = false
925 926
926 927 [[items]]
927 928 section = "experimental"
928 929 name = "graphstyle.grandparent"
929 930 default-type = "dynamic"
930 931
931 932 [[items]]
932 933 section = "experimental"
933 934 name = "graphstyle.missing"
934 935 default-type = "dynamic"
935 936
936 937 [[items]]
937 938 section = "experimental"
938 939 name = "graphstyle.parent"
939 940 default-type = "dynamic"
940 941
941 942 [[items]]
942 943 section = "experimental"
943 944 name = "hook-track-tags"
944 945 default = false
945 946
946 947 [[items]]
947 948 section = "experimental"
948 949 name = "httppostargs"
949 950 default = false
950 951
951 952 [[items]]
952 953 section = "experimental"
953 954 name = "log.topo"
954 955 default = false
955 956
956 957 [[items]]
957 958 section = "experimental"
958 959 name = "maxdeltachainspan"
959 960 default = -1
960 961
961 962 [[items]]
962 963 section = "experimental"
963 964 name = "merge-track-salvaged"
964 965 default = false
965 966 documentation = """Tracks files which were undeleted (merge might delete them \
966 967 but we explicitly kept/undeleted them) and creates new filenodes for them."""
967 968
968 969 [[items]]
969 970 section = "experimental"
970 971 name = "merge.checkpathconflicts"
971 972 default = false
972 973
973 974 [[items]]
974 975 section = "experimental"
975 976 name = "mmapindexthreshold"
976 977
977 978 [[items]]
978 979 section = "experimental"
979 980 name = "narrow"
980 981 default = false
981 982
982 983 [[items]]
983 984 section = "experimental"
984 985 name = "nointerrupt"
985 986 default = false
986 987
987 988 [[items]]
988 989 section = "experimental"
989 990 name = "nointerrupt-interactiveonly"
990 991 default = true
991 992
992 993 [[items]]
993 994 section = "experimental"
994 995 name = "nonnormalparanoidcheck"
995 996 default = false
996 997
997 998 [[items]]
998 999 section = "experimental"
999 1000 name = "obsmarkers-exchange-debug"
1000 1001 default = false
1001 1002
1002 1003 [[items]]
1003 1004 section = "experimental"
1004 1005 name = "rebaseskipobsolete"
1005 1006 default = true
1006 1007
1007 1008 [[items]]
1008 1009 section = "experimental"
1009 1010 name = "remotenames"
1010 1011 default = false
1011 1012
1012 1013 [[items]]
1013 1014 section = "experimental"
1014 1015 name = "removeemptydirs"
1015 1016 default = true
1016 1017
1017 1018 [[items]]
1018 1019 section = "experimental"
1019 1020 name = "revert.interactive.select-to-keep"
1020 1021 default = false
1021 1022
1022 1023 [[items]]
1023 1024 section = "experimental"
1024 1025 name = "revisions.disambiguatewithin"
1025 1026
1026 1027 [[items]]
1027 1028 section = "experimental"
1028 1029 name = "revisions.prefixhexnode"
1029 1030 default = false
1030 1031
1031 1032 # "out of experimental" todo list.
1032 1033 #
1033 1034 # * include management of a persistent nodemap in the main docket
1034 1035 # * enforce a "no-truncate" policy for mmap safety
1035 1036 # - for censoring operation
1036 1037 # - for stripping operation
1037 1038 # - for rollback operation
1038 1039 # * proper streaming (race free) of the docket file
1039 1040 # * track garbage data to evemtually allow rewriting -existing- sidedata.
1040 1041 # * Exchange-wise, we will also need to do something more efficient than
1041 1042 # keeping references to the affected revlogs, especially memory-wise when
1042 1043 # rewriting sidedata.
1043 1044 # * introduce a proper solution to reduce the number of filelog related files.
1044 1045 # * use caching for reading sidedata (similar to what we do for data).
1045 1046 # * no longer set offset=0 if sidedata_size=0 (simplify cutoff computation).
1046 1047 # * Improvement to consider
1047 1048 # - avoid compression header in chunk using the default compression?
1048 1049 # - forbid "inline" compression mode entirely?
1049 1050 # - split the data offset and flag field (the 2 bytes save are mostly trouble)
1050 1051 # - keep track of uncompressed -chunk- size (to preallocate memory better)
1051 1052 # - keep track of chain base or size (probably not that useful anymore)
1052 1053 [[items]]
1053 1054 section = "experimental"
1054 1055 name = "revlogv2"
1055 1056
1056 1057 [[items]]
1057 1058 section = "experimental"
1058 1059 name = "rust.index"
1059 1060 default = false
1060 1061
1061 1062 [[items]]
1062 1063 section = "experimental"
1063 1064 name = "server.allow-hidden-access"
1064 1065 default-type = "list_type"
1065 1066
1066 1067 [[items]]
1067 1068 section = "experimental"
1068 1069 name = "server.filesdata.recommended-batch-size"
1069 1070 default = 50000
1070 1071
1071 1072 [[items]]
1072 1073 section = "experimental"
1073 1074 name = "server.manifestdata.recommended-batch-size"
1074 1075 default = 100000
1075 1076
1076 1077 [[items]]
1077 1078 section = "experimental"
1078 1079 name = "server.stream-narrow-clones"
1079 1080 default = false
1080 1081
1081 1082 [[items]]
1082 1083 section = "experimental"
1083 1084 name = "single-head-per-branch"
1084 1085 default = false
1085 1086
1086 1087 [[items]]
1087 1088 section = "experimental"
1088 1089 name = "single-head-per-branch:account-closed-heads"
1089 1090 default = false
1090 1091
1091 1092 [[items]]
1092 1093 section = "experimental"
1093 1094 name = "single-head-per-branch:public-changes-only"
1094 1095 default = false
1095 1096
1096 1097 [[items]]
1097 1098 section = "experimental"
1098 1099 name = "sparse-read"
1099 1100 default = false
1100 1101
1101 1102 [[items]]
1102 1103 section = "experimental"
1103 1104 name = "sparse-read.density-threshold"
1104 1105 default = 0.5
1105 1106
1106 1107 [[items]]
1107 1108 section = "experimental"
1108 1109 name = "sparse-read.min-gap-size"
1109 1110 default = "65K"
1110 1111
1111 1112 [[items]]
1112 1113 section = "experimental"
1113 1114 name = "stream-v3"
1114 1115 default = false
1115 1116
1116 1117 [[items]]
1117 1118 section = "experimental"
1118 1119 name = "treemanifest"
1119 1120 default = false
1120 1121
1121 1122 [[items]]
1122 1123 section = "experimental"
1123 1124 name = "update.atomic-file"
1124 1125 default = false
1125 1126
1126 1127 [[items]]
1127 1128 section = "experimental"
1128 1129 name = "web.full-garbage-collection-rate"
1129 1130 default = 1 # still forcing a full collection on each request
1130 1131
1131 1132 [[items]]
1132 1133 section = "experimental"
1133 1134 name = "worker.repository-upgrade"
1134 1135 default = false
1135 1136
1136 1137 [[items]]
1137 1138 section = "experimental"
1138 1139 name = "worker.wdir-get-thread-safe"
1139 1140 default = false
1140 1141
1141 1142 [[items]]
1142 1143 section = "experimental"
1143 1144 name = "xdiff"
1144 1145 default = false
1145 1146
1146 1147 [[items]]
1147 1148 section = "extdata"
1148 1149 name = ".*"
1149 1150 generic = true
1150 1151
1151 1152 [[items]]
1152 1153 section = "extensions"
1153 1154 name = "[^:]*"
1154 1155 generic = true
1155 1156
1156 1157 [[items]]
1157 1158 section = "extensions"
1158 1159 name = "[^:]*:required"
1159 1160 default = false
1160 1161 generic = true
1161 1162
1162 1163 [[items]]
1163 1164 section = "format"
1164 1165 name = "bookmarks-in-store"
1165 1166 default = false
1166 1167
1167 1168 [[items]]
1168 1169 section = "format"
1169 1170 name = "chunkcachesize"
1170 1171 experimental = true
1171 1172
1172 1173 [[items]]
1173 1174 section = "format"
1174 1175 name = "dotencode"
1175 1176 default = true
1176 1177
1177 1178 # The interaction between the archived phase and obsolescence markers needs to
1178 1179 # be sorted out before wider usage of this are to be considered.
1179 1180 #
1180 1181 # At the time this message is written, behavior when archiving obsolete
1181 1182 # changeset differ significantly from stripping. As part of stripping, we also
1182 1183 # remove the obsolescence marker associated to the stripped changesets,
1183 1184 # revealing the precedecessors changesets when applicable. When archiving, we
1184 1185 # don't touch the obsolescence markers, keeping everything hidden. This can
1185 1186 # result in quite confusing situation for people combining exchanging draft
1186 1187 # with the archived phases. As some markers needed by others may be skipped
1187 1188 # during exchange.
1188 1189 [[items]]
1189 1190 section = "format"
1190 1191 name = "exp-archived-phase"
1191 1192 default = false
1192 1193 experimental = true
1193 1194
1194 1195 # Experimental TODOs:
1195 1196 #
1196 1197 # * Same as for revlogv2 (but for the reduction of the number of files)
1197 1198 # * Actually computing the rank of changesets
1198 1199 # * Improvement to investigate
1199 1200 # - storing .hgtags fnode
1200 1201 # - storing branch related identifier
1201 1202 [[items]]
1202 1203 section = "format"
1203 1204 name = "exp-use-changelog-v2"
1204 1205 experimental = true
1205 1206
1206 1207 [[items]]
1207 1208 section = "format"
1208 1209 name = "exp-use-copies-side-data-changeset"
1209 1210 default = false
1210 1211 experimental = true
1211 1212
1212 1213 [[items]]
1213 1214 section = "format"
1214 1215 name = "generaldelta"
1215 1216 default = false
1216 1217 experimental = true
1217 1218
1218 1219 [[items]]
1219 1220 section = "format"
1220 1221 name = "manifestcachesize"
1221 1222 experimental = true
1222 1223
1223 1224 [[items]]
1224 1225 section = "format"
1225 1226 name = "maxchainlen"
1226 1227 default-type = "dynamic"
1227 1228 experimental = true
1228 1229
1229 1230 [[items]]
1230 1231 section = "format"
1231 1232 name = "obsstore-version"
1232 1233
1233 1234 [[items]]
1234 1235 section = "format"
1235 1236 name = "revlog-compression"
1236 1237 default-type = "lambda"
1237 1238 alias = [["experimental", "format.compression"]]
1238 1239 default = [ "zstd", "zlib",]
1239 1240
1240 1241 [[items]]
1241 1242 section = "format"
1242 1243 name = "sparse-revlog"
1243 1244 default = true
1244 1245
1245 1246 [[items]]
1246 1247 section = "format"
1247 1248 name = "use-dirstate-tracked-hint"
1248 1249 default = false
1249 1250 experimental = true
1250 1251
1251 1252 [[items]]
1252 1253 section = "format"
1253 1254 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories"
1254 1255 default = false
1255 1256 experimental = true
1256 1257
1257 1258 [[items]]
1258 1259 section = "format"
1259 1260 name = "use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories:quiet"
1260 1261 default = false
1261 1262 experimental = true
1262 1263
1263 1264 [[items]]
1264 1265 section = "format"
1265 1266 name = "use-dirstate-tracked-hint.version"
1266 1267 default = 1
1267 1268 experimental = true
1268 1269
1269 1270 [[items]]
1270 1271 section = "format"
1271 1272 name = "use-dirstate-v2"
1272 1273 default = false
1273 1274 alias = [["format", "exp-rc-dirstate-v2"]]
1274 1275 experimental = true
1275 1276 documentation = """Enables dirstate-v2 format *when creating a new repository*.
1276 1277 Which format to use for existing repos is controlled by `.hg/requires`."""
1277 1278
1278 1279 [[items]]
1279 1280 section = "format"
1280 1281 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories"
1281 1282 default = false
1282 1283 experimental = true
1283 1284
1284 1285 [[items]]
1285 1286 section = "format"
1286 1287 name = "use-dirstate-v2.automatic-upgrade-of-mismatching-repositories:quiet"
1287 1288 default = false
1288 1289 experimental = true
1289 1290
1290 1291 # Having this on by default means we are confident about the scaling of phases.
1291 1292 # This is not garanteed to be the case at the time this message is written.
1292 1293 [[items]]
1293 1294 section = "format"
1294 1295 name = "use-internal-phase"
1295 1296 default = false
1296 1297 experimental = true
1297 1298
1298 1299 [[items]]
1299 1300 section = "format"
1300 1301 name = "use-persistent-nodemap"
1301 1302 default-type = "dynamic"
1302 1303
1303 1304 [[items]]
1304 1305 section = "format"
1305 1306 name = "use-share-safe"
1306 1307 default = true
1307 1308
1308 1309 [[items]]
1309 1310 section = "format"
1310 1311 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories"
1311 1312 default = false
1312 1313 experimental = true
1313 1314
1314 1315 [[items]]
1315 1316 section = "format"
1316 1317 name = "use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet"
1317 1318 default = false
1318 1319 experimental = true
1319 1320
1320 1321 [[items]]
1321 1322 section = "format"
1322 1323 name = "usefncache"
1323 1324 default = true
1324 1325
1325 1326 [[items]]
1326 1327 section = "format"
1327 1328 name = "usegeneraldelta"
1328 1329 default = true
1329 1330
1330 1331 [[items]]
1331 1332 section = "format"
1332 1333 name = "usestore"
1333 1334 default = true
1334 1335
1335 1336 [[items]]
1336 1337 section = "fsmonitor"
1337 1338 name = "warn_update_file_count"
1338 1339 default = 50000
1339 1340
1340 1341 [[items]]
1341 1342 section = "fsmonitor"
1342 1343 name = "warn_update_file_count_rust"
1343 1344 default = 400000
1344 1345
1345 1346 [[items]]
1346 1347 section = "fsmonitor"
1347 1348 name = "warn_when_unused"
1348 1349 default = true
1349 1350
1350 1351 [[items]]
1351 1352 section = "help"
1352 1353 name = 'hidden-command\..*'
1353 1354 default = false
1354 1355 generic = true
1355 1356
1356 1357 [[items]]
1357 1358 section = "help"
1358 1359 name = 'hidden-topic\..*'
1359 1360 default = false
1360 1361 generic = true
1361 1362
1362 1363 [[items]]
1363 1364 section = "hgweb-paths"
1364 1365 name = ".*"
1365 1366 default-type = "list_type"
1366 1367 generic = true
1367 1368
1368 1369 [[items]]
1369 1370 section = "hooks"
1370 1371 name = ".*:run-with-plain"
1371 1372 default = true
1372 1373 generic = true
1373 1374
1374 1375 [[items]]
1375 1376 section = "hooks"
1376 1377 name = "[^:]*"
1377 1378 default-type = "dynamic"
1378 1379 generic = true
1379 1380
1380 1381 [[items]]
1381 1382 section = "hostfingerprints"
1382 1383 name = ".*"
1383 1384 default-type = "list_type"
1384 1385 generic = true
1385 1386
1386 1387 [[items]]
1387 1388 section = "hostsecurity"
1388 1389 name = ".*:ciphers$"
1389 1390 default-type = "dynamic"
1390 1391 generic = true
1391 1392
1392 1393 [[items]]
1393 1394 section = "hostsecurity"
1394 1395 name = ".*:fingerprints$"
1395 1396 default-type = "list_type"
1396 1397 generic = true
1397 1398
1398 1399 [[items]]
1399 1400 section = "hostsecurity"
1400 1401 name = ".*:minimumprotocol$"
1401 1402 default-type = "dynamic"
1402 1403 generic = true
1403 1404
1404 1405 [[items]]
1405 1406 section = "hostsecurity"
1406 1407 name = ".*:verifycertsfile$"
1407 1408 generic = true
1408 1409
1409 1410 [[items]]
1410 1411 section = "hostsecurity"
1411 1412 name = "ciphers"
1412 1413
1413 1414 [[items]]
1414 1415 section = "hostsecurity"
1415 1416 name = "minimumprotocol"
1416 1417 default-type = "dynamic"
1417 1418
1418 1419 [[items]]
1419 1420 section = "http"
1420 1421 name = "timeout"
1421 1422
1422 1423 [[items]]
1423 1424 section = "http_proxy"
1424 1425 name = "always"
1425 1426 default = false
1426 1427
1427 1428 [[items]]
1428 1429 section = "http_proxy"
1429 1430 name = "host"
1430 1431
1431 1432 [[items]]
1432 1433 section = "http_proxy"
1433 1434 name = "no"
1434 1435 default-type = "list_type"
1435 1436
1436 1437 [[items]]
1437 1438 section = "http_proxy"
1438 1439 name = "passwd"
1439 1440
1440 1441 [[items]]
1441 1442 section = "http_proxy"
1442 1443 name = "user"
1443 1444
1444 1445 [[items]]
1445 1446 section = "logtoprocess"
1446 1447 name = "command"
1447 1448
1448 1449 [[items]]
1449 1450 section = "logtoprocess"
1450 1451 name = "commandexception"
1451 1452
1452 1453 [[items]]
1453 1454 section = "logtoprocess"
1454 1455 name = "commandfinish"
1455 1456
1456 1457 [[items]]
1457 1458 section = "logtoprocess"
1458 1459 name = "develwarn"
1459 1460
1460 1461 [[items]]
1461 1462 section = "logtoprocess"
1462 1463 name = "uiblocked"
1463 1464
1464 1465 [[items]]
1465 1466 section = "merge"
1466 1467 name = "checkignored"
1467 1468 default = "abort"
1468 1469
1469 1470 [[items]]
1470 1471 section = "merge"
1471 1472 name = "checkunknown"
1472 1473 default = "abort"
1473 1474
1474 1475 [[items]]
1475 1476 section = "merge"
1476 1477 name = "disable-partial-tools"
1477 1478 default = false
1478 1479 experimental = true
1479 1480
1480 1481 [[items]]
1481 1482 section = "merge"
1482 1483 name = "followcopies"
1483 1484 default = true
1484 1485
1485 1486 [[items]]
1486 1487 section = "merge"
1487 1488 name = "on-failure"
1488 1489 default = "continue"
1489 1490
1490 1491 [[items]]
1491 1492 section = "merge"
1492 1493 name = "preferancestor"
1493 1494 default-type = "lambda"
1494 1495 default = ["*"]
1495 1496 experimental = true
1496 1497
1497 1498 [[items]]
1498 1499 section = "merge"
1499 1500 name = "strict-capability-check"
1500 1501 default = false
1501 1502
1502 1503 [[items]]
1503 1504 section = "merge-tools"
1504 1505 name = ".*"
1505 1506 generic = true
1506 1507
1507 1508 [[items]]
1508 1509 section = "merge-tools"
1509 1510 name = '.*\.args$'
1510 1511 default = "$local $base $other"
1511 1512 generic = true
1512 1513 priority = -1
1513 1514
1514 1515 [[items]]
1515 1516 section = "merge-tools"
1516 1517 name = '.*\.binary$'
1517 1518 default = false
1518 1519 generic = true
1519 1520 priority = -1
1520 1521
1521 1522 [[items]]
1522 1523 section = "merge-tools"
1523 1524 name = '.*\.check$'
1524 1525 default-type = "list_type"
1525 1526 generic = true
1526 1527 priority = -1
1527 1528
1528 1529 [[items]]
1529 1530 section = "merge-tools"
1530 1531 name = '.*\.checkchanged$'
1531 1532 default = false
1532 1533 generic = true
1533 1534 priority = -1
1534 1535
1535 1536 [[items]]
1536 1537 section = "merge-tools"
1537 1538 name = '.*\.executable$'
1538 1539 default-type = "dynamic"
1539 1540 generic = true
1540 1541 priority = -1
1541 1542
1542 1543 [[items]]
1543 1544 section = "merge-tools"
1544 1545 name = '.*\.fixeol$'
1545 1546 default = false
1546 1547 generic = true
1547 1548 priority = -1
1548 1549
1549 1550 [[items]]
1550 1551 section = "merge-tools"
1551 1552 name = '.*\.gui$'
1552 1553 default = false
1553 1554 generic = true
1554 1555 priority = -1
1555 1556
1556 1557 [[items]]
1557 1558 section = "merge-tools"
1558 1559 name = '.*\.mergemarkers$'
1559 1560 default = "basic"
1560 1561 generic = true
1561 1562 priority = -1
1562 1563
1563 1564 [[items]]
1564 1565 section = "merge-tools"
1565 1566 name = '.*\.mergemarkertemplate$' # take from command-templates.mergemarker
1566 1567 default-type = "dynamic"
1567 1568 generic = true
1568 1569 priority = -1
1569 1570
1570 1571 [[items]]
1571 1572 section = "merge-tools"
1572 1573 name = '.*\.premerge$'
1573 1574 default-type = "dynamic"
1574 1575 generic = true
1575 1576 priority = -1
1576 1577
1577 1578 [[items]]
1578 1579 section = "merge-tools"
1579 1580 name = '.*\.priority$'
1580 1581 default = 0
1581 1582 generic = true
1582 1583 priority = -1
1583 1584
1584 1585 [[items]]
1585 1586 section = "merge-tools"
1586 1587 name = '.*\.regappend$'
1587 1588 default = ""
1588 1589 generic = true
1589 1590 priority = -1
1590 1591
1591 1592 [[items]]
1592 1593 section = "merge-tools"
1593 1594 name = '.*\.symlink$'
1594 1595 default = false
1595 1596 generic = true
1596 1597 priority = -1
1597 1598
1598 1599 [[items]]
1599 1600 section = "pager"
1600 1601 name = "attend-.*"
1601 1602 default-type = "dynamic"
1602 1603 generic = true
1603 1604
1604 1605 [[items]]
1605 1606 section = "pager"
1606 1607 name = "ignore"
1607 1608 default-type = "list_type"
1608 1609
1609 1610 [[items]]
1610 1611 section = "pager"
1611 1612 name = "pager"
1612 1613 default-type = "dynamic"
1613 1614
1614 1615 [[items]]
1615 1616 section = "partial-merge-tools"
1616 1617 name = ".*"
1617 1618 generic = true
1618 1619 experimental = true
1619 1620
1620 1621 [[items]]
1621 1622 section = "partial-merge-tools"
1622 1623 name = '.*\.args'
1623 1624 default = "$local $base $other"
1624 1625 generic = true
1625 1626 priority = -1
1626 1627 experimental = true
1627 1628
1628 1629 [[items]]
1629 1630 section = "partial-merge-tools"
1630 1631 name = '.*\.disable'
1631 1632 default = false
1632 1633 generic = true
1633 1634 priority = -1
1634 1635 experimental = true
1635 1636
1636 1637 [[items]]
1637 1638 section = "partial-merge-tools"
1638 1639 name = '.*\.executable$'
1639 1640 default-type = "dynamic"
1640 1641 generic = true
1641 1642 priority = -1
1642 1643 experimental = true
1643 1644
1644 1645 [[items]]
1645 1646 section = "partial-merge-tools"
1646 1647 name = '.*\.order'
1647 1648 default = 0
1648 1649 generic = true
1649 1650 priority = -1
1650 1651 experimental = true
1651 1652
1652 1653 [[items]]
1653 1654 section = "partial-merge-tools"
1654 1655 name = '.*\.patterns'
1655 1656 default-type = "dynamic"
1656 1657 generic = true
1657 1658 priority = -1
1658 1659 experimental = true
1659 1660
1660 1661 [[items]]
1661 1662 section = "patch"
1662 1663 name = "eol"
1663 1664 default = "strict"
1664 1665
1665 1666 [[items]]
1666 1667 section = "patch"
1667 1668 name = "fuzz"
1668 1669 default = 2
1669 1670
1670 1671 [[items]]
1671 1672 section = "paths"
1672 1673 name = "[^:]*"
1673 1674 generic = true
1674 1675
1675 1676 [[items]]
1676 1677 section = "paths"
1677 1678 name = ".*:bookmarks.mode"
1678 1679 default = "default"
1679 1680 generic = true
1680 1681
1681 1682 [[items]]
1682 1683 section = "paths"
1683 1684 name = ".*:multi-urls"
1684 1685 default = false
1685 1686 generic = true
1686 1687
1687 1688 [[items]]
1688 1689 section = "paths"
1689 1690 name = ".*:pulled-delta-reuse-policy"
1690 1691 generic = true
1691 1692
1692 1693 [[items]]
1693 1694 section = "paths"
1694 1695 name = ".*:pushrev"
1695 1696 generic = true
1696 1697
1697 1698 [[items]]
1698 1699 section = "paths"
1699 1700 name = ".*:pushurl"
1700 1701 generic = true
1701 1702
1702 1703 [[items]]
1703 1704 section = "paths"
1704 1705 name = "default"
1705 1706
1706 1707 [[items]]
1707 1708 section = "paths"
1708 1709 name = "default-push"
1709 1710
1710 1711 [[items]]
1711 1712 section = "phases"
1712 1713 name = "checksubrepos"
1713 1714 default = "follow"
1714 1715
1715 1716 [[items]]
1716 1717 section = "phases"
1717 1718 name = "new-commit"
1718 1719 default = "draft"
1719 1720
1720 1721 [[items]]
1721 1722 section = "phases"
1722 1723 name = "publish"
1723 1724 default = true
1724 1725
1725 1726 [[items]]
1726 1727 section = "profiling"
1727 1728 name = "enabled"
1728 1729 default = false
1729 1730
1730 1731 [[items]]
1731 1732 section = "profiling"
1732 1733 name = "format"
1733 1734 default = "text"
1734 1735
1735 1736 [[items]]
1736 1737 section = "profiling"
1737 1738 name = "freq"
1738 1739 default = 1000
1739 1740
1740 1741 [[items]]
1741 1742 section = "profiling"
1742 1743 name = "limit"
1743 1744 default = 30
1744 1745
1745 1746 [[items]]
1746 1747 section = "profiling"
1747 1748 name = "nested"
1748 1749 default = 0
1749 1750
1750 1751 [[items]]
1751 1752 section = "profiling"
1752 1753 name = "output"
1753 1754
1754 1755 [[items]]
1755 1756 section = "profiling"
1756 1757 name = "showmax"
1757 1758 default = 0.999
1758 1759
1759 1760 [[items]]
1760 1761 section = "profiling"
1761 1762 name = "showmin"
1762 1763 default-type = "dynamic"
1763 1764
1764 1765 [[items]]
1765 1766 section = "profiling"
1766 1767 name = "showtime"
1767 1768 default = true
1768 1769
1769 1770 [[items]]
1770 1771 section = "profiling"
1771 1772 name = "sort"
1772 1773 default = "inlinetime"
1773 1774
1774 1775 [[items]]
1775 1776 section = "profiling"
1776 1777 name = "statformat"
1777 1778 default = "hotpath"
1778 1779
1779 1780 [[items]]
1780 1781 section = "profiling"
1781 1782 name = "time-track"
1782 1783 default-type = "dynamic"
1783 1784
1784 1785 [[items]]
1785 1786 section = "profiling"
1786 1787 name = "type"
1787 1788 default = "stat"
1788 1789
1789 1790 [[items]]
1790 1791 section = "progress"
1791 1792 name = "assume-tty"
1792 1793 default = false
1793 1794
1794 1795 [[items]]
1795 1796 section = "progress"
1796 1797 name = "changedelay"
1797 1798 default = 1
1798 1799
1799 1800 [[items]]
1800 1801 section = "progress"
1801 1802 name = "clear-complete"
1802 1803 default = true
1803 1804
1804 1805 [[items]]
1805 1806 section = "progress"
1806 1807 name = "debug"
1807 1808 default = false
1808 1809
1809 1810 [[items]]
1810 1811 section = "progress"
1811 1812 name = "delay"
1812 1813 default = 3
1813 1814
1814 1815 [[items]]
1815 1816 section = "progress"
1816 1817 name = "disable"
1817 1818 default = false
1818 1819
1819 1820 [[items]]
1820 1821 section = "progress"
1821 1822 name = "estimateinterval"
1822 1823 default = 60.0
1823 1824
1824 1825 [[items]]
1825 1826 section = "progress"
1826 1827 name = "format"
1827 1828 default-type = "lambda"
1828 1829 default = [ "topic", "bar", "number", "estimate",]
1829 1830
1830 1831 [[items]]
1831 1832 section = "progress"
1832 1833 name = "refresh"
1833 1834 default = 0.1
1834 1835
1835 1836 [[items]]
1836 1837 section = "progress"
1837 1838 name = "width"
1838 1839 default-type = "dynamic"
1839 1840
1840 1841 [[items]]
1841 1842 section = "pull"
1842 1843 name = "confirm"
1843 1844 default = false
1844 1845
1845 1846 [[items]]
1846 1847 section = "push"
1847 1848 name = "pushvars.server"
1848 1849 default = false
1849 1850
1850 1851 [[items]]
1851 1852 section = "rebase"
1852 1853 name = "experimental.inmemory"
1853 1854 default = false
1854 1855
1855 1856 [[items]]
1856 1857 section = "rebase"
1857 1858 name = "singletransaction"
1858 1859 default = false
1859 1860
1860 1861 [[items]]
1861 1862 section = "rebase"
1862 1863 name = "store-source"
1863 1864 default = true
1864 1865 experimental = true
1865 1866 documentation = """Controls creation of a `rebase_source` extra field during rebase.
1866 1867 When false, no such field is created. This is useful e.g. for incrementally \
1867 1868 converting changesets and then rebasing them onto an existing repo.
1868 1869 WARNING: this is an advanced setting reserved for people who know \
1869 1870 exactly what they are doing. Misuse of this setting can easily \
1870 1871 result in obsmarker cycles and a vivid headache."""
1871 1872
1872 1873 [[items]]
1873 1874 section = "rewrite"
1874 1875 name = "backup-bundle"
1875 1876 default = true
1876 1877 alias = [["ui", "history-editing-backup"]]
1877 1878
1878 1879 [[items]]
1879 1880 section = "rewrite"
1880 1881 name = "empty-successor"
1881 1882 default = "skip"
1882 1883 experimental = true
1883 1884
1884 1885 [[items]]
1885 1886 section = "rewrite"
1886 1887 name = "update-timestamp"
1887 1888 default = false
1888 1889
1889 1890 [[items]]
1890 1891 section = "server"
1891 1892 name = "bookmarks-pushkey-compat"
1892 1893 default = true
1893 1894
1894 1895 [[items]]
1895 1896 section = "server"
1896 1897 name = "bundle1"
1897 1898 default = true
1898 1899
1899 1900 [[items]]
1900 1901 section = "server"
1901 1902 name = "bundle1.pull"
1902 1903
1903 1904 [[items]]
1904 1905 section = "server"
1905 1906 name = "bundle1.push"
1906 1907
1907 1908 [[items]]
1908 1909 section = "server"
1909 1910 name = "bundle1gd"
1910 1911
1911 1912 [[items]]
1912 1913 section = "server"
1913 1914 name = "bundle1gd.pull"
1914 1915
1915 1916 [[items]]
1916 1917 section = "server"
1917 1918 name = "bundle1gd.push"
1918 1919
1919 1920 [[items]]
1920 1921 section = "server"
1921 1922 name = "bundle2.stream"
1922 1923 default = true
1923 1924 alias = [["experimental", "bundle2.stream"]]
1924 1925
1925 1926 [[items]]
1926 1927 section = "server"
1927 1928 name = "compressionengines"
1928 1929 default-type = "list_type"
1929 1930
1930 1931 [[items]]
1931 1932 section = "server"
1932 1933 name = "concurrent-push-mode"
1933 1934 default = "check-related"
1934 1935
1935 1936 [[items]]
1936 1937 section = "server"
1937 1938 name = "disablefullbundle"
1938 1939 default = false
1939 1940
1940 1941 [[items]]
1941 1942 section = "server"
1942 1943 name = "maxhttpheaderlen"
1943 1944 default = 1024
1944 1945
1945 1946 [[items]]
1946 1947 section = "server"
1947 1948 name = "preferuncompressed"
1948 1949 default = false
1949 1950
1950 1951 [[items]]
1951 1952 section = "server"
1952 1953 name = "pullbundle"
1953 1954 default = true
1954 1955
1955 1956 [[items]]
1956 1957 section = "server"
1957 1958 name = "streamunbundle"
1958 1959 default = false
1959 1960
1960 1961 [[items]]
1961 1962 section = "server"
1962 1963 name = "uncompressed"
1963 1964 default = true
1964 1965
1965 1966 [[items]]
1966 1967 section = "server"
1967 1968 name = "uncompressedallowsecret"
1968 1969 default = false
1969 1970
1970 1971 [[items]]
1971 1972 section = "server"
1972 1973 name = "validate"
1973 1974 default = false
1974 1975
1975 1976 [[items]]
1976 1977 section = "server"
1977 1978 name = "view"
1978 1979 default = "served"
1979 1980
1980 1981 [[items]]
1981 1982 section = "server"
1982 1983 name = "zliblevel"
1983 1984 default = -1
1984 1985
1985 1986 [[items]]
1986 1987 section = "server"
1987 1988 name = "zstdlevel"
1988 1989 default = 3
1989 1990
1990 1991 [[items]]
1991 1992 section = "share"
1992 1993 name = "pool"
1993 1994
1994 1995 [[items]]
1995 1996 section = "share"
1996 1997 name = "poolnaming"
1997 1998 default = "identity"
1998 1999
1999 2000 [[items]]
2000 2001 section = "share"
2001 2002 name = "safe-mismatch.source-not-safe"
2002 2003 default = "abort"
2003 2004
2004 2005 [[items]]
2005 2006 section = "share"
2006 2007 name = "safe-mismatch.source-not-safe.warn"
2007 2008 default = true
2008 2009
2009 2010 [[items]]
2010 2011 section = "share"
2011 2012 name = "safe-mismatch.source-not-safe:verbose-upgrade"
2012 2013 default = true
2013 2014
2014 2015 [[items]]
2015 2016 section = "share"
2016 2017 name = "safe-mismatch.source-safe"
2017 2018 default = "abort"
2018 2019
2019 2020 [[items]]
2020 2021 section = "share"
2021 2022 name = "safe-mismatch.source-safe.warn"
2022 2023 default = true
2023 2024
2024 2025 [[items]]
2025 2026 section = "share"
2026 2027 name = "safe-mismatch.source-safe:verbose-upgrade"
2027 2028 default = true
2028 2029
2029 2030 [[items]]
2030 2031 section = "shelve"
2031 2032 name = "maxbackups"
2032 2033 default = 10
2033 2034
2034 2035 [[items]]
2035 2036 section = "shelve"
2036 2037 name = "store"
2037 2038 default = "internal"
2038 2039 experimental = true
2039 2040
2040 2041 [[items]]
2041 2042 section = "smtp"
2042 2043 name = "host"
2043 2044
2044 2045 [[items]]
2045 2046 section = "smtp"
2046 2047 name = "local_hostname"
2047 2048
2048 2049 [[items]]
2049 2050 section = "smtp"
2050 2051 name = "password"
2051 2052
2052 2053 [[items]]
2053 2054 section = "smtp"
2054 2055 name = "port"
2055 2056 default-type = "dynamic"
2056 2057
2057 2058 [[items]]
2058 2059 section = "smtp"
2059 2060 name = "tls"
2060 2061 default = "none"
2061 2062
2062 2063 [[items]]
2063 2064 section = "smtp"
2064 2065 name = "username"
2065 2066
2066 2067 [[items]]
2067 2068 section = "sparse"
2068 2069 name = "missingwarning"
2069 2070 default = true
2070 2071 experimental = true
2071 2072
2072 2073 [[items]]
2073 2074 section = "storage"
2074 2075 name = "dirstate-v2.slow-path"
2075 2076 default = "abort"
2076 2077 experimental = true # experimental as long as format.use-dirstate-v2 is.
2077 2078
2078 2079 [[items]]
2079 2080 section = "storage"
2080 2081 name = "new-repo-backend"
2081 2082 default = "revlogv1"
2082 2083 experimental = true
2083 2084
2084 2085 [[items]]
2085 2086 section = "storage"
2086 2087 name = "revlog.delta-parent-search.candidate-group-chunk-size"
2087 2088 default = 20
2088 2089
2089 2090 [[items]]
2090 2091 section = "storage"
2091 2092 name = "revlog.issue6528.fix-incoming"
2092 2093 default = true
2093 2094
2094 2095 [[items]]
2095 2096 section = "storage"
2096 2097 name = "revlog.optimize-delta-parent-choice"
2097 2098 default = true
2098 2099 alias = [["format", "aggressivemergedeltas"]]
2099 2100
2100 2101 [[items]]
2101 2102 section = "storage"
2102 2103 name = "revlog.persistent-nodemap.mmap"
2103 2104 default = true
2104 2105
2105 2106 [[items]]
2106 2107 section = "storage"
2107 2108 name = "revlog.persistent-nodemap.slow-path"
2108 2109 default = "abort"
2109 2110
2110 2111 [[items]]
2111 2112 section = "storage"
2112 2113 name = "revlog.reuse-external-delta"
2113 2114 default = true
2114 2115
2115 2116 [[items]]
2116 2117 section = "storage"
2117 2118 name = "revlog.reuse-external-delta-parent"
2118 2119 documentation = """This option is true unless `format.generaldelta` is set."""
2119 2120
2120 2121 [[items]]
2121 2122 section = "storage"
2122 2123 name = "revlog.zlib.level"
2123 2124
2124 2125 [[items]]
2125 2126 section = "storage"
2126 2127 name = "revlog.zstd.level"
2127 2128
2128 2129 [[items]]
2129 2130 section = "subrepos"
2130 2131 name = "allowed"
2131 2132 default-type = "dynamic" # to make backporting simpler
2132 2133
2133 2134 [[items]]
2134 2135 section = "subrepos"
2135 2136 name = "git:allowed"
2136 2137 default-type = "dynamic"
2137 2138
2138 2139 [[items]]
2139 2140 section = "subrepos"
2140 2141 name = "hg:allowed"
2141 2142 default-type = "dynamic"
2142 2143
2143 2144 [[items]]
2144 2145 section = "subrepos"
2145 2146 name = "svn:allowed"
2146 2147 default-type = "dynamic"
2147 2148
2148 2149 [[items]]
2149 2150 section = "templateconfig"
2150 2151 name = ".*"
2151 2152 default-type = "dynamic"
2152 2153 generic = true
2153 2154
2154 2155 [[items]]
2155 2156 section = "templates"
2156 2157 name = ".*"
2157 2158 generic = true
2158 2159
2159 2160 [[items]]
2160 2161 section = "trusted"
2161 2162 name = "groups"
2162 2163 default-type = "list_type"
2163 2164
2164 2165 [[items]]
2165 2166 section = "trusted"
2166 2167 name = "users"
2167 2168 default-type = "list_type"
2168 2169
2169 2170 [[items]]
2170 2171 section = "ui"
2171 2172 name = "_usedassubrepo"
2172 2173 default = false
2173 2174
2174 2175 [[items]]
2175 2176 section = "ui"
2176 2177 name = "allowemptycommit"
2177 2178 default = false
2178 2179
2179 2180 [[items]]
2180 2181 section = "ui"
2181 2182 name = "archivemeta"
2182 2183 default = true
2183 2184
2184 2185 [[items]]
2185 2186 section = "ui"
2186 2187 name = "askusername"
2187 2188 default = false
2188 2189
2189 2190 [[items]]
2190 2191 section = "ui"
2191 2192 name = "available-memory"
2192 2193
2193 2194 [[items]]
2194 2195 section = "ui"
2195 2196 name = "clonebundlefallback"
2196 2197 default = false
2197 2198
2198 2199 [[items]]
2199 2200 section = "ui"
2200 2201 name = "clonebundleprefers"
2201 2202 default-type = "list_type"
2202 2203
2203 2204 [[items]]
2204 2205 section = "ui"
2205 2206 name = "clonebundles"
2206 2207 default = true
2207 2208
2208 2209 [[items]]
2209 2210 section = "ui"
2210 2211 name = "color"
2211 2212 default = "auto"
2212 2213
2213 2214 [[items]]
2214 2215 section = "ui"
2215 2216 name = "commitsubrepos"
2216 2217 default = false
2217 2218
2218 2219 [[items]]
2219 2220 section = "ui"
2220 2221 name = "debug"
2221 2222 default = false
2222 2223
2223 2224 [[items]]
2224 2225 section = "ui"
2225 2226 name = "debugger"
2226 2227
2227 2228 [[items]]
2228 2229 section = "ui"
2229 2230 name = "detailed-exit-code"
2230 2231 default = false
2231 2232 experimental = true
2232 2233
2233 2234 [[items]]
2234 2235 section = "ui"
2235 2236 name = "editor"
2236 2237 default-type = "dynamic"
2237 2238
2238 2239 [[items]]
2239 2240 section = "ui"
2240 2241 name = "fallbackencoding"
2241 2242
2242 2243 [[items]]
2243 2244 section = "ui"
2244 2245 name = "forcecwd"
2245 2246
2246 2247 [[items]]
2247 2248 section = "ui"
2248 2249 name = "forcemerge"
2249 2250
2250 2251 [[items]]
2251 2252 section = "ui"
2252 2253 name = "formatdebug"
2253 2254 default = false
2254 2255
2255 2256 [[items]]
2256 2257 section = "ui"
2257 2258 name = "formatjson"
2258 2259 default = false
2259 2260
2260 2261 [[items]]
2261 2262 section = "ui"
2262 2263 name = "formatted"
2263 2264
2264 2265 [[items]]
2265 2266 section = "ui"
2266 2267 name = "interactive"
2267 2268
2268 2269 [[items]]
2269 2270 section = "ui"
2270 2271 name = "interface"
2271 2272
2272 2273 [[items]]
2273 2274 section = "ui"
2274 2275 name = "interface.chunkselector"
2275 2276
2276 2277 [[items]]
2277 2278 section = "ui"
2278 2279 name = "large-file-limit"
2279 2280 default = 10485760
2280 2281
2281 2282 [[items]]
2282 2283 section = "ui"
2283 2284 name = "logblockedtimes"
2284 2285 default = false
2285 2286
2286 2287 [[items]]
2287 2288 section = "ui"
2288 2289 name = "merge"
2289 2290
2290 2291 [[items]]
2291 2292 section = "ui"
2292 2293 name = "mergemarkers"
2293 2294 default = "basic"
2294 2295
2295 2296 [[items]]
2296 2297 section = "ui"
2297 2298 name = "message-output"
2298 2299 default = "stdio"
2299 2300
2300 2301 [[items]]
2301 2302 section = "ui"
2302 2303 name = "nontty"
2303 2304 default = false
2304 2305
2305 2306 [[items]]
2306 2307 section = "ui"
2307 2308 name = "origbackuppath"
2308 2309
2309 2310 [[items]]
2310 2311 section = "ui"
2311 2312 name = "paginate"
2312 2313 default = true
2313 2314
2314 2315 [[items]]
2315 2316 section = "ui"
2316 2317 name = "patch"
2317 2318
2318 2319 [[items]]
2319 2320 section = "ui"
2320 2321 name = "portablefilenames"
2321 2322 default = "warn"
2322 2323
2323 2324 [[items]]
2324 2325 section = "ui"
2325 2326 name = "promptecho"
2326 2327 default = false
2327 2328
2328 2329 [[items]]
2329 2330 section = "ui"
2330 2331 name = "quiet"
2331 2332 default = false
2332 2333
2333 2334 [[items]]
2334 2335 section = "ui"
2335 2336 name = "quietbookmarkmove"
2336 2337 default = false
2337 2338
2338 2339 [[items]]
2339 2340 section = "ui"
2340 2341 name = "relative-paths"
2341 2342 default = "legacy"
2342 2343
2343 2344 [[items]]
2344 2345 section = "ui"
2345 2346 name = "remotecmd"
2346 2347 default = "hg"
2347 2348
2348 2349 [[items]]
2349 2350 section = "ui"
2350 2351 name = "report_untrusted"
2351 2352 default = true
2352 2353
2353 2354 [[items]]
2354 2355 section = "ui"
2355 2356 name = "rollback"
2356 2357 default = true
2357 2358
2358 2359 [[items]]
2359 2360 section = "ui"
2360 2361 name = "signal-safe-lock"
2361 2362 default = true
2362 2363
2363 2364 [[items]]
2364 2365 section = "ui"
2365 2366 name = "slash"
2366 2367 default = false
2367 2368
2368 2369 [[items]]
2369 2370 section = "ui"
2370 2371 name = "ssh"
2371 2372 default = "ssh"
2372 2373
2373 2374 [[items]]
2374 2375 section = "ui"
2375 2376 name = "ssherrorhint"
2376 2377
2377 2378 [[items]]
2378 2379 section = "ui"
2379 2380 name = "statuscopies"
2380 2381 default = false
2381 2382
2382 2383 [[items]]
2383 2384 section = "ui"
2384 2385 name = "strict"
2385 2386 default = false
2386 2387
2387 2388 [[items]]
2388 2389 section = "ui"
2389 2390 name = "style"
2390 2391 default = ""
2391 2392
2392 2393 [[items]]
2393 2394 section = "ui"
2394 2395 name = "supportcontact"
2395 2396
2396 2397 [[items]]
2397 2398 section = "ui"
2398 2399 name = "textwidth"
2399 2400 default = 78
2400 2401
2401 2402 [[items]]
2402 2403 section = "ui"
2403 2404 name = "timeout"
2404 2405 default = "600"
2405 2406
2406 2407 [[items]]
2407 2408 section = "ui"
2408 2409 name = "timeout.warn"
2409 2410 default = 0
2410 2411
2411 2412 [[items]]
2412 2413 section = "ui"
2413 2414 name = "timestamp-output"
2414 2415 default = false
2415 2416
2416 2417 [[items]]
2417 2418 section = "ui"
2418 2419 name = "traceback"
2419 2420 default = false
2420 2421
2421 2422 [[items]]
2422 2423 section = "ui"
2423 2424 name = "tweakdefaults"
2424 2425 default = false
2425 2426
2426 2427 [[items]]
2427 2428 section = "ui"
2428 2429 name = "username"
2429 2430 alias = [["ui", "user"]]
2430 2431
2431 2432 [[items]]
2432 2433 section = "ui"
2433 2434 name = "verbose"
2434 2435 default = false
2435 2436
2436 2437 [[items]]
2437 2438 section = "verify"
2438 2439 name = "skipflags"
2439 2440 default = 0
2440 2441
2441 2442 [[items]]
2442 2443 section = "web"
2443 2444 name = "accesslog"
2444 2445 default = "-"
2445 2446
2446 2447 [[items]]
2447 2448 section = "web"
2448 2449 name = "address"
2449 2450 default = ""
2450 2451
2451 2452 [[items]]
2452 2453 section = "web"
2453 2454 name = "allow-archive"
2454 2455 default-type = "list_type"
2455 2456 alias = [["web", "allow_archive"]]
2456 2457
2457 2458 [[items]]
2458 2459 section = "web"
2459 2460 name = "allow-pull"
2460 2461 default = true
2461 2462 alias = [["web", "allowpull"]]
2462 2463
2463 2464 [[items]]
2464 2465 section = "web"
2465 2466 name = "allow-push"
2466 2467 default-type = "list_type"
2467 2468 alias = [["web", "allow_push"]]
2468 2469
2469 2470 [[items]]
2470 2471 section = "web"
2471 2472 name = "allow_read"
2472 2473 default-type = "list_type"
2473 2474
2474 2475 [[items]]
2475 2476 section = "web"
2476 2477 name = "allowbz2"
2477 2478 default = false
2478 2479
2479 2480 [[items]]
2480 2481 section = "web"
2481 2482 name = "allowgz"
2482 2483 default = false
2483 2484
2484 2485 [[items]]
2485 2486 section = "web"
2486 2487 name = "allowzip"
2487 2488 default = false
2488 2489
2489 2490 [[items]]
2490 2491 section = "web"
2491 2492 name = "archivesubrepos"
2492 2493 default = false
2493 2494
2494 2495 [[items]]
2495 2496 section = "web"
2496 2497 name = "baseurl"
2497 2498
2498 2499 [[items]]
2499 2500 section = "web"
2500 2501 name = "cacerts"
2501 2502
2502 2503 [[items]]
2503 2504 section = "web"
2504 2505 name = "cache"
2505 2506 default = true
2506 2507
2507 2508 [[items]]
2508 2509 section = "web"
2509 2510 name = "certificate"
2510 2511
2511 2512 [[items]]
2512 2513 section = "web"
2513 2514 name = "collapse"
2514 2515 default = false
2515 2516
2516 2517 [[items]]
2517 2518 section = "web"
2518 2519 name = "comparisoncontext"
2519 2520 default = 5
2520 2521
2521 2522 [[items]]
2522 2523 section = "web"
2523 2524 name = "contact"
2524 2525
2525 2526 [[items]]
2526 2527 section = "web"
2527 2528 name = "csp"
2528 2529
2529 2530 [[items]]
2530 2531 section = "web"
2531 2532 name = "deny_push"
2532 2533 default-type = "list_type"
2533 2534
2534 2535 [[items]]
2535 2536 section = "web"
2536 2537 name = "deny_read"
2537 2538 default-type = "list_type"
2538 2539
2539 2540 [[items]]
2540 2541 section = "web"
2541 2542 name = "descend"
2542 2543 default = true
2543 2544
2544 2545 [[items]]
2545 2546 section = "web"
2546 2547 name = "description"
2547 2548 default = ""
2548 2549
2549 2550 [[items]]
2550 2551 section = "web"
2551 2552 name = "encoding"
2552 2553 default-type = "lazy_module"
2553 2554 default = "encoding.encoding"
2554 2555
2555 2556 [[items]]
2556 2557 section = "web"
2557 2558 name = "errorlog"
2558 2559 default = "-"
2559 2560
2560 2561 [[items]]
2561 2562 section = "web"
2562 2563 name = "guessmime"
2563 2564 default = false
2564 2565
2565 2566 [[items]]
2566 2567 section = "web"
2567 2568 name = "hidden"
2568 2569 default = false
2569 2570
2570 2571 [[items]]
2571 2572 section = "web"
2572 2573 name = "ipv6"
2573 2574 default = false
2574 2575
2575 2576 [[items]]
2576 2577 section = "web"
2577 2578 name = "labels"
2578 2579 default-type = "list_type"
2579 2580
2580 2581 [[items]]
2581 2582 section = "web"
2582 2583 name = "logoimg"
2583 2584 default = "hglogo.png"
2584 2585
2585 2586 [[items]]
2586 2587 section = "web"
2587 2588 name = "logourl"
2588 2589 default = "https://mercurial-scm.org/"
2589 2590
2590 2591 [[items]]
2591 2592 section = "web"
2592 2593 name = "maxchanges"
2593 2594 default = 10
2594 2595
2595 2596 [[items]]
2596 2597 section = "web"
2597 2598 name = "maxfiles"
2598 2599 default = 10
2599 2600
2600 2601 [[items]]
2601 2602 section = "web"
2602 2603 name = "maxshortchanges"
2603 2604 default = 60
2604 2605
2605 2606 [[items]]
2606 2607 section = "web"
2607 2608 name = "motd"
2608 2609 default = ""
2609 2610
2610 2611 [[items]]
2611 2612 section = "web"
2612 2613 name = "name"
2613 2614 default-type = "dynamic"
2614 2615
2615 2616 [[items]]
2616 2617 section = "web"
2617 2618 name = "port"
2618 2619 default = 8000
2619 2620
2620 2621 [[items]]
2621 2622 section = "web"
2622 2623 name = "prefix"
2623 2624 default = ""
2624 2625
2625 2626 [[items]]
2626 2627 section = "web"
2627 2628 name = "push_ssl"
2628 2629 default = true
2629 2630
2630 2631 [[items]]
2631 2632 section = "web"
2632 2633 name = "refreshinterval"
2633 2634 default = 20
2634 2635
2635 2636 [[items]]
2636 2637 section = "web"
2637 2638 name = "server-header"
2638 2639
2639 2640 [[items]]
2640 2641 section = "web"
2641 2642 name = "static"
2642 2643
2643 2644 [[items]]
2644 2645 section = "web"
2645 2646 name = "staticurl"
2646 2647
2647 2648 [[items]]
2648 2649 section = "web"
2649 2650 name = "stripes"
2650 2651 default = 1
2651 2652
2652 2653 [[items]]
2653 2654 section = "web"
2654 2655 name = "style"
2655 2656 default = "paper"
2656 2657
2657 2658 [[items]]
2658 2659 section = "web"
2659 2660 name = "templates"
2660 2661
2661 2662 [[items]]
2662 2663 section = "web"
2663 2664 name = "view"
2664 2665 default = "served"
2665 2666 experimental = true
2666 2667
2667 2668 [[items]]
2668 2669 section = "worker"
2669 2670 name = "backgroundclose"
2670 2671 default-type = "dynamic"
2671 2672
2672 2673 [[items]]
2673 2674 section = "worker"
2674 2675 name = "backgroundclosemaxqueue"
2675 2676 # Windows defaults to a limit of 512 open files. A buffer of 128
2676 2677 # should give us enough headway.
2677 2678 default = 384
2678 2679
2679 2680 [[items]]
2680 2681 section = "worker"
2681 2682 name = "backgroundcloseminfilecount"
2682 2683 default = 2048
2683 2684
2684 2685 [[items]]
2685 2686 section = "worker"
2686 2687 name = "backgroundclosethreadcount"
2687 2688 default = 4
2688 2689
2689 2690 [[items]]
2690 2691 section = "worker"
2691 2692 name = "enabled"
2692 2693 default = true
2693 2694
2694 2695 [[items]]
2695 2696 section = "worker"
2696 2697 name = "numcpus"
2697 2698
2699 # Templates and template applications
2700
2698 2701 [[template-applications]]
2699 2702 template = "diff-options"
2700 2703 section = "annotate"
2701 2704
2702 2705 [[template-applications]]
2703 2706 template = "diff-options"
2704 2707 section = "commands"
2705 2708 prefix = "commit.interactive"
2706 2709
2707 2710 [[template-applications]]
2708 2711 template = "diff-options"
2709 2712 section = "commands"
2710 2713 prefix = "revert.interactive"
2711 2714
2712 2715 [[template-applications]]
2713 2716 template = "diff-options"
2714 2717 section = "diff"
2715 2718
2716 2719 [templates]
2717 2720 [[templates.diff-options]]
2718 2721 suffix = "nodates"
2719 2722 default = false
2720 2723
2721 2724 [[templates.diff-options]]
2722 2725 suffix = "showfunc"
2723 2726 default = false
2724 2727
2725 2728 [[templates.diff-options]]
2726 2729 suffix = "unified"
2727 2730
2728 2731 [[templates.diff-options]]
2729 2732 suffix = "git"
2730 2733 default = false
2731 2734
2732 2735 [[templates.diff-options]]
2733 2736 suffix = "ignorews"
2734 2737 default = false
2735 2738
2736 2739 [[templates.diff-options]]
2737 2740 suffix = "ignorewsamount"
2738 2741 default = false
2739 2742
2740 2743 [[templates.diff-options]]
2741 2744 suffix = "ignoreblanklines"
2742 2745 default = false
2743 2746
2744 2747 [[templates.diff-options]]
2745 2748 suffix = "ignorewseol"
2746 2749 default = false
2747 2750
2748 2751 [[templates.diff-options]]
2749 2752 suffix = "nobinary"
2750 2753 default = false
2751 2754
2752 2755 [[templates.diff-options]]
2753 2756 suffix = "noprefix"
2754 2757 default = false
2755 2758
2756 2759 [[templates.diff-options]]
2757 2760 suffix = "word-diff"
2758 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 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 import collections
10 10 import contextlib
11 11 import datetime
12 12 import errno
13 13 import inspect
14 14 import os
15 15 import re
16 16 import signal
17 17 import socket
18 18 import subprocess
19 19 import sys
20 20 import traceback
21 21
22 22 from typing import (
23 23 Any,
24 24 Callable,
25 25 Dict,
26 26 List,
27 27 NoReturn,
28 28 Optional,
29 29 Tuple,
30 30 Type,
31 31 TypeVar,
32 32 Union,
33 33 cast,
34 34 overload,
35 35 )
36 36
37 37 from .i18n import _
38 38 from .node import hex
39 39 from .pycompat import (
40 40 getattr,
41 41 open,
42 42 )
43 43
44 44 from . import (
45 45 color,
46 46 config,
47 47 configitems,
48 48 encoding,
49 49 error,
50 extensions,
50 51 formatter,
51 52 loggingutil,
52 53 progress,
53 54 pycompat,
54 55 rcutil,
55 56 scmutil,
56 57 util,
57 58 )
58 59 from .utils import (
59 60 dateutil,
60 61 procutil,
61 62 resourceutil,
62 63 stringutil,
63 64 urlutil,
64 65 )
65 66
66 67 _ConfigItems = Dict[Tuple[bytes, bytes], object] # {(section, name) : value}
67 68 # The **opts args of the various write() methods can be basically anything, but
68 69 # there's no way to express it as "anything but str". So type it to be the
69 70 # handful of known types that are used.
70 71 _MsgOpts = Union[bytes, bool, List["_PromptChoice"]]
71 72 _PromptChoice = Tuple[bytes, bytes]
72 73 _Tui = TypeVar('_Tui', bound="ui")
73 74
74 75 urlreq = util.urlreq
75 76
76 77 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
77 78 _keepalnum: bytes = b''.join(
78 79 c for c in map(pycompat.bytechr, range(256)) if not c.isalnum()
79 80 )
80 81
81 82 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
82 83 tweakrc: bytes = b"""
83 84 [ui]
84 85 # The rollback command is dangerous. As a rule, don't use it.
85 86 rollback = False
86 87 # Make `hg status` report copy information
87 88 statuscopies = yes
88 89 # Prefer curses UIs when available. Revert to plain-text with `text`.
89 90 interface = curses
90 91 # Make compatible commands emit cwd-relative paths by default.
91 92 relative-paths = yes
92 93
93 94 [commands]
94 95 # Grep working directory by default.
95 96 grep.all-files = True
96 97 # Refuse to perform an `hg update` that would cause a file content merge
97 98 update.check = noconflict
98 99 # Show conflicts information in `hg status`
99 100 status.verbose = True
100 101 # Make `hg resolve` with no action (like `-m`) fail instead of re-merging.
101 102 resolve.explicit-re-merge = True
102 103
103 104 [diff]
104 105 git = 1
105 106 showfunc = 1
106 107 word-diff = 1
107 108 """
108 109
109 110 samplehgrcs: Dict[bytes, bytes] = {
110 111 b'user': b"""# example user config (see 'hg help config' for more info)
111 112 [ui]
112 113 # name and email, e.g.
113 114 # username = Jane Doe <jdoe@example.com>
114 115 username =
115 116
116 117 # We recommend enabling tweakdefaults to get slight improvements to
117 118 # the UI over time. Make sure to set HGPLAIN in the environment when
118 119 # writing scripts!
119 120 # tweakdefaults = True
120 121
121 122 # uncomment to disable color in command output
122 123 # (see 'hg help color' for details)
123 124 # color = never
124 125
125 126 # uncomment to disable command output pagination
126 127 # (see 'hg help pager' for details)
127 128 # paginate = never
128 129
129 130 [extensions]
130 131 # uncomment the lines below to enable some popular extensions
131 132 # (see 'hg help extensions' for more info)
132 133 #
133 134 # histedit =
134 135 # rebase =
135 136 # uncommit =
136 137 """,
137 138 b'cloned': b"""# example repository config (see 'hg help config' for more info)
138 139 [paths]
139 140 default = %s
140 141
141 142 # path aliases to other clones of this repo in URLs or filesystem paths
142 143 # (see 'hg help config.paths' for more info)
143 144 #
144 145 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
145 146 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
146 147 # my-clone = /home/jdoe/jdoes-clone
147 148
148 149 [ui]
149 150 # name and email (local to this repository, optional), e.g.
150 151 # username = Jane Doe <jdoe@example.com>
151 152 """,
152 153 b'local': b"""# example repository config (see 'hg help config' for more info)
153 154 [paths]
154 155 # path aliases to other clones of this repo in URLs or filesystem paths
155 156 # (see 'hg help config.paths' for more info)
156 157 #
157 158 # default = http://example.com/hg/example-repo
158 159 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
159 160 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
160 161 # my-clone = /home/jdoe/jdoes-clone
161 162
162 163 [ui]
163 164 # name and email (local to this repository, optional), e.g.
164 165 # username = Jane Doe <jdoe@example.com>
165 166 """,
166 167 b'global': b"""# example system-wide hg config (see 'hg help config' for more info)
167 168
168 169 [ui]
169 170 # uncomment to disable color in command output
170 171 # (see 'hg help color' for details)
171 172 # color = never
172 173
173 174 # uncomment to disable command output pagination
174 175 # (see 'hg help pager' for details)
175 176 # paginate = never
176 177
177 178 [extensions]
178 179 # uncomment the lines below to enable some popular extensions
179 180 # (see 'hg help extensions' for more info)
180 181 #
181 182 # blackbox =
182 183 # churn =
183 184 """,
184 185 }
185 186
186 187
187 188 def _maybestrurl(maybebytes):
188 189 return pycompat.rapply(pycompat.strurl, maybebytes)
189 190
190 191
191 192 def _maybebytesurl(maybestr):
192 193 return pycompat.rapply(pycompat.bytesurl, maybestr)
193 194
194 195
195 196 class httppasswordmgrdbproxy:
196 197 """Delays loading urllib2 until it's needed."""
197 198
198 199 def __init__(self) -> None:
199 200 self._mgr = None
200 201
201 202 def _get_mgr(self):
202 203 if self._mgr is None:
203 204 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
204 205 return self._mgr
205 206
206 207 def add_password(self, realm, uris, user, passwd):
207 208 return self._get_mgr().add_password(
208 209 _maybestrurl(realm),
209 210 _maybestrurl(uris),
210 211 _maybestrurl(user),
211 212 _maybestrurl(passwd),
212 213 )
213 214
214 215 def find_user_password(self, realm, uri):
215 216 mgr = self._get_mgr()
216 217 return _maybebytesurl(
217 218 mgr.find_user_password(_maybestrurl(realm), _maybestrurl(uri))
218 219 )
219 220
220 221
221 222 def _catchterm(*args) -> NoReturn:
222 223 raise error.SignalInterrupt
223 224
224 225
225 226 # unique object used to detect no default value has been provided when
226 227 # retrieving configuration value.
227 228 _unset = object()
228 229
229 230 # _reqexithandlers: callbacks run at the end of a request
230 231 _reqexithandlers: List = []
231 232
232 233
233 234 class ui:
234 235 def __init__(self, src: Optional["ui"] = None) -> None:
235 236 """Create a fresh new ui object if no src given
236 237
237 238 Use uimod.ui.load() to create a ui which knows global and user configs.
238 239 In most cases, you should use ui.copy() to create a copy of an existing
239 240 ui object.
240 241 """
241 242 # _buffers: used for temporary capture of output
242 243 self._buffers = []
243 244 # 3-tuple describing how each buffer in the stack behaves.
244 245 # Values are (capture stderr, capture subprocesses, apply labels).
245 246 self._bufferstates = []
246 247 # When a buffer is active, defines whether we are expanding labels.
247 248 # This exists to prevent an extra list lookup.
248 249 self._bufferapplylabels = None
249 250 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
250 251 self._reportuntrusted = True
251 252 self._knownconfig = configitems.coreitems
252 253 self._ocfg = config.config() # overlay
253 254 self._tcfg = config.config() # trusted
254 255 self._ucfg = config.config() # untrusted
255 256 self._trustusers = set()
256 257 self._trustgroups = set()
257 258 self.callhooks = True
258 259 # hold the root to use for each [paths] entry
259 260 self._path_to_root = {}
260 261 # Insecure server connections requested.
261 262 self.insecureconnections = False
262 263 # Blocked time
263 264 self.logblockedtimes = False
264 265 # color mode: see mercurial/color.py for possible value
265 266 self._colormode = None
266 267 self._terminfoparams = {}
267 268 self._styles = {}
268 269 self._uninterruptible = False
269 270 self.showtimestamp = False
270 271
271 272 if src:
272 273 self._fout = src._fout
273 274 self._ferr = src._ferr
274 275 self._fin = src._fin
275 276 self._fmsg = src._fmsg
276 277 self._fmsgout = src._fmsgout
277 278 self._fmsgerr = src._fmsgerr
278 279 self._finoutredirected = src._finoutredirected
279 280 self._loggers = src._loggers.copy()
280 281 self.pageractive = src.pageractive
281 282 self._disablepager = src._disablepager
282 283 self._tweaked = src._tweaked
283 284
284 285 self._tcfg = src._tcfg.copy()
285 286 self._ucfg = src._ucfg.copy()
286 287 self._ocfg = src._ocfg.copy()
287 288 self._trustusers = src._trustusers.copy()
288 289 self._trustgroups = src._trustgroups.copy()
289 290 self.environ = src.environ
290 291 self.callhooks = src.callhooks
291 292 self._path_to_root = src._path_to_root
292 293 self.insecureconnections = src.insecureconnections
293 294 self._colormode = src._colormode
294 295 self._terminfoparams = src._terminfoparams.copy()
295 296 self._styles = src._styles.copy()
296 297
297 298 self.fixconfig()
298 299
299 300 self.httppasswordmgrdb = src.httppasswordmgrdb
300 301 self._blockedtimes = src._blockedtimes
301 302 else:
302 303 self._fout = procutil.stdout
303 304 self._ferr = procutil.stderr
304 305 self._fin = procutil.stdin
305 306 self._fmsg = None
306 307 self._fmsgout = self.fout # configurable
307 308 self._fmsgerr = self.ferr # configurable
308 309 self._finoutredirected = False
309 310 self._loggers = {}
310 311 self.pageractive = False
311 312 self._disablepager = False
312 313 self._tweaked = False
313 314
314 315 # shared read-only environment
315 316 self.environ = encoding.environ
316 317
317 318 self.httppasswordmgrdb = httppasswordmgrdbproxy()
318 319 self._blockedtimes = collections.defaultdict(int)
319 320
320 321 allowed = self.configlist(b'experimental', b'exportableenviron')
321 322 if b'*' in allowed:
322 323 self._exportableenviron = self.environ
323 324 else:
324 325 self._exportableenviron = {}
325 326 for k in allowed:
326 327 if k in self.environ:
327 328 self._exportableenviron[k] = self.environ[k]
328 329
329 330 def _new_source(self) -> None:
330 331 self._ocfg.new_source()
331 332 self._tcfg.new_source()
332 333 self._ucfg.new_source()
333 334
334 335 @classmethod
335 336 def load(cls: Type[_Tui]) -> _Tui:
336 337 """Create a ui and load global and user configs"""
337 338 u = cls()
338 339 # we always trust global config files and environment variables
339 340 for t, f in rcutil.rccomponents():
340 341 if t == b'path':
341 342 u.readconfig(f, trust=True)
342 343 elif t == b'resource':
343 344 u.read_resource_config(f, trust=True)
344 345 elif t == b'items':
345 346 u._new_source()
346 347 sections = set()
347 348 for section, name, value, source in f:
348 349 # do not set u._ocfg
349 350 # XXX clean this up once immutable config object is a thing
350 351 u._tcfg.set(section, name, value, source)
351 352 u._ucfg.set(section, name, value, source)
352 353 sections.add(section)
353 354 for section in sections:
354 355 u.fixconfig(section=section)
355 356 else:
356 357 raise error.ProgrammingError(b'unknown rctype: %s' % t)
357 358 u._maybetweakdefaults()
358 359 u._new_source() # anything after that is a different level
359 360 return u
360 361
361 362 def _maybetweakdefaults(self) -> None:
362 363 if not self.configbool(b'ui', b'tweakdefaults'):
363 364 return
364 365 if self._tweaked or self.plain(b'tweakdefaults'):
365 366 return
366 367
367 368 # Note: it is SUPER IMPORTANT that you set self._tweaked to
368 369 # True *before* any calls to setconfig(), otherwise you'll get
369 370 # infinite recursion between setconfig and this method.
370 371 #
371 372 # TODO: We should extract an inner method in setconfig() to
372 373 # avoid this weirdness.
373 374 self._tweaked = True
374 375 tmpcfg = config.config()
375 376 tmpcfg.parse(b'<tweakdefaults>', tweakrc)
376 377 for section in tmpcfg:
377 378 for name, value in tmpcfg.items(section):
378 379 if not self.hasconfig(section, name):
379 380 self.setconfig(section, name, value, b"<tweakdefaults>")
380 381
381 382 def copy(self: _Tui) -> _Tui:
382 383 return self.__class__(self)
383 384
384 385 def resetstate(self) -> None:
385 386 """Clear internal state that shouldn't persist across commands"""
386 387 if self._progbar:
387 388 self._progbar.resetstate() # reset last-print time of progress bar
388 389 self.httppasswordmgrdb = httppasswordmgrdbproxy()
389 390
390 391 @contextlib.contextmanager
391 392 def timeblockedsection(self, key: bytes):
392 393 # this is open-coded below - search for timeblockedsection to find them
393 394 starttime = util.timer()
394 395 try:
395 396 yield
396 397 finally:
397 398 self._blockedtimes[key + b'_blocked'] += (
398 399 util.timer() - starttime
399 400 ) * 1000
400 401
401 402 @contextlib.contextmanager
402 403 def uninterruptible(self):
403 404 """Mark an operation as unsafe.
404 405
405 406 Most operations on a repository are safe to interrupt, but a
406 407 few are risky (for example repair.strip). This context manager
407 408 lets you advise Mercurial that something risky is happening so
408 409 that control-C etc can be blocked if desired.
409 410 """
410 411 enabled = self.configbool(b'experimental', b'nointerrupt')
411 412 if enabled and self.configbool(
412 413 b'experimental', b'nointerrupt-interactiveonly'
413 414 ):
414 415 enabled = self.interactive()
415 416 if self._uninterruptible or not enabled:
416 417 # if nointerrupt support is turned off, the process isn't
417 418 # interactive, or we're already in an uninterruptible
418 419 # block, do nothing.
419 420 yield
420 421 return
421 422
422 423 def warn():
423 424 self.warn(_(b"shutting down cleanly\n"))
424 425 self.warn(
425 426 _(b"press ^C again to terminate immediately (dangerous)\n")
426 427 )
427 428 return True
428 429
429 430 with procutil.uninterruptible(warn):
430 431 try:
431 432 self._uninterruptible = True
432 433 yield
433 434 finally:
434 435 self._uninterruptible = False
435 436
436 437 def formatter(self, topic: bytes, opts):
437 438 return formatter.formatter(self, self, topic, opts)
438 439
439 440 def _trusted(self, fp, f: bytes) -> bool:
440 441 st = util.fstat(fp)
441 442 if util.isowner(st):
442 443 return True
443 444
444 445 tusers, tgroups = self._trustusers, self._trustgroups
445 446 if b'*' in tusers or b'*' in tgroups:
446 447 return True
447 448
448 449 user = util.username(st.st_uid)
449 450 group = util.groupname(st.st_gid)
450 451 if user in tusers or group in tgroups or user == util.username():
451 452 return True
452 453
453 454 if self._reportuntrusted:
454 455 self.warn(
455 456 _(
456 457 b'not trusting file %s from untrusted '
457 458 b'user %s, group %s\n'
458 459 )
459 460 % (f, user, group)
460 461 )
461 462 return False
462 463
463 464 def read_resource_config(
464 465 self, name, root=None, trust=False, sections=None, remap=None
465 466 ) -> None:
466 467 try:
467 468 fp = resourceutil.open_resource(name[0], name[1])
468 469 except IOError:
469 470 if not sections: # ignore unless we were looking for something
470 471 return
471 472 raise
472 473
473 474 self._readconfig(
474 475 b'resource:%s.%s' % name, fp, root, trust, sections, remap
475 476 )
476 477
477 478 def readconfig(
478 479 self, filename, root=None, trust=False, sections=None, remap=None
479 480 ) -> None:
480 481 try:
481 482 fp = open(filename, 'rb')
482 483 except IOError:
483 484 if not sections: # ignore unless we were looking for something
484 485 return
485 486 raise
486 487
487 488 self._readconfig(filename, fp, root, trust, sections, remap)
488 489
489 490 def _readconfig(
490 491 self, filename, fp, root=None, trust=False, sections=None, remap=None
491 492 ) -> None:
492 493 with fp:
493 494 cfg = config.config()
494 495 trusted = sections or trust or self._trusted(fp, filename)
495 496
496 497 try:
497 498 cfg.read(filename, fp, sections=sections, remap=remap)
498 499 except error.ConfigError as inst:
499 500 if trusted:
500 501 raise
501 502 self.warn(
502 503 _(b'ignored %s: %s\n') % (inst.location, inst.message)
503 504 )
504 505
505 506 self._applyconfig(cfg, trusted, root)
506 507
507 508 def applyconfig(
508 509 self, configitems: _ConfigItems, source=b"", root=None
509 510 ) -> None:
510 511 """Add configitems from a non-file source. Unlike with ``setconfig()``,
511 512 they can be overridden by subsequent config file reads. The items are
512 513 in the same format as ``configoverride()``, namely a dict of the
513 514 following structures: {(section, name) : value}
514 515
515 516 Typically this is used by extensions that inject themselves into the
516 517 config file load procedure by monkeypatching ``localrepo.loadhgrc()``.
517 518 """
518 519 cfg = config.config()
519 520
520 521 for (section, name), value in configitems.items():
521 522 cfg.set(section, name, value, source)
522 523
523 524 self._applyconfig(cfg, True, root)
524 525
525 526 def _applyconfig(self, cfg, trusted, root) -> None:
526 527 if self.plain():
527 528 for k in (
528 529 b'debug',
529 530 b'fallbackencoding',
530 531 b'quiet',
531 532 b'slash',
532 533 b'logtemplate',
533 534 b'message-output',
534 535 b'statuscopies',
535 536 b'style',
536 537 b'traceback',
537 538 b'verbose',
538 539 ):
539 540 if k in cfg[b'ui']:
540 541 del cfg[b'ui'][k]
541 542 for k, v in cfg.items(b'defaults'):
542 543 del cfg[b'defaults'][k]
543 544 for k, v in cfg.items(b'commands'):
544 545 del cfg[b'commands'][k]
545 546 for k, v in cfg.items(b'command-templates'):
546 547 del cfg[b'command-templates'][k]
547 548 # Don't remove aliases from the configuration if in the exceptionlist
548 549 if self.plain(b'alias'):
549 550 for k, v in cfg.items(b'alias'):
550 551 del cfg[b'alias'][k]
551 552 if self.plain(b'revsetalias'):
552 553 for k, v in cfg.items(b'revsetalias'):
553 554 del cfg[b'revsetalias'][k]
554 555 if self.plain(b'templatealias'):
555 556 for k, v in cfg.items(b'templatealias'):
556 557 del cfg[b'templatealias'][k]
557 558
558 559 if trusted:
559 560 self._tcfg.update(cfg)
560 561 self._tcfg.update(self._ocfg)
561 562 self._ucfg.update(cfg)
562 563 self._ucfg.update(self._ocfg)
563 564
564 565 if root is None:
565 566 root = os.path.expanduser(b'~')
566 567 self.fixconfig(root=root)
567 568
568 569 def fixconfig(self, root=None, section=None) -> None:
569 570 if section in (None, b'paths'):
570 571 # expand vars and ~
571 572 # translate paths relative to root (or home) into absolute paths
572 573 root = root or encoding.getcwd()
573 574 for c in self._tcfg, self._ucfg, self._ocfg:
574 575 for n, p in c.items(b'paths'):
575 576 old_p = p
576 577 s = self.configsource(b'paths', n) or b'none'
577 578 root_key = (n, p, s)
578 579 if root_key not in self._path_to_root:
579 580 self._path_to_root[root_key] = root
580 581 # Ignore sub-options.
581 582 if b':' in n:
582 583 continue
583 584 if not p:
584 585 continue
585 586 if b'%%' in p:
586 587 if s is None:
587 588 s = 'none'
588 589 self.warn(
589 590 _(b"(deprecated '%%' in path %s=%s from %s)\n")
590 591 % (n, p, s)
591 592 )
592 593 p = p.replace(b'%%', b'%')
593 594 if p != old_p:
594 595 c.alter(b"paths", n, p)
595 596
596 597 if section in (None, b'ui'):
597 598 # update ui options
598 599 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
599 600 self.debugflag = self.configbool(b'ui', b'debug')
600 601 self.verbose = self.debugflag or self.configbool(b'ui', b'verbose')
601 602 self.quiet = not self.debugflag and self.configbool(b'ui', b'quiet')
602 603 if self.verbose and self.quiet:
603 604 self.quiet = self.verbose = False
604 605 self._reportuntrusted = self.debugflag or self.configbool(
605 606 b"ui", b"report_untrusted"
606 607 )
607 608 self.showtimestamp = self.configbool(b'ui', b'timestamp-output')
608 609 self.tracebackflag = self.configbool(b'ui', b'traceback')
609 610 self.logblockedtimes = self.configbool(b'ui', b'logblockedtimes')
610 611
611 612 if section in (None, b'trusted'):
612 613 # update trust information
613 614 self._trustusers.update(self.configlist(b'trusted', b'users'))
614 615 self._trustgroups.update(self.configlist(b'trusted', b'groups'))
615 616
616 617 if section in (None, b'devel', b'ui') and self.debugflag:
617 618 tracked = set()
618 619 if self.configbool(b'devel', b'debug.extensions'):
619 620 tracked.add(b'extension')
620 621 if tracked:
621 622 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
622 623 self.setlogger(b'debug', logger)
623 624
624 625 def backupconfig(self, section, item):
625 626 return (
626 627 self._ocfg.backup(section, item),
627 628 self._tcfg.backup(section, item),
628 629 self._ucfg.backup(section, item),
629 630 )
630 631
631 632 def restoreconfig(self, data) -> None:
632 633 self._ocfg.restore(data[0])
633 634 self._tcfg.restore(data[1])
634 635 self._ucfg.restore(data[2])
635 636
636 637 def setconfig(self, section, name, value, source=b'') -> None:
637 638 for cfg in (self._ocfg, self._tcfg, self._ucfg):
638 639 cfg.set(section, name, value, source)
639 640 self.fixconfig(section=section)
640 641 self._maybetweakdefaults()
641 642
642 643 def _data(self, untrusted):
643 644 return untrusted and self._ucfg or self._tcfg
644 645
645 646 def configsource(self, section, name, untrusted=False):
646 647 return self._data(untrusted).source(section, name)
647 648
648 649 def config(self, section, name, default=_unset, untrusted=False):
649 650 """return the plain string version of a config"""
650 651 value = self._config(
651 652 section, name, default=default, untrusted=untrusted
652 653 )
653 654 if value is _unset:
654 655 return None
655 656 return value
656 657
657 658 def _config(self, section, name, default=_unset, untrusted=False):
658 659 value = itemdefault = default
659 660 item = self._knownconfig.get(section, {}).get(name)
660 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 669 if item is not None:
663 670 alternates.extend(item.alias)
664 671 if callable(item.default):
665 672 itemdefault = item.default()
666 673 else:
667 674 itemdefault = item.default
668 675 else:
669 676 msg = b"accessing unregistered config item: '%s.%s'"
670 677 msg %= (section, name)
671 678 self.develwarn(msg, 2, b'warn-config-unknown')
672 679
673 680 if default is _unset:
674 681 if item is None:
675 682 value = default
676 683 elif item.default is configitems.dynamicdefault:
677 684 value = None
678 685 msg = b"config item requires an explicit default value: '%s.%s'"
679 686 msg %= (section, name)
680 687 self.develwarn(msg, 2, b'warn-config-default')
681 688 else:
682 689 value = itemdefault
683 690 elif (
684 691 item is not None
685 692 and item.default is not configitems.dynamicdefault
686 693 and default != itemdefault
687 694 ):
688 695 msg = (
689 696 b"specifying a mismatched default value for a registered "
690 697 b"config item: '%s.%s' '%s'"
691 698 )
692 699 msg %= (section, name, pycompat.bytestr(default))
693 700 self.develwarn(msg, 2, b'warn-config-default')
694 701
695 702 candidates = []
696 703 config = self._data(untrusted)
697 704 for s, n in alternates:
698 705 candidate = config.get(s, n, None)
699 706 if candidate is not None:
700 707 candidates.append((s, n, candidate))
701 708 if candidates:
702 709
703 710 def level(x):
704 711 return config.level(x[0], x[1])
705 712
706 713 value = max(candidates, key=level)[2]
707 714
708 715 if self.debugflag and not untrusted and self._reportuntrusted:
709 716 for s, n in alternates:
710 717 uvalue = self._ucfg.get(s, n)
711 718 if uvalue is not None and uvalue != value:
712 719 self.debug(
713 720 b"ignoring untrusted configuration option "
714 721 b"%s.%s = %s\n" % (s, n, uvalue)
715 722 )
716 723 return value
717 724
718 725 def config_default(self, section, name):
719 726 """return the default value for a config option
720 727
721 728 The default is returned "raw", for example if it is a callable, the
722 729 callable was not called.
723 730 """
724 731 item = self._knownconfig.get(section, {}).get(name)
725 732
726 733 if item is None:
727 734 raise KeyError((section, name))
728 735 return item.default
729 736
730 737 def configsuboptions(self, section, name, default=_unset, untrusted=False):
731 738 """Get a config option and all sub-options.
732 739
733 740 Some config options have sub-options that are declared with the
734 741 format "key:opt = value". This method is used to return the main
735 742 option and all its declared sub-options.
736 743
737 744 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
738 745 is a dict of defined sub-options where keys and values are strings.
739 746 """
740 747 main = self.config(section, name, default, untrusted=untrusted)
741 748 data = self._data(untrusted)
742 749 sub = {}
743 750 prefix = b'%s:' % name
744 751 for k, v in data.items(section):
745 752 if k.startswith(prefix):
746 753 sub[k[len(prefix) :]] = v
747 754
748 755 if self.debugflag and not untrusted and self._reportuntrusted:
749 756 for k, v in sub.items():
750 757 uvalue = self._ucfg.get(section, b'%s:%s' % (name, k))
751 758 if uvalue is not None and uvalue != v:
752 759 self.debug(
753 760 b'ignoring untrusted configuration option '
754 761 b'%s:%s.%s = %s\n' % (section, name, k, uvalue)
755 762 )
756 763
757 764 return main, sub
758 765
759 766 def configpath(self, section, name, default=_unset, untrusted=False):
760 767 """get a path config item, expanded relative to repo root or config
761 768 file"""
762 769 v = self.config(section, name, default, untrusted)
763 770 if v is None:
764 771 return None
765 772 if not os.path.isabs(v) or b"://" not in v:
766 773 src = self.configsource(section, name, untrusted)
767 774 if b':' in src:
768 775 base = os.path.dirname(src.rsplit(b':')[0])
769 776 v = os.path.join(base, os.path.expanduser(v))
770 777 return v
771 778
772 779 def configbool(self, section, name, default=_unset, untrusted=False):
773 780 """parse a configuration element as a boolean
774 781
775 782 >>> u = ui(); s = b'foo'
776 783 >>> u.setconfig(s, b'true', b'yes')
777 784 >>> u.configbool(s, b'true')
778 785 True
779 786 >>> u.setconfig(s, b'false', b'no')
780 787 >>> u.configbool(s, b'false')
781 788 False
782 789 >>> u.configbool(s, b'unknown')
783 790 False
784 791 >>> u.configbool(s, b'unknown', True)
785 792 True
786 793 >>> u.setconfig(s, b'invalid', b'somevalue')
787 794 >>> u.configbool(s, b'invalid')
788 795 Traceback (most recent call last):
789 796 ...
790 797 ConfigError: foo.invalid is not a boolean ('somevalue')
791 798 """
792 799
793 800 v = self._config(section, name, default, untrusted=untrusted)
794 801 if v is None:
795 802 return v
796 803 if v is _unset:
797 804 if default is _unset:
798 805 return False
799 806 return default
800 807 if isinstance(v, bool):
801 808 return v
802 809 b = stringutil.parsebool(v)
803 810 if b is None:
804 811 raise error.ConfigError(
805 812 _(b"%s.%s is not a boolean ('%s')") % (section, name, v)
806 813 )
807 814 return b
808 815
809 816 def configwith(
810 817 self, convert, section, name, default=_unset, desc=None, untrusted=False
811 818 ):
812 819 """parse a configuration element with a conversion function
813 820
814 821 >>> u = ui(); s = b'foo'
815 822 >>> u.setconfig(s, b'float1', b'42')
816 823 >>> u.configwith(float, s, b'float1')
817 824 42.0
818 825 >>> u.setconfig(s, b'float2', b'-4.25')
819 826 >>> u.configwith(float, s, b'float2')
820 827 -4.25
821 828 >>> u.configwith(float, s, b'unknown', 7)
822 829 7.0
823 830 >>> u.setconfig(s, b'invalid', b'somevalue')
824 831 >>> u.configwith(float, s, b'invalid')
825 832 Traceback (most recent call last):
826 833 ...
827 834 ConfigError: foo.invalid is not a valid float ('somevalue')
828 835 >>> u.configwith(float, s, b'invalid', desc=b'womble')
829 836 Traceback (most recent call last):
830 837 ...
831 838 ConfigError: foo.invalid is not a valid womble ('somevalue')
832 839 """
833 840
834 841 v = self.config(section, name, default, untrusted)
835 842 if v is None:
836 843 return v # do not attempt to convert None
837 844 try:
838 845 return convert(v)
839 846 except (ValueError, error.ParseError):
840 847 if desc is None:
841 848 desc = pycompat.sysbytes(convert.__name__)
842 849 raise error.ConfigError(
843 850 _(b"%s.%s is not a valid %s ('%s')") % (section, name, desc, v)
844 851 )
845 852
846 853 def configint(self, section, name, default=_unset, untrusted=False):
847 854 """parse a configuration element as an integer
848 855
849 856 >>> u = ui(); s = b'foo'
850 857 >>> u.setconfig(s, b'int1', b'42')
851 858 >>> u.configint(s, b'int1')
852 859 42
853 860 >>> u.setconfig(s, b'int2', b'-42')
854 861 >>> u.configint(s, b'int2')
855 862 -42
856 863 >>> u.configint(s, b'unknown', 7)
857 864 7
858 865 >>> u.setconfig(s, b'invalid', b'somevalue')
859 866 >>> u.configint(s, b'invalid')
860 867 Traceback (most recent call last):
861 868 ...
862 869 ConfigError: foo.invalid is not a valid integer ('somevalue')
863 870 """
864 871
865 872 return self.configwith(
866 873 int, section, name, default, b'integer', untrusted
867 874 )
868 875
869 876 def configbytes(self, section, name, default=_unset, untrusted=False):
870 877 """parse a configuration element as a quantity in bytes
871 878
872 879 Units can be specified as b (bytes), k or kb (kilobytes), m or
873 880 mb (megabytes), g or gb (gigabytes).
874 881
875 882 >>> u = ui(); s = b'foo'
876 883 >>> u.setconfig(s, b'val1', b'42')
877 884 >>> u.configbytes(s, b'val1')
878 885 42
879 886 >>> u.setconfig(s, b'val2', b'42.5 kb')
880 887 >>> u.configbytes(s, b'val2')
881 888 43520
882 889 >>> u.configbytes(s, b'unknown', b'7 MB')
883 890 7340032
884 891 >>> u.setconfig(s, b'invalid', b'somevalue')
885 892 >>> u.configbytes(s, b'invalid')
886 893 Traceback (most recent call last):
887 894 ...
888 895 ConfigError: foo.invalid is not a byte quantity ('somevalue')
889 896 """
890 897
891 898 value = self._config(section, name, default, untrusted)
892 899 if value is _unset:
893 900 if default is _unset:
894 901 default = 0
895 902 value = default
896 903 if not isinstance(value, bytes):
897 904 return value
898 905 try:
899 906 return util.sizetoint(value)
900 907 except error.ParseError:
901 908 raise error.ConfigError(
902 909 _(b"%s.%s is not a byte quantity ('%s')")
903 910 % (section, name, value)
904 911 )
905 912
906 913 def configlist(self, section, name, default=_unset, untrusted=False):
907 914 """parse a configuration element as a list of comma/space separated
908 915 strings
909 916
910 917 >>> u = ui(); s = b'foo'
911 918 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
912 919 >>> u.configlist(s, b'list1')
913 920 ['this', 'is', 'a small', 'test']
914 921 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
915 922 >>> u.configlist(s, b'list2')
916 923 ['this', 'is', 'a small', 'test']
917 924 """
918 925 # default is not always a list
919 926 v = self.configwith(
920 927 stringutil.parselist, section, name, default, b'list', untrusted
921 928 )
922 929 if isinstance(v, bytes):
923 930 return stringutil.parselist(v)
924 931 elif v is None:
925 932 return []
926 933 return v
927 934
928 935 def configdate(self, section, name, default=_unset, untrusted=False):
929 936 """parse a configuration element as a tuple of ints
930 937
931 938 >>> u = ui(); s = b'foo'
932 939 >>> u.setconfig(s, b'date', b'0 0')
933 940 >>> u.configdate(s, b'date')
934 941 (0, 0)
935 942 """
936 943 if self.config(section, name, default, untrusted):
937 944 return self.configwith(
938 945 dateutil.parsedate, section, name, default, b'date', untrusted
939 946 )
940 947 if default is _unset:
941 948 return None
942 949 return default
943 950
944 951 def configdefault(self, section, name):
945 952 """returns the default value of the config item"""
946 953 item = self._knownconfig.get(section, {}).get(name)
947 954 itemdefault = None
948 955 if item is not None:
949 956 if callable(item.default):
950 957 itemdefault = item.default()
951 958 else:
952 959 itemdefault = item.default
953 960 return itemdefault
954 961
955 962 def hasconfig(self, section, name, untrusted=False):
956 963 return self._data(untrusted).hasitem(section, name)
957 964
958 965 def has_section(self, section, untrusted=False):
959 966 '''tell whether section exists in config.'''
960 967 return section in self._data(untrusted)
961 968
962 969 def configitems(self, section, untrusted=False, ignoresub=False):
963 970 items = self._data(untrusted).items(section)
964 971 if ignoresub:
965 972 items = [i for i in items if b':' not in i[0]]
966 973 if self.debugflag and not untrusted and self._reportuntrusted:
967 974 for k, v in self._ucfg.items(section):
968 975 if self._tcfg.get(section, k) != v:
969 976 self.debug(
970 977 b"ignoring untrusted configuration option "
971 978 b"%s.%s = %s\n" % (section, k, v)
972 979 )
973 980 return items
974 981
975 982 def walkconfig(self, untrusted=False, all_known=False):
976 983 defined = self._walk_config(untrusted)
977 984 if not all_known:
978 985 for d in defined:
979 986 yield d
980 987 return
981 988 known = self._walk_known()
982 989 current_defined = next(defined, None)
983 990 current_known = next(known, None)
984 991 while current_defined is not None or current_known is not None:
985 992 if current_defined is None:
986 993 yield current_known
987 994 current_known = next(known, None)
988 995 elif current_known is None:
989 996 yield current_defined
990 997 current_defined = next(defined, None)
991 998 elif current_known[0:2] == current_defined[0:2]:
992 999 yield current_defined
993 1000 current_defined = next(defined, None)
994 1001 current_known = next(known, None)
995 1002 elif current_known[0:2] < current_defined[0:2]:
996 1003 yield current_known
997 1004 current_known = next(known, None)
998 1005 else:
999 1006 yield current_defined
1000 1007 current_defined = next(defined, None)
1001 1008
1002 1009 def _walk_known(self):
1003 1010 for section, items in sorted(self._knownconfig.items()):
1004 1011 for k, i in sorted(items.items()):
1005 1012 # We don't have a way to display generic well, so skip them
1006 1013 if i.generic:
1007 1014 continue
1008 1015 if callable(i.default):
1009 1016 default = i.default()
1010 1017 elif i.default is configitems.dynamicdefault:
1011 1018 default = b'<DYNAMIC>'
1012 1019 else:
1013 1020 default = i.default
1014 1021 yield section, i.name, default
1015 1022
1016 1023 def _walk_config(self, untrusted):
1017 1024 cfg = self._data(untrusted)
1018 1025 for section in cfg.sections():
1019 1026 for name, value in self.configitems(section, untrusted):
1020 1027 yield section, name, value
1021 1028
1022 1029 def plain(self, feature: Optional[bytes] = None) -> bool:
1023 1030 """is plain mode active?
1024 1031
1025 1032 Plain mode means that all configuration variables which affect
1026 1033 the behavior and output of Mercurial should be
1027 1034 ignored. Additionally, the output should be stable,
1028 1035 reproducible and suitable for use in scripts or applications.
1029 1036
1030 1037 The only way to trigger plain mode is by setting either the
1031 1038 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
1032 1039
1033 1040 The return value can either be
1034 1041 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
1035 1042 - False if feature is disabled by default and not included in HGPLAIN
1036 1043 - True otherwise
1037 1044 """
1038 1045 if (
1039 1046 b'HGPLAIN' not in encoding.environ
1040 1047 and b'HGPLAINEXCEPT' not in encoding.environ
1041 1048 ):
1042 1049 return False
1043 1050 exceptions = (
1044 1051 encoding.environ.get(b'HGPLAINEXCEPT', b'').strip().split(b',')
1045 1052 )
1046 1053 # TODO: add support for HGPLAIN=+feature,-feature syntax
1047 1054 if b'+strictflags' not in encoding.environ.get(b'HGPLAIN', b'').split(
1048 1055 b','
1049 1056 ):
1050 1057 exceptions.append(b'strictflags')
1051 1058 if feature and exceptions:
1052 1059 return feature not in exceptions
1053 1060 return True
1054 1061
1055 1062 def username(self, acceptempty=False):
1056 1063 """Return default username to be used in commits.
1057 1064
1058 1065 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
1059 1066 and stop searching if one of these is set.
1060 1067 If not found and acceptempty is True, returns None.
1061 1068 If not found and ui.askusername is True, ask the user, else use
1062 1069 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
1063 1070 If no username could be found, raise an Abort error.
1064 1071 """
1065 1072 user = encoding.environ.get(b"HGUSER")
1066 1073 if user is None:
1067 1074 user = self.config(b"ui", b"username")
1068 1075 if user is not None:
1069 1076 user = os.path.expandvars(user)
1070 1077 if user is None:
1071 1078 user = encoding.environ.get(b"EMAIL")
1072 1079 if user is None and acceptempty:
1073 1080 return user
1074 1081 if user is None and self.configbool(b"ui", b"askusername"):
1075 1082 user = self.prompt(_(b"enter a commit username:"), default=None)
1076 1083 if user is None and not self.interactive():
1077 1084 try:
1078 1085 user = b'%s@%s' % (
1079 1086 procutil.getuser(),
1080 1087 encoding.strtolocal(socket.getfqdn()),
1081 1088 )
1082 1089 self.warn(_(b"no username found, using '%s' instead\n") % user)
1083 1090 except KeyError:
1084 1091 pass
1085 1092 if not user:
1086 1093 raise error.Abort(
1087 1094 _(b'no username supplied'),
1088 1095 hint=_(b"use 'hg config --edit' " b'to set your username'),
1089 1096 )
1090 1097 if b"\n" in user:
1091 1098 raise error.Abort(
1092 1099 _(b"username %r contains a newline\n") % pycompat.bytestr(user)
1093 1100 )
1094 1101 return user
1095 1102
1096 1103 def shortuser(self, user: bytes) -> bytes:
1097 1104 """Return a short representation of a user name or email address."""
1098 1105 if not self.verbose:
1099 1106 user = stringutil.shortuser(user)
1100 1107 return user
1101 1108
1102 1109 @util.propertycache
1103 1110 def paths(self):
1104 1111 return urlutil.paths(self)
1105 1112
1106 1113 @property
1107 1114 def fout(self):
1108 1115 return self._fout
1109 1116
1110 1117 @util.propertycache
1111 1118 def _fout_is_a_tty(self):
1112 1119 self._isatty(self._fout)
1113 1120
1114 1121 @fout.setter
1115 1122 def fout(self, f):
1116 1123 self._fout = f
1117 1124 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1118 1125 if '_fout_is_a_tty' in vars(self):
1119 1126 del self._fout_is_a_tty
1120 1127
1121 1128 @property
1122 1129 def ferr(self):
1123 1130 return self._ferr
1124 1131
1125 1132 @ferr.setter
1126 1133 def ferr(self, f):
1127 1134 self._ferr = f
1128 1135 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1129 1136
1130 1137 @property
1131 1138 def fin(self):
1132 1139 return self._fin
1133 1140
1134 1141 @fin.setter
1135 1142 def fin(self, f):
1136 1143 self._fin = f
1137 1144
1138 1145 @property
1139 1146 def fmsg(self):
1140 1147 """Stream dedicated for status/error messages; may be None if
1141 1148 fout/ferr are used"""
1142 1149 return self._fmsg
1143 1150
1144 1151 @fmsg.setter
1145 1152 def fmsg(self, f):
1146 1153 self._fmsg = f
1147 1154 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
1148 1155
1149 1156 @contextlib.contextmanager
1150 1157 def silent(
1151 1158 self, error: bool = False, subproc: bool = False, labeled: bool = False
1152 1159 ):
1153 1160 self.pushbuffer(error=error, subproc=subproc, labeled=labeled)
1154 1161 try:
1155 1162 yield
1156 1163 finally:
1157 1164 self.popbuffer()
1158 1165
1159 1166 def pushbuffer(
1160 1167 self, error: bool = False, subproc: bool = False, labeled: bool = False
1161 1168 ) -> None:
1162 1169 """install a buffer to capture standard output of the ui object
1163 1170
1164 1171 If error is True, the error output will be captured too.
1165 1172
1166 1173 If subproc is True, output from subprocesses (typically hooks) will be
1167 1174 captured too.
1168 1175
1169 1176 If labeled is True, any labels associated with buffered
1170 1177 output will be handled. By default, this has no effect
1171 1178 on the output returned, but extensions and GUI tools may
1172 1179 handle this argument and returned styled output. If output
1173 1180 is being buffered so it can be captured and parsed or
1174 1181 processed, labeled should not be set to True.
1175 1182 """
1176 1183 self._buffers.append([])
1177 1184 self._bufferstates.append((error, subproc, labeled))
1178 1185 self._bufferapplylabels = labeled
1179 1186
1180 1187 def popbuffer(self) -> bytes:
1181 1188 '''pop the last buffer and return the buffered output'''
1182 1189 self._bufferstates.pop()
1183 1190 if self._bufferstates:
1184 1191 self._bufferapplylabels = self._bufferstates[-1][2]
1185 1192 else:
1186 1193 self._bufferapplylabels = None
1187 1194
1188 1195 return b"".join(self._buffers.pop())
1189 1196
1190 1197 def _isbuffered(self, dest) -> bool:
1191 1198 if dest is self._fout:
1192 1199 return bool(self._buffers)
1193 1200 if dest is self._ferr:
1194 1201 return bool(self._bufferstates and self._bufferstates[-1][0])
1195 1202 return False
1196 1203
1197 1204 def canwritewithoutlabels(self) -> bool:
1198 1205 '''check if write skips the label'''
1199 1206 if self._buffers and not self._bufferapplylabels:
1200 1207 return True
1201 1208 return self._colormode is None
1202 1209
1203 1210 def canbatchlabeledwrites(self) -> bool:
1204 1211 '''check if write calls with labels are batchable'''
1205 1212 # Windows color printing is special, see ``write``.
1206 1213 return self._colormode != b'win32'
1207 1214
1208 1215 def write(self, *args: bytes, **opts: _MsgOpts) -> None:
1209 1216 """write args to output
1210 1217
1211 1218 By default, this method simply writes to the buffer or stdout.
1212 1219 Color mode can be set on the UI class to have the output decorated
1213 1220 with color modifier before being written to stdout.
1214 1221
1215 1222 The color used is controlled by an optional keyword argument, "label".
1216 1223 This should be a string containing label names separated by space.
1217 1224 Label names take the form of "topic.type". For example, ui.debug()
1218 1225 issues a label of "ui.debug".
1219 1226
1220 1227 Progress reports via stderr are normally cleared before writing as
1221 1228 stdout and stderr go to the same terminal. This can be skipped with
1222 1229 the optional keyword argument "keepprogressbar". The progress bar
1223 1230 will continue to occupy a partial line on stderr in that case.
1224 1231 This functionality is intended when Mercurial acts as data source
1225 1232 in a pipe.
1226 1233
1227 1234 When labeling output for a specific command, a label of
1228 1235 "cmdname.type" is recommended. For example, status issues
1229 1236 a label of "status.modified" for modified files.
1230 1237 """
1231 1238 dest = self._fout
1232 1239
1233 1240 # inlined _write() for speed
1234 1241 if self._buffers:
1235 1242 label = opts.get('label', b'')
1236 1243 if label and self._bufferapplylabels:
1237 1244 self._buffers[-1].extend(self.label(a, label) for a in args)
1238 1245 else:
1239 1246 self._buffers[-1].extend(args)
1240 1247 return
1241 1248
1242 1249 # inlined _writenobuf() for speed
1243 1250 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1244 1251 self._progclear()
1245 1252 msg = b''.join(args)
1246 1253
1247 1254 # opencode timeblockedsection because this is a critical path
1248 1255 starttime = util.timer()
1249 1256 try:
1250 1257 if self._colormode == b'win32':
1251 1258 # windows color printing is its own can of crab, defer to
1252 1259 # the color module and that is it.
1253 1260 color.win32print(self, dest.write, msg, **opts)
1254 1261 else:
1255 1262 if self._colormode is not None:
1256 1263 label = opts.get('label', b'')
1257 1264 msg = self.label(msg, label)
1258 1265 dest.write(msg)
1259 1266 except IOError as err:
1260 1267 raise error.StdioError(err)
1261 1268 finally:
1262 1269 self._blockedtimes[b'stdio_blocked'] += (
1263 1270 util.timer() - starttime
1264 1271 ) * 1000
1265 1272
1266 1273 def write_err(self, *args: bytes, **opts: _MsgOpts) -> None:
1267 1274 self._write(self._ferr, *args, **opts)
1268 1275
1269 1276 def _write(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1270 1277 # update write() as well if you touch this code
1271 1278 if self._isbuffered(dest):
1272 1279 label = opts.get('label', b'')
1273 1280 if label and self._bufferapplylabels:
1274 1281 self._buffers[-1].extend(self.label(a, label) for a in args)
1275 1282 else:
1276 1283 self._buffers[-1].extend(args)
1277 1284 else:
1278 1285 self._writenobuf(dest, *args, **opts)
1279 1286
1280 1287 def _writenobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1281 1288 # update write() as well if you touch this code
1282 1289 if not opts.get('keepprogressbar', self._fout_is_a_tty):
1283 1290 self._progclear()
1284 1291 msg = b''.join(args)
1285 1292
1286 1293 # opencode timeblockedsection because this is a critical path
1287 1294 starttime = util.timer()
1288 1295 try:
1289 1296 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1290 1297 self._fout.flush()
1291 1298 if getattr(dest, 'structured', False):
1292 1299 # channel for machine-readable output with metadata, where
1293 1300 # no extra colorization is necessary.
1294 1301 dest.write(msg, **opts)
1295 1302 elif self._colormode == b'win32':
1296 1303 # windows color printing is its own can of crab, defer to
1297 1304 # the color module and that is it.
1298 1305 color.win32print(self, dest.write, msg, **opts)
1299 1306 else:
1300 1307 if self._colormode is not None:
1301 1308 label = opts.get('label', b'')
1302 1309 msg = self.label(msg, label)
1303 1310 dest.write(msg)
1304 1311 # stderr may be buffered under win32 when redirected to files,
1305 1312 # including stdout.
1306 1313 if dest is self._ferr and not getattr(dest, 'closed', False):
1307 1314 dest.flush()
1308 1315 except IOError as err:
1309 1316 if dest is self._ferr and err.errno in (
1310 1317 errno.EPIPE,
1311 1318 errno.EIO,
1312 1319 errno.EBADF,
1313 1320 ):
1314 1321 # no way to report the error, so ignore it
1315 1322 return
1316 1323 raise error.StdioError(err)
1317 1324 finally:
1318 1325 self._blockedtimes[b'stdio_blocked'] += (
1319 1326 util.timer() - starttime
1320 1327 ) * 1000
1321 1328
1322 1329 def _writemsg(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1323 1330 timestamp = self.showtimestamp and opts.get('type') in {
1324 1331 b'debug',
1325 1332 b'error',
1326 1333 b'note',
1327 1334 b'status',
1328 1335 b'warning',
1329 1336 }
1330 1337 if timestamp:
1331 1338 args = (
1332 1339 b'[%s] '
1333 1340 % pycompat.bytestr(datetime.datetime.now().isoformat()),
1334 1341 ) + args
1335 1342 _writemsgwith(self._write, dest, *args, **opts)
1336 1343 if timestamp:
1337 1344 dest.flush()
1338 1345
1339 1346 def _writemsgnobuf(self, dest, *args: bytes, **opts: _MsgOpts) -> None:
1340 1347 _writemsgwith(self._writenobuf, dest, *args, **opts)
1341 1348
1342 1349 def flush(self) -> None:
1343 1350 # opencode timeblockedsection because this is a critical path
1344 1351 starttime = util.timer()
1345 1352 try:
1346 1353 try:
1347 1354 self._fout.flush()
1348 1355 except IOError as err:
1349 1356 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1350 1357 raise error.StdioError(err)
1351 1358 finally:
1352 1359 try:
1353 1360 self._ferr.flush()
1354 1361 except IOError as err:
1355 1362 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1356 1363 raise error.StdioError(err)
1357 1364 finally:
1358 1365 self._blockedtimes[b'stdio_blocked'] += (
1359 1366 util.timer() - starttime
1360 1367 ) * 1000
1361 1368
1362 1369 def _isatty(self, fh) -> bool:
1363 1370 if self.configbool(b'ui', b'nontty'):
1364 1371 return False
1365 1372 return procutil.isatty(fh)
1366 1373
1367 1374 def protectfinout(self):
1368 1375 """Duplicate ui streams and redirect original if they are stdio
1369 1376
1370 1377 Returns (fin, fout) which point to the original ui fds, but may be
1371 1378 copy of them. The returned streams can be considered "owned" in that
1372 1379 print(), exec(), etc. never reach to them.
1373 1380 """
1374 1381 if self._finoutredirected:
1375 1382 # if already redirected, protectstdio() would just create another
1376 1383 # nullfd pair, which is equivalent to returning self._fin/_fout.
1377 1384 return self._fin, self._fout
1378 1385 fin, fout = procutil.protectstdio(self._fin, self._fout)
1379 1386 self._finoutredirected = (fin, fout) != (self._fin, self._fout)
1380 1387 return fin, fout
1381 1388
1382 1389 def restorefinout(self, fin, fout):
1383 1390 """Restore ui streams from possibly duplicated (fin, fout)"""
1384 1391 if (fin, fout) == (self._fin, self._fout):
1385 1392 return
1386 1393 procutil.restorestdio(self._fin, self._fout, fin, fout)
1387 1394 # protectfinout() won't create more than one duplicated streams,
1388 1395 # so we can just turn the redirection flag off.
1389 1396 self._finoutredirected = False
1390 1397
1391 1398 @contextlib.contextmanager
1392 1399 def protectedfinout(self):
1393 1400 """Run code block with protected standard streams"""
1394 1401 fin, fout = self.protectfinout()
1395 1402 try:
1396 1403 yield fin, fout
1397 1404 finally:
1398 1405 self.restorefinout(fin, fout)
1399 1406
1400 1407 def disablepager(self) -> None:
1401 1408 self._disablepager = True
1402 1409
1403 1410 def pager(self, command: bytes) -> None:
1404 1411 """Start a pager for subsequent command output.
1405 1412
1406 1413 Commands which produce a long stream of output should call
1407 1414 this function to activate the user's preferred pagination
1408 1415 mechanism (which may be no pager). Calling this function
1409 1416 precludes any future use of interactive functionality, such as
1410 1417 prompting the user or activating curses.
1411 1418
1412 1419 Args:
1413 1420 command: The full, non-aliased name of the command. That is, "log"
1414 1421 not "history, "summary" not "summ", etc.
1415 1422 """
1416 1423 if self._disablepager or self.pageractive:
1417 1424 # how pager should do is already determined
1418 1425 return
1419 1426
1420 1427 if not command.startswith(b'internal-always-') and (
1421 1428 # explicit --pager=on (= 'internal-always-' prefix) should
1422 1429 # take precedence over disabling factors below
1423 1430 command in self.configlist(b'pager', b'ignore')
1424 1431 or not self.configbool(b'ui', b'paginate')
1425 1432 or not self.configbool(b'pager', b'attend-' + command, True)
1426 1433 or encoding.environ.get(b'TERM') == b'dumb'
1427 1434 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1428 1435 # formatted() will need some adjustment.
1429 1436 or not self.formatted()
1430 1437 or self.plain()
1431 1438 or self._buffers
1432 1439 # TODO: expose debugger-enabled on the UI object
1433 1440 or b'--debugger' in pycompat.sysargv
1434 1441 ):
1435 1442 # We only want to paginate if the ui appears to be
1436 1443 # interactive, the user didn't say HGPLAIN or
1437 1444 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1438 1445 return
1439 1446
1440 1447 # py2exe doesn't appear to be able to use legacy I/O, and nothing is
1441 1448 # output to the pager for paged commands. Piping to `more` in cmd.exe
1442 1449 # works, but is easy to forget. Just disable pager for py2exe, but
1443 1450 # leave it working for pyoxidizer and exewrapper builds.
1444 1451 if pycompat.iswindows and getattr(sys, "frozen", None) == "console_exe":
1445 1452 self.debug(b"pager is unavailable with py2exe packaging\n")
1446 1453 return
1447 1454
1448 1455 pagercmd = self.config(b'pager', b'pager', rcutil.fallbackpager)
1449 1456 if not pagercmd:
1450 1457 return
1451 1458
1452 1459 pagerenv = {}
1453 1460 for name, value in rcutil.defaultpagerenv().items():
1454 1461 if name not in encoding.environ:
1455 1462 pagerenv[name] = value
1456 1463
1457 1464 self.debug(
1458 1465 b'starting pager for command %s\n' % stringutil.pprint(command)
1459 1466 )
1460 1467 self.flush()
1461 1468
1462 1469 wasformatted = self.formatted()
1463 1470 if util.safehasattr(signal, b"SIGPIPE"):
1464 1471 signal.signal(signal.SIGPIPE, _catchterm)
1465 1472 if self._runpager(pagercmd, pagerenv):
1466 1473 self.pageractive = True
1467 1474 # Preserve the formatted-ness of the UI. This is important
1468 1475 # because we mess with stdout, which might confuse
1469 1476 # auto-detection of things being formatted.
1470 1477 self.setconfig(b'ui', b'formatted', wasformatted, b'pager')
1471 1478 self.setconfig(b'ui', b'interactive', False, b'pager')
1472 1479
1473 1480 # If pagermode differs from color.mode, reconfigure color now that
1474 1481 # pageractive is set.
1475 1482 cm = self._colormode
1476 1483 if cm != self.config(b'color', b'pagermode', cm):
1477 1484 color.setup(self)
1478 1485 else:
1479 1486 # If the pager can't be spawned in dispatch when --pager=on is
1480 1487 # given, don't try again when the command runs, to avoid a duplicate
1481 1488 # warning about a missing pager command.
1482 1489 self.disablepager()
1483 1490
1484 1491 def _runpager(self, command: bytes, env=None) -> bool:
1485 1492 """Actually start the pager and set up file descriptors.
1486 1493
1487 1494 This is separate in part so that extensions (like chg) can
1488 1495 override how a pager is invoked.
1489 1496 """
1490 1497 if command == b'cat':
1491 1498 # Save ourselves some work.
1492 1499 return False
1493 1500 # If the command doesn't contain any of these characters, we
1494 1501 # assume it's a binary and exec it directly. This means for
1495 1502 # simple pager command configurations, we can degrade
1496 1503 # gracefully and tell the user about their broken pager.
1497 1504 shell = any(c in command for c in b"|&;<>()$`\\\"' \t\n*?[#~=%")
1498 1505
1499 1506 if pycompat.iswindows and not shell:
1500 1507 # Window's built-in `more` cannot be invoked with shell=False, but
1501 1508 # its `more.com` can. Hide this implementation detail from the
1502 1509 # user so we can also get sane bad PAGER behavior. MSYS has
1503 1510 # `more.exe`, so do a cmd.exe style resolution of the executable to
1504 1511 # determine which one to use.
1505 1512 fullcmd = procutil.findexe(command)
1506 1513 if not fullcmd:
1507 1514 self.warn(
1508 1515 _(b"missing pager command '%s', skipping pager\n") % command
1509 1516 )
1510 1517 return False
1511 1518
1512 1519 command = fullcmd
1513 1520
1514 1521 try:
1515 1522 pager = subprocess.Popen(
1516 1523 procutil.tonativestr(command),
1517 1524 shell=shell,
1518 1525 bufsize=-1,
1519 1526 close_fds=procutil.closefds,
1520 1527 stdin=subprocess.PIPE,
1521 1528 stdout=procutil.stdout,
1522 1529 stderr=procutil.stderr,
1523 1530 env=procutil.tonativeenv(procutil.shellenviron(env)),
1524 1531 )
1525 1532 except FileNotFoundError:
1526 1533 if not shell:
1527 1534 self.warn(
1528 1535 _(b"missing pager command '%s', skipping pager\n") % command
1529 1536 )
1530 1537 return False
1531 1538 raise
1532 1539
1533 1540 # back up original file descriptors
1534 1541 stdoutfd = os.dup(procutil.stdout.fileno())
1535 1542 stderrfd = os.dup(procutil.stderr.fileno())
1536 1543
1537 1544 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1538 1545 if self._isatty(procutil.stderr):
1539 1546 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1540 1547
1541 1548 @self.atexit
1542 1549 def killpager():
1543 1550 if util.safehasattr(signal, b"SIGINT"):
1544 1551 signal.signal(signal.SIGINT, signal.SIG_IGN)
1545 1552 # restore original fds, closing pager.stdin copies in the process
1546 1553 os.dup2(stdoutfd, procutil.stdout.fileno())
1547 1554 os.dup2(stderrfd, procutil.stderr.fileno())
1548 1555 pager.stdin.close()
1549 1556 pager.wait()
1550 1557
1551 1558 return True
1552 1559
1553 1560 @property
1554 1561 def _exithandlers(self):
1555 1562 return _reqexithandlers
1556 1563
1557 1564 def atexit(self, func, *args, **kwargs):
1558 1565 """register a function to run after dispatching a request
1559 1566
1560 1567 Handlers do not stay registered across request boundaries."""
1561 1568 self._exithandlers.append((func, args, kwargs))
1562 1569 return func
1563 1570
1564 1571 def interface(self, feature: bytes) -> bytes:
1565 1572 """what interface to use for interactive console features?
1566 1573
1567 1574 The interface is controlled by the value of `ui.interface` but also by
1568 1575 the value of feature-specific configuration. For example:
1569 1576
1570 1577 ui.interface.histedit = text
1571 1578 ui.interface.chunkselector = curses
1572 1579
1573 1580 Here the features are "histedit" and "chunkselector".
1574 1581
1575 1582 The configuration above means that the default interfaces for commands
1576 1583 is curses, the interface for histedit is text and the interface for
1577 1584 selecting chunk is crecord (the best curses interface available).
1578 1585
1579 1586 Consider the following example:
1580 1587 ui.interface = curses
1581 1588 ui.interface.histedit = text
1582 1589
1583 1590 Then histedit will use the text interface and chunkselector will use
1584 1591 the default curses interface (crecord at the moment).
1585 1592 """
1586 1593 alldefaults = frozenset([b"text", b"curses"])
1587 1594
1588 1595 featureinterfaces = {
1589 1596 b"chunkselector": [
1590 1597 b"text",
1591 1598 b"curses",
1592 1599 ],
1593 1600 b"histedit": [
1594 1601 b"text",
1595 1602 b"curses",
1596 1603 ],
1597 1604 }
1598 1605
1599 1606 # Feature-specific interface
1600 1607 if feature not in featureinterfaces.keys():
1601 1608 # Programming error, not user error
1602 1609 raise ValueError(b"Unknown feature requested %s" % feature)
1603 1610
1604 1611 availableinterfaces = frozenset(featureinterfaces[feature])
1605 1612 if alldefaults > availableinterfaces:
1606 1613 # Programming error, not user error. We need a use case to
1607 1614 # define the right thing to do here.
1608 1615 raise ValueError(
1609 1616 b"Feature %s does not handle all default interfaces" % feature
1610 1617 )
1611 1618
1612 1619 if self.plain() or encoding.environ.get(b'TERM') == b'dumb':
1613 1620 return b"text"
1614 1621
1615 1622 # Default interface for all the features
1616 1623 defaultinterface = b"text"
1617 1624 i = self.config(b"ui", b"interface")
1618 1625 if i in alldefaults:
1619 1626 defaultinterface = cast(bytes, i) # cast to help pytype
1620 1627
1621 1628 choseninterface: bytes = defaultinterface
1622 1629 f = self.config(b"ui", b"interface.%s" % feature)
1623 1630 if f in availableinterfaces:
1624 1631 choseninterface = cast(bytes, f) # cast to help pytype
1625 1632
1626 1633 if i is not None and defaultinterface != i:
1627 1634 if f is not None:
1628 1635 self.warn(_(b"invalid value for ui.interface: %s\n") % (i,))
1629 1636 else:
1630 1637 self.warn(
1631 1638 _(b"invalid value for ui.interface: %s (using %s)\n")
1632 1639 % (i, choseninterface)
1633 1640 )
1634 1641 if f is not None and choseninterface != f:
1635 1642 self.warn(
1636 1643 _(b"invalid value for ui.interface.%s: %s (using %s)\n")
1637 1644 % (feature, f, choseninterface)
1638 1645 )
1639 1646
1640 1647 return choseninterface
1641 1648
1642 1649 def interactive(self):
1643 1650 """is interactive input allowed?
1644 1651
1645 1652 An interactive session is a session where input can be reasonably read
1646 1653 from `sys.stdin'. If this function returns false, any attempt to read
1647 1654 from stdin should fail with an error, unless a sensible default has been
1648 1655 specified.
1649 1656
1650 1657 Interactiveness is triggered by the value of the `ui.interactive'
1651 1658 configuration variable or - if it is unset - when `sys.stdin' points
1652 1659 to a terminal device.
1653 1660
1654 1661 This function refers to input only; for output, see `ui.formatted()'.
1655 1662 """
1656 1663 i = self.configbool(b"ui", b"interactive")
1657 1664 if i is None:
1658 1665 # some environments replace stdin without implementing isatty
1659 1666 # usually those are non-interactive
1660 1667 return self._isatty(self._fin)
1661 1668
1662 1669 return i
1663 1670
1664 1671 def termwidth(self) -> int:
1665 1672 """how wide is the terminal in columns?"""
1666 1673 if b'COLUMNS' in encoding.environ:
1667 1674 try:
1668 1675 return int(encoding.environ[b'COLUMNS'])
1669 1676 except ValueError:
1670 1677 pass
1671 1678 return scmutil.termsize(self)[0]
1672 1679
1673 1680 def formatted(self):
1674 1681 """should formatted output be used?
1675 1682
1676 1683 It is often desirable to format the output to suite the output medium.
1677 1684 Examples of this are truncating long lines or colorizing messages.
1678 1685 However, this is not often not desirable when piping output into other
1679 1686 utilities, e.g. `grep'.
1680 1687
1681 1688 Formatted output is triggered by the value of the `ui.formatted'
1682 1689 configuration variable or - if it is unset - when `sys.stdout' points
1683 1690 to a terminal device. Please note that `ui.formatted' should be
1684 1691 considered an implementation detail; it is not intended for use outside
1685 1692 Mercurial or its extensions.
1686 1693
1687 1694 This function refers to output only; for input, see `ui.interactive()'.
1688 1695 This function always returns false when in plain mode, see `ui.plain()'.
1689 1696 """
1690 1697 if self.plain():
1691 1698 return False
1692 1699
1693 1700 i = self.configbool(b"ui", b"formatted")
1694 1701 if i is None:
1695 1702 # some environments replace stdout without implementing isatty
1696 1703 # usually those are non-interactive
1697 1704 return self._isatty(self._fout)
1698 1705
1699 1706 return i
1700 1707
1701 1708 def _readline(
1702 1709 self,
1703 1710 prompt: bytes = b' ',
1704 1711 promptopts: Optional[Dict[str, _MsgOpts]] = None,
1705 1712 ) -> bytes:
1706 1713 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1707 1714 # because they have to be text streams with *no buffering*. Instead,
1708 1715 # we use rawinput() only if call_readline() will be invoked by
1709 1716 # PyOS_Readline(), so no I/O will be made at Python layer.
1710 1717 usereadline = (
1711 1718 self._isatty(self._fin)
1712 1719 and self._isatty(self._fout)
1713 1720 and procutil.isstdin(self._fin)
1714 1721 and procutil.isstdout(self._fout)
1715 1722 )
1716 1723 if usereadline:
1717 1724 try:
1718 1725 # magically add command line editing support, where
1719 1726 # available
1720 1727 import readline
1721 1728
1722 1729 # force demandimport to really load the module
1723 1730 readline.read_history_file
1724 1731 # windows sometimes raises something other than ImportError
1725 1732 except Exception:
1726 1733 usereadline = False
1727 1734
1728 1735 if self._colormode == b'win32' or not usereadline:
1729 1736 if not promptopts:
1730 1737 promptopts = {}
1731 1738 self._writemsgnobuf(
1732 1739 self._fmsgout, prompt, type=b'prompt', **promptopts
1733 1740 )
1734 1741 self.flush()
1735 1742 prompt = b' '
1736 1743 else:
1737 1744 prompt = self.label(prompt, b'ui.prompt') + b' '
1738 1745
1739 1746 # prompt ' ' must exist; otherwise readline may delete entire line
1740 1747 # - http://bugs.python.org/issue12833
1741 1748 with self.timeblockedsection(b'stdio'):
1742 1749 if usereadline:
1743 1750 self.flush()
1744 1751 prompt = encoding.strfromlocal(prompt)
1745 1752 line = encoding.strtolocal(input(prompt))
1746 1753 # When stdin is in binary mode on Windows, it can cause
1747 1754 # input() to emit an extra trailing carriage return
1748 1755 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1749 1756 line = line[:-1]
1750 1757 else:
1751 1758 self._fout.write(pycompat.bytestr(prompt))
1752 1759 self._fout.flush()
1753 1760 line = self._fin.readline()
1754 1761 if not line:
1755 1762 raise EOFError
1756 1763 line = line.rstrip(pycompat.oslinesep)
1757 1764
1758 1765 return line
1759 1766
1760 1767 if pycompat.TYPE_CHECKING:
1761 1768
1762 1769 @overload
1763 1770 def prompt(self, msg: bytes, default: bytes) -> bytes:
1764 1771 pass
1765 1772
1766 1773 @overload
1767 1774 def prompt(self, msg: bytes, default: None) -> Optional[bytes]:
1768 1775 pass
1769 1776
1770 1777 def prompt(self, msg, default=b"y"):
1771 1778 """Prompt user with msg, read response.
1772 1779 If ui is not interactive, the default is returned.
1773 1780 """
1774 1781 return self._prompt(msg, default=default)
1775 1782
1776 1783 if pycompat.TYPE_CHECKING:
1777 1784
1778 1785 @overload
1779 1786 def _prompt(
1780 1787 self, msg: bytes, default: bytes, **opts: _MsgOpts
1781 1788 ) -> bytes:
1782 1789 pass
1783 1790
1784 1791 @overload
1785 1792 def _prompt(
1786 1793 self, msg: bytes, default: None, **opts: _MsgOpts
1787 1794 ) -> Optional[bytes]:
1788 1795 pass
1789 1796
1790 1797 def _prompt(self, msg, default=b'y', **opts):
1791 1798 opts = {**opts, 'default': default}
1792 1799 if not self.interactive():
1793 1800 self._writemsg(self._fmsgout, msg, b' ', type=b'prompt', **opts)
1794 1801 self._writemsg(
1795 1802 self._fmsgout, default or b'', b"\n", type=b'promptecho'
1796 1803 )
1797 1804 return default
1798 1805 try:
1799 1806 r = self._readline(prompt=msg, promptopts=opts)
1800 1807 if not r:
1801 1808 r = default
1802 1809 if self.configbool(b'ui', b'promptecho'):
1803 1810 self._writemsg(
1804 1811 self._fmsgout, r or b'', b"\n", type=b'promptecho'
1805 1812 )
1806 1813 return r
1807 1814 except EOFError:
1808 1815 raise error.ResponseExpected()
1809 1816
1810 1817 @staticmethod
1811 1818 def extractchoices(prompt: bytes) -> Tuple[bytes, List[_PromptChoice]]:
1812 1819 """Extract prompt message and list of choices from specified prompt.
1813 1820
1814 1821 This returns tuple "(message, choices)", and "choices" is the
1815 1822 list of tuple "(response character, text without &)".
1816 1823
1817 1824 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1818 1825 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1819 1826 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1820 1827 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1821 1828 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1822 1829 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1823 1830 """
1824 1831
1825 1832 # Sadly, the prompt string may have been built with a filename
1826 1833 # containing "$$" so let's try to find the first valid-looking
1827 1834 # prompt to start parsing. Sadly, we also can't rely on
1828 1835 # choices containing spaces, ASCII, or basically anything
1829 1836 # except an ampersand followed by a character.
1830 1837 m = re.match(br'(?s)(.+?)\$\$([^$]*&[^ $].*)', prompt)
1831 1838
1832 1839 assert m is not None # help pytype
1833 1840
1834 1841 msg = m.group(1)
1835 1842 choices = [p.strip(b' ') for p in m.group(2).split(b'$$')]
1836 1843
1837 1844 def choicetuple(s):
1838 1845 ampidx = s.index(b'&')
1839 1846 return s[ampidx + 1 : ampidx + 2].lower(), s.replace(b'&', b'', 1)
1840 1847
1841 1848 return (msg, [choicetuple(s) for s in choices])
1842 1849
1843 1850 def promptchoice(self, prompt: bytes, default: int = 0) -> int:
1844 1851 """Prompt user with a message, read response, and ensure it matches
1845 1852 one of the provided choices. The prompt is formatted as follows:
1846 1853
1847 1854 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1848 1855
1849 1856 The index of the choice is returned. Responses are case
1850 1857 insensitive. If ui is not interactive, the default is
1851 1858 returned.
1852 1859 """
1853 1860
1854 1861 msg, choices = self.extractchoices(prompt)
1855 1862 resps = [r for r, t in choices]
1856 1863 while True:
1857 1864 r = self._prompt(msg, default=resps[default], choices=choices)
1858 1865 if r.lower() in resps:
1859 1866 return resps.index(r.lower())
1860 1867 # TODO: shouldn't it be a warning?
1861 1868 self._writemsg(self._fmsgout, _(b"unrecognized response\n"))
1862 1869
1863 1870 def getpass(
1864 1871 self, prompt: Optional[bytes] = None, default: Optional[bytes] = None
1865 1872 ) -> Optional[bytes]:
1866 1873 if not self.interactive():
1867 1874 return default
1868 1875 try:
1869 1876 self._writemsg(
1870 1877 self._fmsgerr,
1871 1878 prompt or _(b'password: '),
1872 1879 type=b'prompt',
1873 1880 password=True,
1874 1881 )
1875 1882 # disable getpass() only if explicitly specified. it's still valid
1876 1883 # to interact with tty even if fin is not a tty.
1877 1884 with self.timeblockedsection(b'stdio'):
1878 1885 if self.configbool(b'ui', b'nontty'):
1879 1886 l = self._fin.readline()
1880 1887 if not l:
1881 1888 raise EOFError
1882 1889 return l.rstrip(b'\n')
1883 1890 else:
1884 1891 return util.get_password()
1885 1892 except EOFError:
1886 1893 raise error.ResponseExpected()
1887 1894
1888 1895 def status(self, *msg: bytes, **opts: _MsgOpts) -> None:
1889 1896 """write status message to output (if ui.quiet is False)
1890 1897
1891 1898 This adds an output label of "ui.status".
1892 1899 """
1893 1900 if not self.quiet:
1894 1901 self._writemsg(self._fmsgout, type=b'status', *msg, **opts)
1895 1902
1896 1903 def warn(self, *msg: bytes, **opts: _MsgOpts) -> None:
1897 1904 """write warning message to output (stderr)
1898 1905
1899 1906 This adds an output label of "ui.warning".
1900 1907 """
1901 1908 self._writemsg(self._fmsgerr, type=b'warning', *msg, **opts)
1902 1909
1903 1910 def error(self, *msg: bytes, **opts: _MsgOpts) -> None:
1904 1911 """write error message to output (stderr)
1905 1912
1906 1913 This adds an output label of "ui.error".
1907 1914 """
1908 1915 self._writemsg(self._fmsgerr, type=b'error', *msg, **opts)
1909 1916
1910 1917 def note(self, *msg: bytes, **opts: _MsgOpts) -> None:
1911 1918 """write note to output (if ui.verbose is True)
1912 1919
1913 1920 This adds an output label of "ui.note".
1914 1921 """
1915 1922 if self.verbose:
1916 1923 self._writemsg(self._fmsgout, type=b'note', *msg, **opts)
1917 1924
1918 1925 def debug(self, *msg: bytes, **opts: _MsgOpts) -> None:
1919 1926 """write debug message to output (if ui.debugflag is True)
1920 1927
1921 1928 This adds an output label of "ui.debug".
1922 1929 """
1923 1930 if self.debugflag:
1924 1931 self._writemsg(self._fmsgout, type=b'debug', *msg, **opts)
1925 1932 self.log(b'debug', b'%s', b''.join(msg))
1926 1933
1927 1934 # Aliases to defeat check-code.
1928 1935 statusnoi18n = status
1929 1936 notenoi18n = note
1930 1937 warnnoi18n = warn
1931 1938 writenoi18n = write
1932 1939
1933 1940 def edit(
1934 1941 self,
1935 1942 text: bytes,
1936 1943 user: bytes,
1937 1944 extra: Optional[Dict[bytes, Any]] = None, # TODO: value type of bytes?
1938 1945 editform=None,
1939 1946 pending=None,
1940 1947 repopath: Optional[bytes] = None,
1941 1948 action: Optional[bytes] = None,
1942 1949 ) -> bytes:
1943 1950 if action is None:
1944 1951 self.develwarn(
1945 1952 b'action is None but will soon be a required '
1946 1953 b'parameter to ui.edit()'
1947 1954 )
1948 1955 extra_defaults = {
1949 1956 b'prefix': b'editor',
1950 1957 b'suffix': b'.txt',
1951 1958 }
1952 1959 if extra is not None:
1953 1960 if extra.get(b'suffix') is not None:
1954 1961 self.develwarn(
1955 1962 b'extra.suffix is not None but will soon be '
1956 1963 b'ignored by ui.edit()'
1957 1964 )
1958 1965 extra_defaults.update(extra)
1959 1966 extra = extra_defaults
1960 1967
1961 1968 if action == b'diff':
1962 1969 suffix = b'.diff'
1963 1970 elif action:
1964 1971 suffix = b'.%s.hg.txt' % action
1965 1972 else:
1966 1973 suffix = extra[b'suffix']
1967 1974
1968 1975 rdir = None
1969 1976 if self.configbool(b'experimental', b'editortmpinhg'):
1970 1977 rdir = repopath
1971 1978 (fd, name) = pycompat.mkstemp(
1972 1979 prefix=b'hg-' + extra[b'prefix'] + b'-', suffix=suffix, dir=rdir
1973 1980 )
1974 1981 try:
1975 1982 with os.fdopen(fd, 'wb') as f:
1976 1983 f.write(util.tonativeeol(text))
1977 1984
1978 1985 environ = {b'HGUSER': user}
1979 1986 if b'transplant_source' in extra:
1980 1987 environ.update(
1981 1988 {b'HGREVISION': hex(extra[b'transplant_source'])}
1982 1989 )
1983 1990 for label in (b'intermediate-source', b'source', b'rebase_source'):
1984 1991 if label in extra:
1985 1992 environ.update({b'HGREVISION': extra[label]})
1986 1993 break
1987 1994 if editform:
1988 1995 environ.update({b'HGEDITFORM': editform})
1989 1996 if pending:
1990 1997 environ.update({b'HG_PENDING': pending})
1991 1998
1992 1999 editor = self.geteditor()
1993 2000
1994 2001 self.system(
1995 2002 b"%s \"%s\"" % (editor, name),
1996 2003 environ=environ,
1997 2004 onerr=error.CanceledError,
1998 2005 errprefix=_(b"edit failed"),
1999 2006 blockedtag=b'editor',
2000 2007 )
2001 2008
2002 2009 with open(name, 'rb') as f:
2003 2010 t = util.fromnativeeol(f.read())
2004 2011 finally:
2005 2012 os.unlink(name)
2006 2013
2007 2014 return t
2008 2015
2009 2016 def system(
2010 2017 self,
2011 2018 cmd: bytes,
2012 2019 environ=None,
2013 2020 cwd: Optional[bytes] = None,
2014 2021 onerr: Optional[Callable[[bytes], Exception]] = None,
2015 2022 errprefix: Optional[bytes] = None,
2016 2023 blockedtag: Optional[bytes] = None,
2017 2024 ) -> int:
2018 2025 """execute shell command with appropriate output stream. command
2019 2026 output will be redirected if fout is not stdout.
2020 2027
2021 2028 if command fails and onerr is None, return status, else raise onerr
2022 2029 object as exception.
2023 2030 """
2024 2031 if blockedtag is None:
2025 2032 # Long cmds tend to be because of an absolute path on cmd. Keep
2026 2033 # the tail end instead
2027 2034 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
2028 2035 blockedtag = b'unknown_system_' + cmdsuffix
2029 2036 out = self._fout
2030 2037 if any(s[1] for s in self._bufferstates):
2031 2038 out = self
2032 2039 with self.timeblockedsection(blockedtag):
2033 2040 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
2034 2041 if rc and onerr:
2035 2042 errmsg = b'%s %s' % (
2036 2043 procutil.shellsplit(cmd)[0],
2037 2044 procutil.explainexit(rc),
2038 2045 )
2039 2046 if errprefix:
2040 2047 errmsg = b'%s: %s' % (errprefix, errmsg)
2041 2048 raise onerr(errmsg)
2042 2049 return rc
2043 2050
2044 2051 def _runsystem(self, cmd: bytes, environ, cwd: Optional[bytes], out) -> int:
2045 2052 """actually execute the given shell command (can be overridden by
2046 2053 extensions like chg)"""
2047 2054 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
2048 2055
2049 2056 def traceback(self, exc=None, force: bool = False):
2050 2057 """print exception traceback if traceback printing enabled or forced.
2051 2058 only to call in exception handler. returns true if traceback
2052 2059 printed."""
2053 2060 if self.tracebackflag or force:
2054 2061 if exc is None:
2055 2062 exc = sys.exc_info()
2056 2063 cause = getattr(exc[1], 'cause', None)
2057 2064
2058 2065 if cause is not None:
2059 2066 causetb = traceback.format_tb(cause[2])
2060 2067 exctb = traceback.format_tb(exc[2])
2061 2068 exconly = traceback.format_exception_only(cause[0], cause[1])
2062 2069
2063 2070 # exclude frame where 'exc' was chained and rethrown from exctb
2064 2071 self.write_err(
2065 2072 b'Traceback (most recent call last):\n',
2066 2073 encoding.strtolocal(''.join(exctb[:-1])),
2067 2074 encoding.strtolocal(''.join(causetb)),
2068 2075 encoding.strtolocal(''.join(exconly)),
2069 2076 )
2070 2077 else:
2071 2078 output = traceback.format_exception(exc[0], exc[1], exc[2])
2072 2079 self.write_err(encoding.strtolocal(''.join(output)))
2073 2080 return self.tracebackflag or force
2074 2081
2075 2082 def geteditor(self):
2076 2083 '''return editor to use'''
2077 2084 if pycompat.sysplatform == b'plan9':
2078 2085 # vi is the MIPS instruction simulator on Plan 9. We
2079 2086 # instead default to E to plumb commit messages to
2080 2087 # avoid confusion.
2081 2088 editor = b'E'
2082 2089 elif pycompat.isdarwin:
2083 2090 # vi on darwin is POSIX compatible to a fault, and that includes
2084 2091 # exiting non-zero if you make any mistake when running an ex
2085 2092 # command. Proof: `vi -c ':unknown' -c ':qa'; echo $?` produces 1,
2086 2093 # while s/vi/vim/ doesn't.
2087 2094 editor = b'vim'
2088 2095 else:
2089 2096 editor = b'vi'
2090 2097 return encoding.environ.get(b"HGEDITOR") or self.config(
2091 2098 b"ui", b"editor", editor
2092 2099 )
2093 2100
2094 2101 @util.propertycache
2095 2102 def _progbar(self) -> Optional[progress.progbar]:
2096 2103 """setup the progbar singleton to the ui object"""
2097 2104 if (
2098 2105 self.quiet
2099 2106 or self.debugflag
2100 2107 or self.configbool(b'progress', b'disable')
2101 2108 or not progress.shouldprint(self)
2102 2109 ):
2103 2110 return None
2104 2111 return getprogbar(self)
2105 2112
2106 2113 def _progclear(self) -> None:
2107 2114 """clear progress bar output if any. use it before any output"""
2108 2115 if not haveprogbar(): # nothing loaded yet
2109 2116 return
2110 2117 if self._progbar is not None and self._progbar.printed:
2111 2118 self._progbar.clear()
2112 2119
2113 2120 def makeprogress(
2114 2121 self, topic: bytes, unit: bytes = b"", total: Optional[int] = None
2115 2122 ) -> scmutil.progress:
2116 2123 """Create a progress helper for the specified topic"""
2117 2124 if getattr(self._fmsgerr, 'structured', False):
2118 2125 # channel for machine-readable output with metadata, just send
2119 2126 # raw information
2120 2127 # TODO: consider porting some useful information (e.g. estimated
2121 2128 # time) from progbar. we might want to support update delay to
2122 2129 # reduce the cost of transferring progress messages.
2123 2130 def updatebar(topic, pos, item, unit, total):
2124 2131 self._fmsgerr.write(
2125 2132 None,
2126 2133 type=b'progress',
2127 2134 topic=topic,
2128 2135 pos=pos,
2129 2136 item=item,
2130 2137 unit=unit,
2131 2138 total=total,
2132 2139 )
2133 2140
2134 2141 elif self._progbar is not None:
2135 2142 updatebar = self._progbar.progress
2136 2143 else:
2137 2144
2138 2145 def updatebar(topic, pos, item, unit, total):
2139 2146 pass
2140 2147
2141 2148 return scmutil.progress(self, updatebar, topic, unit, total)
2142 2149
2143 2150 def getlogger(self, name):
2144 2151 """Returns a logger of the given name; or None if not registered"""
2145 2152 return self._loggers.get(name)
2146 2153
2147 2154 def setlogger(self, name, logger) -> None:
2148 2155 """Install logger which can be identified later by the given name
2149 2156
2150 2157 More than one loggers can be registered. Use extension or module
2151 2158 name to uniquely identify the logger instance.
2152 2159 """
2153 2160 self._loggers[name] = logger
2154 2161
2155 2162 def log(self, event, msgfmt, *msgargs, **opts) -> None:
2156 2163 """hook for logging facility extensions
2157 2164
2158 2165 event should be a readily-identifiable subsystem, which will
2159 2166 allow filtering.
2160 2167
2161 2168 msgfmt should be a newline-terminated format string to log, and
2162 2169 *msgargs are %-formatted into it.
2163 2170
2164 2171 **opts currently has no defined meanings.
2165 2172 """
2166 2173 if not self._loggers:
2167 2174 return
2168 2175 activeloggers = [l for l in self._loggers.values() if l.tracked(event)]
2169 2176 if not activeloggers:
2170 2177 return
2171 2178 msg = msgfmt % msgargs
2172 2179 opts = pycompat.byteskwargs(opts)
2173 2180 # guard against recursion from e.g. ui.debug()
2174 2181 registeredloggers = self._loggers
2175 2182 self._loggers = {}
2176 2183 try:
2177 2184 for logger in activeloggers:
2178 2185 logger.log(self, event, msg, opts)
2179 2186 finally:
2180 2187 self._loggers = registeredloggers
2181 2188
2182 2189 def label(self, msg: bytes, label: bytes) -> bytes:
2183 2190 """style msg based on supplied label
2184 2191
2185 2192 If some color mode is enabled, this will add the necessary control
2186 2193 characters to apply such color. In addition, 'debug' color mode adds
2187 2194 markup showing which label affects a piece of text.
2188 2195
2189 2196 ui.write(s, 'label') is equivalent to
2190 2197 ui.write(ui.label(s, 'label')).
2191 2198 """
2192 2199 if self._colormode is not None:
2193 2200 return color.colorlabel(self, msg, label)
2194 2201 return msg
2195 2202
2196 2203 def develwarn(
2197 2204 self, msg: bytes, stacklevel: int = 1, config: Optional[bytes] = None
2198 2205 ) -> None:
2199 2206 """issue a developer warning message
2200 2207
2201 2208 Use 'stacklevel' to report the offender some layers further up in the
2202 2209 stack.
2203 2210 """
2204 2211 if not self.configbool(b'devel', b'all-warnings'):
2205 2212 if config is None or not self.configbool(b'devel', config):
2206 2213 return
2207 2214 msg = b'devel-warn: ' + msg
2208 2215 stacklevel += 1 # get in develwarn
2209 2216 if self.tracebackflag:
2210 2217 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
2211 2218 self.log(
2212 2219 b'develwarn',
2213 2220 b'%s at:\n%s'
2214 2221 % (msg, b''.join(util.getstackframes(stacklevel))),
2215 2222 )
2216 2223 else:
2217 2224 curframe = inspect.currentframe()
2218 2225 calframe = inspect.getouterframes(curframe, 2)
2219 2226 fname, lineno, fmsg = calframe[stacklevel][1:4]
2220 2227 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
2221 2228 self.write_err(b'%s at: %s:%d (%s)\n' % (msg, fname, lineno, fmsg))
2222 2229 self.log(
2223 2230 b'develwarn', b'%s at: %s:%d (%s)\n', msg, fname, lineno, fmsg
2224 2231 )
2225 2232
2226 2233 # avoid cycles
2227 2234 del curframe
2228 2235 del calframe
2229 2236
2230 2237 def deprecwarn(
2231 2238 self,
2232 2239 msg: bytes,
2233 2240 version: bytes,
2234 2241 stacklevel: int = 2,
2235 2242 ) -> None:
2236 2243 """issue a deprecation warning
2237 2244
2238 2245 - msg: message explaining what is deprecated and how to upgrade,
2239 2246 - version: last version where the API will be supported,
2240 2247 """
2241 2248 if not (
2242 2249 self.configbool(b'devel', b'all-warnings')
2243 2250 or self.configbool(b'devel', b'deprec-warn')
2244 2251 ):
2245 2252 return
2246 2253 msg += (
2247 2254 b"\n(compatibility will be dropped after Mercurial-%s,"
2248 2255 b" update your code.)"
2249 2256 ) % version
2250 2257 self.develwarn(msg, stacklevel=stacklevel, config=b'deprec-warn')
2251 2258
2252 2259 def exportableenviron(self):
2253 2260 """The environment variables that are safe to export, e.g. through
2254 2261 hgweb.
2255 2262 """
2256 2263 return self._exportableenviron
2257 2264
2258 2265 @contextlib.contextmanager
2259 2266 def configoverride(self, overrides: _ConfigItems, source: bytes = b""):
2260 2267 """Context manager for temporary config overrides
2261 2268 `overrides` must be a dict of the following structure:
2262 2269 {(section, name) : value}"""
2263 2270 backups = {}
2264 2271 try:
2265 2272 for (section, name), value in overrides.items():
2266 2273 backups[(section, name)] = self.backupconfig(section, name)
2267 2274 self.setconfig(section, name, value, source)
2268 2275 yield
2269 2276 finally:
2270 2277 for __, backup in backups.items():
2271 2278 self.restoreconfig(backup)
2272 2279 # just restoring ui.quiet config to the previous value is not enough
2273 2280 # as it does not update ui.quiet class member
2274 2281 if (b'ui', b'quiet') in overrides:
2275 2282 self.fixconfig(section=b'ui')
2276 2283
2277 2284 def estimatememory(self) -> Optional[int]:
2278 2285 """Provide an estimate for the available system memory in Bytes.
2279 2286
2280 2287 This can be overriden via ui.available-memory. It returns None, if
2281 2288 no estimate can be computed.
2282 2289 """
2283 2290 value = self.config(b'ui', b'available-memory')
2284 2291 if value is not None:
2285 2292 try:
2286 2293 return util.sizetoint(value)
2287 2294 except error.ParseError:
2288 2295 raise error.ConfigError(
2289 2296 _(b"ui.available-memory value is invalid ('%s')") % value
2290 2297 )
2291 2298 return util._estimatememory()
2292 2299
2293 2300
2294 2301 # we instantiate one globally shared progress bar to avoid
2295 2302 # competing progress bars when multiple UI objects get created
2296 2303 _progresssingleton: Optional[progress.progbar] = None
2297 2304
2298 2305
2299 2306 def getprogbar(ui: ui) -> progress.progbar:
2300 2307 global _progresssingleton
2301 2308 if _progresssingleton is None:
2302 2309 # passing 'ui' object to the singleton is fishy,
2303 2310 # this is how the extension used to work but feel free to rework it.
2304 2311 _progresssingleton = progress.progbar(ui)
2305 2312 return _progresssingleton
2306 2313
2307 2314
2308 2315 def haveprogbar() -> bool:
2309 2316 return _progresssingleton is not None
2310 2317
2311 2318
2312 2319 def _selectmsgdests(ui: ui):
2313 2320 name = ui.config(b'ui', b'message-output')
2314 2321 if name == b'channel':
2315 2322 if ui.fmsg:
2316 2323 return ui.fmsg, ui.fmsg
2317 2324 else:
2318 2325 # fall back to ferr if channel isn't ready so that status/error
2319 2326 # messages can be printed
2320 2327 return ui.ferr, ui.ferr
2321 2328 if name == b'stdio':
2322 2329 return ui.fout, ui.ferr
2323 2330 if name == b'stderr':
2324 2331 return ui.ferr, ui.ferr
2325 2332 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2326 2333
2327 2334
2328 2335 def _writemsgwith(write, dest, *args: bytes, **opts: _MsgOpts) -> None:
2329 2336 """Write ui message with the given ui._write*() function
2330 2337
2331 2338 The specified message type is translated to 'ui.<type>' label if the dest
2332 2339 isn't a structured channel, so that the message will be colorized.
2333 2340 """
2334 2341 # TODO: maybe change 'type' to a mandatory option
2335 2342 if 'type' in opts and not getattr(dest, 'structured', False):
2336 2343 opts['label'] = opts.get('label', b'') + b' ui.%s' % opts.pop('type')
2337 2344 write(dest, *args, **opts)
@@ -1,669 +1,692 b''
1 1 //! Code for parsing default Mercurial config items.
2 2 use itertools::Itertools;
3 3 use serde::Deserialize;
4 4
5 5 use crate::{errors::HgError, exit_codes, FastHashMap};
6 6
7 7 /// Corresponds to the structure of `mercurial/configitems.toml`.
8 8 #[derive(Debug, Deserialize)]
9 9 pub struct ConfigItems {
10 10 items: Vec<DefaultConfigItem>,
11 11 templates: FastHashMap<String, Vec<TemplateItem>>,
12 12 #[serde(rename = "template-applications")]
13 13 template_applications: Vec<TemplateApplication>,
14 14 }
15 15
16 16 /// Corresponds to a config item declaration in `mercurial/configitems.toml`.
17 17 #[derive(Clone, Debug, PartialEq, Deserialize)]
18 18 #[serde(try_from = "RawDefaultConfigItem")]
19 19 pub struct DefaultConfigItem {
20 20 /// Section of the config the item is in (e.g. `[merge-tools]`)
21 21 section: String,
22 22 /// Name of the item (e.g. `meld.gui`)
23 23 name: String,
24 24 /// Default value (can be dynamic, see [`DefaultConfigItemType`])
25 25 default: Option<DefaultConfigItemType>,
26 26 /// If the config option is generic (e.g. `merge-tools.*`), defines
27 27 /// the priority of this item relative to other generic items.
28 28 /// If we're looking for <pattern>, then all generic items within the same
29 29 /// section will be sorted by order of priority, and the first regex match
30 30 /// against `name` is returned.
31 31 #[serde(default)]
32 32 priority: Option<isize>,
33 33 /// Aliases, if any. Each alias is a tuple of `(section, name)` for each
34 34 /// option that is aliased to this one.
35 35 #[serde(default)]
36 36 alias: Vec<(String, String)>,
37 37 /// Whether the config item is marked as experimental
38 38 #[serde(default)]
39 39 experimental: bool,
40 40 /// The (possibly empty) docstring for the item
41 41 #[serde(default)]
42 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 50 /// Corresponds to the raw (i.e. on disk) structure of config items. Used as
46 51 /// an intermediate step in deserialization.
47 52 #[derive(Clone, Debug, Deserialize)]
48 53 struct RawDefaultConfigItem {
49 54 section: String,
50 55 name: String,
51 56 default: Option<toml::Value>,
52 57 #[serde(rename = "default-type")]
53 58 default_type: Option<String>,
54 59 #[serde(default)]
55 60 priority: isize,
56 61 #[serde(default)]
57 62 generic: bool,
58 63 #[serde(default)]
59 64 alias: Vec<(String, String)>,
60 65 #[serde(default)]
61 66 experimental: bool,
62 67 #[serde(default)]
63 68 documentation: String,
69 #[serde(default)]
70 in_core_extension: Option<String>,
64 71 }
65 72
66 73 impl TryFrom<RawDefaultConfigItem> for DefaultConfigItem {
67 74 type Error = HgError;
68 75
69 76 fn try_from(value: RawDefaultConfigItem) -> Result<Self, Self::Error> {
70 77 Ok(Self {
71 78 section: value.section,
72 79 name: value.name,
73 80 default: raw_default_to_concrete(
74 81 value.default_type,
75 82 value.default,
76 83 )?,
77 84 priority: if value.generic {
78 85 Some(value.priority)
79 86 } else {
80 87 None
81 88 },
82 89 alias: value.alias,
83 90 experimental: value.experimental,
84 91 documentation: value.documentation,
92 in_core_extension: value.in_core_extension,
85 93 })
86 94 }
87 95 }
88 96
89 97 impl DefaultConfigItem {
90 98 fn is_generic(&self) -> bool {
91 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 111 impl<'a> TryFrom<&'a DefaultConfigItem> for Option<&'a str> {
96 112 type Error = HgError;
97 113
98 114 fn try_from(
99 115 value: &'a DefaultConfigItem,
100 116 ) -> Result<Option<&'a str>, Self::Error> {
101 117 match &value.default {
102 118 Some(default) => {
103 119 let err = HgError::abort(
104 120 format!(
105 121 "programming error: wrong query on config item '{}.{}'",
106 122 value.section,
107 123 value.name
108 124 ),
109 125 exit_codes::ABORT,
110 126 Some(format!(
111 127 "asked for '&str', type of default is '{}'",
112 128 default.type_str()
113 129 )),
114 130 );
115 131 match default {
116 132 DefaultConfigItemType::Primitive(toml::Value::String(
117 133 s,
118 134 )) => Ok(Some(s)),
119 135 _ => Err(err),
120 136 }
121 137 }
122 138 None => Ok(None),
123 139 }
124 140 }
125 141 }
126 142
127 143 impl TryFrom<&DefaultConfigItem> for Option<bool> {
128 144 type Error = HgError;
129 145
130 146 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
131 147 match &value.default {
132 148 Some(default) => {
133 149 let err = HgError::abort(
134 150 format!(
135 151 "programming error: wrong query on config item '{}.{}'",
136 152 value.section,
137 153 value.name
138 154 ),
139 155 exit_codes::ABORT,
140 156 Some(format!(
141 157 "asked for 'bool', type of default is '{}'",
142 158 default.type_str()
143 159 )),
144 160 );
145 161 match default {
146 162 DefaultConfigItemType::Primitive(
147 163 toml::Value::Boolean(b),
148 164 ) => Ok(Some(*b)),
149 165 _ => Err(err),
150 166 }
151 167 }
152 168 None => Ok(Some(false)),
153 169 }
154 170 }
155 171 }
156 172
157 173 impl TryFrom<&DefaultConfigItem> for Option<u32> {
158 174 type Error = HgError;
159 175
160 176 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
161 177 match &value.default {
162 178 Some(default) => {
163 179 let err = HgError::abort(
164 180 format!(
165 181 "programming error: wrong query on config item '{}.{}'",
166 182 value.section,
167 183 value.name
168 184 ),
169 185 exit_codes::ABORT,
170 186 Some(format!(
171 187 "asked for 'u32', type of default is '{}'",
172 188 default.type_str()
173 189 )),
174 190 );
175 191 match default {
176 192 DefaultConfigItemType::Primitive(
177 193 toml::Value::Integer(b),
178 194 ) => {
179 195 Ok(Some((*b).try_into().expect("TOML integer to u32")))
180 196 }
181 197 _ => Err(err),
182 198 }
183 199 }
184 200 None => Ok(None),
185 201 }
186 202 }
187 203 }
188 204
189 205 impl TryFrom<&DefaultConfigItem> for Option<u64> {
190 206 type Error = HgError;
191 207
192 208 fn try_from(value: &DefaultConfigItem) -> Result<Self, Self::Error> {
193 209 match &value.default {
194 210 Some(default) => {
195 211 let err = HgError::abort(
196 212 format!(
197 213 "programming error: wrong query on config item '{}.{}'",
198 214 value.section,
199 215 value.name
200 216 ),
201 217 exit_codes::ABORT,
202 218 Some(format!(
203 219 "asked for 'u64', type of default is '{}'",
204 220 default.type_str()
205 221 )),
206 222 );
207 223 match default {
208 224 DefaultConfigItemType::Primitive(
209 225 toml::Value::Integer(b),
210 226 ) => {
211 227 Ok(Some((*b).try_into().expect("TOML integer to u64")))
212 228 }
213 229 _ => Err(err),
214 230 }
215 231 }
216 232 None => Ok(None),
217 233 }
218 234 }
219 235 }
220 236
221 237 /// Allows abstracting over more complex default values than just primitives.
222 238 /// The former `configitems.py` contained some dynamic code that is encoded
223 239 /// in this enum.
224 240 #[derive(Debug, PartialEq, Clone, Deserialize)]
225 241 pub enum DefaultConfigItemType {
226 242 /// Some primitive type (string, integer, boolean)
227 243 Primitive(toml::Value),
228 244 /// A dynamic value that will be given by the code at runtime
229 245 Dynamic,
230 246 /// An lazily-returned array (possibly only relevant in the Python impl)
231 247 /// Example: `lambda: [b"zstd", b"zlib"]`
232 248 Lambda(Vec<String>),
233 249 /// For now, a special case for `web.encoding` that points to the
234 250 /// `encoding.encoding` module in the Python impl so that local encoding
235 251 /// is correctly resolved at runtime
236 252 LazyModule(String),
237 253 ListType,
238 254 }
239 255
240 256 impl DefaultConfigItemType {
241 257 pub fn type_str(&self) -> &str {
242 258 match self {
243 259 DefaultConfigItemType::Primitive(primitive) => {
244 260 primitive.type_str()
245 261 }
246 262 DefaultConfigItemType::Dynamic => "dynamic",
247 263 DefaultConfigItemType::Lambda(_) => "lambda",
248 264 DefaultConfigItemType::LazyModule(_) => "lazy_module",
249 265 DefaultConfigItemType::ListType => "list_type",
250 266 }
251 267 }
252 268 }
253 269
254 270 /// Most of the fields are shared with [`DefaultConfigItem`].
255 271 #[derive(Debug, Clone, Deserialize)]
256 272 #[serde(try_from = "RawTemplateItem")]
257 273 struct TemplateItem {
258 274 suffix: String,
259 275 default: Option<DefaultConfigItemType>,
260 276 priority: Option<isize>,
261 277 #[serde(default)]
262 278 alias: Vec<(String, String)>,
263 279 #[serde(default)]
264 280 experimental: bool,
265 281 #[serde(default)]
266 282 documentation: String,
267 283 }
268 284
269 285 /// Corresponds to the raw (i.e. on disk) representation of a template item.
270 286 /// Used as an intermediate step in deserialization.
271 287 #[derive(Clone, Debug, Deserialize)]
272 288 struct RawTemplateItem {
273 289 suffix: String,
274 290 default: Option<toml::Value>,
275 291 #[serde(rename = "default-type")]
276 292 default_type: Option<String>,
277 293 #[serde(default)]
278 294 priority: isize,
279 295 #[serde(default)]
280 296 generic: bool,
281 297 #[serde(default)]
282 298 alias: Vec<(String, String)>,
283 299 #[serde(default)]
284 300 experimental: bool,
285 301 #[serde(default)]
286 302 documentation: String,
287 303 }
288 304
289 305 impl TemplateItem {
290 306 fn into_default_item(
291 307 self,
292 308 application: TemplateApplication,
293 309 ) -> DefaultConfigItem {
294 310 DefaultConfigItem {
295 311 section: application.section,
296 312 name: application
297 313 .prefix
298 314 .map(|prefix| format!("{}.{}", prefix, self.suffix))
299 315 .unwrap_or(self.suffix),
300 316 default: self.default,
301 317 priority: self.priority,
302 318 alias: self.alias,
303 319 experimental: self.experimental,
304 320 documentation: self.documentation,
321 in_core_extension: None,
305 322 }
306 323 }
307 324 }
308 325
309 326 impl TryFrom<RawTemplateItem> for TemplateItem {
310 327 type Error = HgError;
311 328
312 329 fn try_from(value: RawTemplateItem) -> Result<Self, Self::Error> {
313 330 Ok(Self {
314 331 suffix: value.suffix,
315 332 default: raw_default_to_concrete(
316 333 value.default_type,
317 334 value.default,
318 335 )?,
319 336 priority: if value.generic {
320 337 Some(value.priority)
321 338 } else {
322 339 None
323 340 },
324 341 alias: value.alias,
325 342 experimental: value.experimental,
326 343 documentation: value.documentation,
327 344 })
328 345 }
329 346 }
330 347
331 348 /// Transforms the on-disk string-based representation of complex default types
332 349 /// to the concrete [`DefaultconfigItemType`].
333 350 fn raw_default_to_concrete(
334 351 default_type: Option<String>,
335 352 default: Option<toml::Value>,
336 353 ) -> Result<Option<DefaultConfigItemType>, HgError> {
337 354 Ok(match default_type.as_deref() {
338 355 None => default.as_ref().map(|default| {
339 356 DefaultConfigItemType::Primitive(default.to_owned())
340 357 }),
341 358 Some("dynamic") => Some(DefaultConfigItemType::Dynamic),
342 359 Some("list_type") => Some(DefaultConfigItemType::ListType),
343 360 Some("lambda") => match &default {
344 361 Some(default) => Some(DefaultConfigItemType::Lambda(
345 362 default.to_owned().try_into().map_err(|e| {
346 363 HgError::abort(
347 364 e.to_string(),
348 365 exit_codes::ABORT,
349 366 Some("Check 'mercurial/configitems.toml'".into()),
350 367 )
351 368 })?,
352 369 )),
353 370 None => {
354 371 return Err(HgError::abort(
355 372 "lambda defined with no return value".to_string(),
356 373 exit_codes::ABORT,
357 374 Some("Check 'mercurial/configitems.toml'".into()),
358 375 ))
359 376 }
360 377 },
361 378 Some("lazy_module") => match &default {
362 379 Some(default) => {
363 380 Some(DefaultConfigItemType::LazyModule(match default {
364 381 toml::Value::String(module) => module.to_owned(),
365 382 _ => {
366 383 return Err(HgError::abort(
367 384 "lazy_module module name should be a string"
368 385 .to_string(),
369 386 exit_codes::ABORT,
370 387 Some("Check 'mercurial/configitems.toml'".into()),
371 388 ))
372 389 }
373 390 }))
374 391 }
375 392 None => {
376 393 return Err(HgError::abort(
377 394 "lazy_module should have a default value".to_string(),
378 395 exit_codes::ABORT,
379 396 Some("Check 'mercurial/configitems.toml'".into()),
380 397 ))
381 398 }
382 399 },
383 400 Some(invalid) => {
384 401 return Err(HgError::abort(
385 402 format!("invalid default_type '{}'", invalid),
386 403 exit_codes::ABORT,
387 404 Some("Check 'mercurial/configitems.toml'".into()),
388 405 ))
389 406 }
390 407 })
391 408 }
392 409
393 410 #[derive(Debug, Clone, Deserialize)]
394 411 struct TemplateApplication {
395 412 template: String,
396 413 section: String,
397 414 #[serde(default)]
398 415 prefix: Option<String>,
399 416 }
400 417
401 418 /// Represents the (dynamic) set of default core Mercurial config items from
402 419 /// `mercurial/configitems.toml`.
403 420 #[derive(Clone, Debug, Default)]
404 421 pub struct DefaultConfig {
405 422 /// Mapping of section -> (mapping of name -> item)
406 423 items: FastHashMap<String, FastHashMap<String, DefaultConfigItem>>,
407 424 }
408 425
409 426 impl DefaultConfig {
410 427 pub fn empty() -> DefaultConfig {
411 428 Self {
412 429 items: Default::default(),
413 430 }
414 431 }
415 432
416 433 /// Returns `Self`, given the contents of `mercurial/configitems.toml`
417 434 #[logging_timer::time("trace")]
418 435 pub fn from_contents(contents: &str) -> Result<Self, HgError> {
419 436 let mut from_file: ConfigItems =
420 437 toml::from_str(contents).map_err(|e| {
421 438 HgError::abort(
422 439 e.to_string(),
423 440 exit_codes::ABORT,
424 441 Some("Check 'mercurial/configitems.toml'".into()),
425 442 )
426 443 })?;
427 444
428 445 let mut flat_items = from_file.items;
429 446
430 447 for application in from_file.template_applications.drain(..) {
431 448 match from_file.templates.get(&application.template) {
432 449 None => return Err(
433 450 HgError::abort(
434 451 format!(
435 452 "template application refers to undefined template '{}'",
436 453 application.template
437 454 ),
438 455 exit_codes::ABORT,
439 456 Some("Check 'mercurial/configitems.toml'".into())
440 457 )
441 458 ),
442 459 Some(template_items) => {
443 460 for template_item in template_items {
444 461 flat_items.push(
445 462 template_item
446 463 .clone()
447 464 .into_default_item(application.clone()),
448 465 )
449 466 }
450 467 }
451 468 };
452 469 }
453 470
454 471 let items = flat_items.into_iter().fold(
455 472 FastHashMap::default(),
456 473 |mut acc, item| {
457 474 acc.entry(item.section.to_owned())
458 475 .or_insert_with(|| {
459 476 let mut section = FastHashMap::default();
460 477 section.insert(item.name.to_owned(), item.to_owned());
461 478 section
462 479 })
463 480 .insert(item.name.to_owned(), item);
464 481 acc
465 482 },
466 483 );
467 484
468 485 Ok(Self { items })
469 486 }
470 487
471 488 /// Return the default config item that matches `section` and `item`.
472 489 pub fn get(
473 490 &self,
474 491 section: &[u8],
475 492 item: &[u8],
476 493 ) -> Option<&DefaultConfigItem> {
477 494 // Core items must be valid UTF-8
478 495 let section = String::from_utf8_lossy(section);
479 496 let section_map = self.items.get(section.as_ref())?;
480 497 let item_name_lossy = String::from_utf8_lossy(item);
481 498 match section_map.get(item_name_lossy.as_ref()) {
482 499 Some(item) => Some(item),
483 500 None => {
484 501 for generic_item in section_map
485 502 .values()
486 503 .filter(|item| item.is_generic())
487 504 .sorted_by_key(|item| match item.priority {
488 505 Some(priority) => (priority, &item.name),
489 506 _ => unreachable!(),
490 507 })
491 508 {
492 509 if regex::bytes::Regex::new(&generic_item.name)
493 510 .expect("invalid regex in configitems")
494 511 .is_match(item)
495 512 {
496 513 return Some(generic_item);
497 514 }
498 515 }
499 516 None
500 517 }
501 518 }
502 519 }
503 520 }
504 521
505 522 #[cfg(test)]
506 523 mod tests {
507 524 use crate::config::config_items::{
508 525 DefaultConfigItem, DefaultConfigItemType,
509 526 };
510 527
511 528 use super::DefaultConfig;
512 529
513 530 #[test]
514 531 fn test_config_read() {
515 532 let contents = r#"
516 533 [[items]]
517 534 section = "alias"
518 535 name = "abcd.*"
519 536 default = 3
520 537 generic = true
521 538 priority = -1
522 539
523 540 [[items]]
524 541 section = "alias"
525 542 name = ".*"
526 543 default-type = "dynamic"
527 544 generic = true
528 545
529 546 [[items]]
530 547 section = "cmdserver"
531 548 name = "track-log"
532 549 default-type = "lambda"
533 550 default = [ "chgserver", "cmdserver", "repocache",]
534 551
535 552 [[items]]
536 553 section = "chgserver"
537 554 name = "idletimeout"
538 555 default = 3600
539 556
540 557 [[items]]
541 558 section = "cmdserver"
542 559 name = "message-encodings"
543 560 default-type = "list_type"
544 561
545 562 [[items]]
546 563 section = "web"
547 564 name = "encoding"
548 565 default-type = "lazy_module"
549 566 default = "encoding.encoding"
550 567
551 568 [[items]]
552 569 section = "command-templates"
553 570 name = "graphnode"
554 571 alias = [["ui", "graphnodetemplate"]]
555 572 documentation = """This is a docstring.
556 573 This is another line \
557 574 but this is not."""
558 575
559 576 [[items]]
560 577 section = "censor"
561 578 name = "policy"
562 579 default = "abort"
563 580 experimental = true
564 581
565 582 [[template-applications]]
566 583 template = "diff-options"
567 584 section = "commands"
568 585 prefix = "revert.interactive"
569 586
570 587 [[template-applications]]
571 588 template = "diff-options"
572 589 section = "diff"
573 590
574 591 [templates]
575 592 [[templates.diff-options]]
576 593 suffix = "nodates"
577 594 default = false
578 595
579 596 [[templates.diff-options]]
580 597 suffix = "showfunc"
581 598 default = false
582 599
583 600 [[templates.diff-options]]
584 601 suffix = "unified"
585 602 "#;
586 603 let res = DefaultConfig::from_contents(contents);
587 604 let config = match res {
588 605 Ok(config) => config,
589 606 Err(e) => panic!("{}", e),
590 607 };
591 608 let expected = DefaultConfigItem {
592 609 section: "censor".into(),
593 610 name: "policy".into(),
594 611 default: Some(DefaultConfigItemType::Primitive("abort".into())),
595 612 priority: None,
596 613 alias: vec![],
597 614 experimental: true,
598 615 documentation: "".into(),
616 in_core_extension: None,
599 617 };
600 618 assert_eq!(config.get(b"censor", b"policy"), Some(&expected));
601 619
602 620 // Test generic priority. The `.*` pattern is wider than `abcd.*`, but
603 621 // `abcd.*` has priority, so it should match first.
604 622 let expected = DefaultConfigItem {
605 623 section: "alias".into(),
606 624 name: "abcd.*".into(),
607 625 default: Some(DefaultConfigItemType::Primitive(3.into())),
608 626 priority: Some(-1),
609 627 alias: vec![],
610 628 experimental: false,
611 629 documentation: "".into(),
630 in_core_extension: None,
612 631 };
613 632 assert_eq!(config.get(b"alias", b"abcdsomething"), Some(&expected));
614 633
615 634 //... but if it doesn't, we should fallback to `.*`
616 635 let expected = DefaultConfigItem {
617 636 section: "alias".into(),
618 637 name: ".*".into(),
619 638 default: Some(DefaultConfigItemType::Dynamic),
620 639 priority: Some(0),
621 640 alias: vec![],
622 641 experimental: false,
623 642 documentation: "".into(),
643 in_core_extension: None,
624 644 };
625 645 assert_eq!(config.get(b"alias", b"something"), Some(&expected));
626 646
627 647 let expected = DefaultConfigItem {
628 648 section: "chgserver".into(),
629 649 name: "idletimeout".into(),
630 650 default: Some(DefaultConfigItemType::Primitive(3600.into())),
631 651 priority: None,
632 652 alias: vec![],
633 653 experimental: false,
634 654 documentation: "".into(),
655 in_core_extension: None,
635 656 };
636 657 assert_eq!(config.get(b"chgserver", b"idletimeout"), Some(&expected));
637 658
638 659 let expected = DefaultConfigItem {
639 660 section: "cmdserver".into(),
640 661 name: "track-log".into(),
641 662 default: Some(DefaultConfigItemType::Lambda(vec![
642 663 "chgserver".into(),
643 664 "cmdserver".into(),
644 665 "repocache".into(),
645 666 ])),
646 667 priority: None,
647 668 alias: vec![],
648 669 experimental: false,
649 670 documentation: "".into(),
671 in_core_extension: None,
650 672 };
651 673 assert_eq!(config.get(b"cmdserver", b"track-log"), Some(&expected));
652 674
653 675 let expected = DefaultConfigItem {
654 676 section: "command-templates".into(),
655 677 name: "graphnode".into(),
656 678 default: None,
657 679 priority: None,
658 680 alias: vec![("ui".into(), "graphnodetemplate".into())],
659 681 experimental: false,
660 682 documentation:
661 683 "This is a docstring.\nThis is another line but this is not."
662 684 .into(),
685 in_core_extension: None,
663 686 };
664 687 assert_eq!(
665 688 config.get(b"command-templates", b"graphnode"),
666 689 Some(&expected)
667 690 );
668 691 }
669 692 }
@@ -1,728 +1,738 b''
1 1 // config.rs
2 2 //
3 3 // Copyright 2020
4 4 // Valentin Gatien-Baron,
5 5 // Raphaël Gomès <rgomes@octobus.net>
6 6 //
7 7 // This software may be used and distributed according to the terms of the
8 8 // GNU General Public License version 2 or any later version.
9 9
10 10 //! Mercurial config parsing and interfaces.
11 11
12 12 pub mod config_items;
13 13 mod layer;
14 14 mod plain_info;
15 15 mod values;
16 16 pub use layer::{ConfigError, ConfigOrigin, ConfigParseError};
17 17 use lazy_static::lazy_static;
18 18 pub use plain_info::PlainInfo;
19 19
20 20 use self::config_items::DefaultConfig;
21 21 use self::config_items::DefaultConfigItem;
22 22 use self::layer::ConfigLayer;
23 23 use self::layer::ConfigValue;
24 24 use crate::errors::HgError;
25 25 use crate::errors::{HgResultExt, IoResultExt};
26 26 use crate::utils::files::get_bytes_from_os_str;
27 27 use format_bytes::{write_bytes, DisplayBytes};
28 28 use std::collections::HashSet;
29 29 use std::env;
30 30 use std::fmt;
31 31 use std::path::{Path, PathBuf};
32 32 use std::str;
33 33
34 34 lazy_static! {
35 35 static ref DEFAULT_CONFIG: Result<DefaultConfig, HgError> = {
36 36 DefaultConfig::from_contents(include_str!(
37 37 "../../../../mercurial/configitems.toml"
38 38 ))
39 39 };
40 40 }
41 41
42 42 /// Holds the config values for the current repository
43 43 /// TODO update this docstring once we support more sources
44 44 #[derive(Clone)]
45 45 pub struct Config {
46 46 layers: Vec<layer::ConfigLayer>,
47 47 plain: PlainInfo,
48 48 }
49 49
50 50 impl DisplayBytes for Config {
51 51 fn display_bytes(
52 52 &self,
53 53 out: &mut dyn std::io::Write,
54 54 ) -> std::io::Result<()> {
55 55 for (index, layer) in self.layers.iter().rev().enumerate() {
56 56 write_bytes!(
57 57 out,
58 58 b"==== Layer {} (trusted: {}) ====\n{}",
59 59 index,
60 60 if layer.trusted {
61 61 &b"yes"[..]
62 62 } else {
63 63 &b"no"[..]
64 64 },
65 65 layer
66 66 )?;
67 67 }
68 68 Ok(())
69 69 }
70 70 }
71 71
72 72 pub enum ConfigSource {
73 73 /// Absolute path to a config file
74 74 AbsPath(PathBuf),
75 75 /// Already parsed (from the CLI, env, Python resources, etc.)
76 76 Parsed(layer::ConfigLayer),
77 77 }
78 78
79 79 #[derive(Debug)]
80 80 pub struct ConfigValueParseErrorDetails {
81 81 pub origin: ConfigOrigin,
82 82 pub line: Option<usize>,
83 83 pub section: Vec<u8>,
84 84 pub item: Vec<u8>,
85 85 pub value: Vec<u8>,
86 86 pub expected_type: &'static str,
87 87 }
88 88
89 89 // boxed to avoid very large Result types
90 90 pub type ConfigValueParseError = Box<ConfigValueParseErrorDetails>;
91 91
92 92 impl fmt::Display for ConfigValueParseError {
93 93 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
94 94 // TODO: add origin and line number information, here and in
95 95 // corresponding python code
96 96 write!(
97 97 f,
98 98 "config error: {}.{} is not a {} ('{}')",
99 99 String::from_utf8_lossy(&self.section),
100 100 String::from_utf8_lossy(&self.item),
101 101 self.expected_type,
102 102 String::from_utf8_lossy(&self.value)
103 103 )
104 104 }
105 105 }
106 106
107 107 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
108 108 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
109 109 // duplication with [_applyconfig] in [ui.py],
110 110 if !plain.is_plain() {
111 111 return false;
112 112 }
113 113 if section == b"alias" {
114 114 return plain.plainalias();
115 115 }
116 116 if section == b"revsetalias" {
117 117 return plain.plainrevsetalias();
118 118 }
119 119 if section == b"templatealias" {
120 120 return plain.plaintemplatealias();
121 121 }
122 122 if section == b"ui" {
123 123 let to_delete: &[&[u8]] = &[
124 124 b"debug",
125 125 b"fallbackencoding",
126 126 b"quiet",
127 127 b"slash",
128 128 b"logtemplate",
129 129 b"message-output",
130 130 b"statuscopies",
131 131 b"style",
132 132 b"traceback",
133 133 b"verbose",
134 134 ];
135 135 return to_delete.contains(&item);
136 136 }
137 137 let sections_to_delete: &[&[u8]] =
138 138 &[b"defaults", b"commands", b"command-templates"];
139 139 sections_to_delete.contains(&section)
140 140 }
141 141
142 142 impl Config {
143 143 /// The configuration to use when printing configuration-loading errors
144 144 pub fn empty() -> Self {
145 145 Self {
146 146 layers: Vec::new(),
147 147 plain: PlainInfo::empty(),
148 148 }
149 149 }
150 150
151 151 /// Load system and user configuration from various files.
152 152 ///
153 153 /// This is also affected by some environment variables.
154 154 pub fn load_non_repo() -> Result<Self, ConfigError> {
155 155 let mut config = Self::empty();
156 156 let opt_rc_path = env::var_os("HGRCPATH");
157 157 // HGRCPATH replaces system config
158 158 if opt_rc_path.is_none() {
159 159 config.add_system_config()?
160 160 }
161 161
162 162 config.add_for_environment_variable("EDITOR", b"ui", b"editor");
163 163 config.add_for_environment_variable("VISUAL", b"ui", b"editor");
164 164 config.add_for_environment_variable("PAGER", b"pager", b"pager");
165 165
166 166 // These are set by `run-tests.py --rhg` to enable fallback for the
167 167 // entire test suite. Alternatives would be setting configuration
168 168 // through `$HGRCPATH` but some tests override that, or changing the
169 169 // `hg` shell alias to include `--config` but that disrupts tests that
170 170 // print command lines and check expected output.
171 171 config.add_for_environment_variable(
172 172 "RHG_ON_UNSUPPORTED",
173 173 b"rhg",
174 174 b"on-unsupported",
175 175 );
176 176 config.add_for_environment_variable(
177 177 "RHG_FALLBACK_EXECUTABLE",
178 178 b"rhg",
179 179 b"fallback-executable",
180 180 );
181 181
182 182 // HGRCPATH replaces user config
183 183 if opt_rc_path.is_none() {
184 184 config.add_user_config()?
185 185 }
186 186 if let Some(rc_path) = &opt_rc_path {
187 187 for path in env::split_paths(rc_path) {
188 188 if !path.as_os_str().is_empty() {
189 189 if path.is_dir() {
190 190 config.add_trusted_dir(&path)?
191 191 } else {
192 192 config.add_trusted_file(&path)?
193 193 }
194 194 }
195 195 }
196 196 }
197 197 Ok(config)
198 198 }
199 199
200 200 pub fn load_cli_args(
201 201 &mut self,
202 202 cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
203 203 color_arg: Option<Vec<u8>>,
204 204 ) -> Result<(), ConfigError> {
205 205 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
206 206 self.layers.push(layer)
207 207 }
208 208 if let Some(arg) = color_arg {
209 209 let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
210 210 layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
211 211 self.layers.push(layer)
212 212 }
213 213 Ok(())
214 214 }
215 215
216 216 fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
217 217 if let Some(entries) = std::fs::read_dir(path)
218 218 .when_reading_file(path)
219 219 .io_not_found_as_none()?
220 220 {
221 221 let mut file_paths = entries
222 222 .map(|result| {
223 223 result.when_reading_file(path).map(|entry| entry.path())
224 224 })
225 225 .collect::<Result<Vec<_>, _>>()?;
226 226 file_paths.sort();
227 227 for file_path in &file_paths {
228 228 if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
229 229 self.add_trusted_file(file_path)?
230 230 }
231 231 }
232 232 }
233 233 Ok(())
234 234 }
235 235
236 236 fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
237 237 if let Some(data) = std::fs::read(path)
238 238 .when_reading_file(path)
239 239 .io_not_found_as_none()?
240 240 {
241 241 self.layers.extend(ConfigLayer::parse(path, &data)?)
242 242 }
243 243 Ok(())
244 244 }
245 245
246 246 fn add_for_environment_variable(
247 247 &mut self,
248 248 var: &str,
249 249 section: &[u8],
250 250 key: &[u8],
251 251 ) {
252 252 if let Some(value) = env::var_os(var) {
253 253 let origin = layer::ConfigOrigin::Environment(var.into());
254 254 let mut layer = ConfigLayer::new(origin);
255 255 layer.add(
256 256 section.to_owned(),
257 257 key.to_owned(),
258 258 get_bytes_from_os_str(value),
259 259 None,
260 260 );
261 261 self.layers.push(layer)
262 262 }
263 263 }
264 264
265 265 #[cfg(unix)] // TODO: other platforms
266 266 fn add_system_config(&mut self) -> Result<(), ConfigError> {
267 267 let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
268 268 let etc = prefix.join("etc").join("mercurial");
269 269 self.add_trusted_file(&etc.join("hgrc"))?;
270 270 self.add_trusted_dir(&etc.join("hgrc.d"))
271 271 };
272 272 let root = Path::new("/");
273 273 // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
274 274 // instead? TODO: can this be a relative path?
275 275 let hg = crate::utils::current_exe()?;
276 276 // TODO: this order (per-installation then per-system) matches
277 277 // `systemrcpath()` in `mercurial/scmposix.py`, but
278 278 // `mercurial/helptext/config.txt` suggests it should be reversed
279 279 if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
280 280 if installation_prefix != root {
281 281 add_for_prefix(installation_prefix)?
282 282 }
283 283 }
284 284 add_for_prefix(root)?;
285 285 Ok(())
286 286 }
287 287
288 288 #[cfg(unix)] // TODO: other plateforms
289 289 fn add_user_config(&mut self) -> Result<(), ConfigError> {
290 290 let opt_home = home::home_dir();
291 291 if let Some(home) = &opt_home {
292 292 self.add_trusted_file(&home.join(".hgrc"))?
293 293 }
294 294 let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
295 295 if !darwin {
296 296 if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
297 297 .map(PathBuf::from)
298 298 .or_else(|| opt_home.map(|home| home.join(".config")))
299 299 {
300 300 self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
301 301 }
302 302 }
303 303 Ok(())
304 304 }
305 305
306 306 /// Loads in order, which means that the precedence is the same
307 307 /// as the order of `sources`.
308 308 pub fn load_from_explicit_sources(
309 309 sources: Vec<ConfigSource>,
310 310 ) -> Result<Self, ConfigError> {
311 311 let mut layers = vec![];
312 312
313 313 for source in sources.into_iter() {
314 314 match source {
315 315 ConfigSource::Parsed(c) => layers.push(c),
316 316 ConfigSource::AbsPath(c) => {
317 317 // TODO check if it should be trusted
318 318 // mercurial/ui.py:427
319 319 let data = match std::fs::read(&c) {
320 320 Err(_) => continue, // same as the python code
321 321 Ok(data) => data,
322 322 };
323 323 layers.extend(ConfigLayer::parse(&c, &data)?)
324 324 }
325 325 }
326 326 }
327 327
328 328 Ok(Config {
329 329 layers,
330 330 plain: PlainInfo::empty(),
331 331 })
332 332 }
333 333
334 334 /// Loads the per-repository config into a new `Config` which is combined
335 335 /// with `self`.
336 336 pub(crate) fn combine_with_repo(
337 337 &self,
338 338 repo_config_files: &[PathBuf],
339 339 ) -> Result<Self, ConfigError> {
340 340 let (cli_layers, other_layers) = self
341 341 .layers
342 342 .iter()
343 343 .cloned()
344 344 .partition(ConfigLayer::is_from_command_line);
345 345
346 346 let mut repo_config = Self {
347 347 layers: other_layers,
348 348 plain: PlainInfo::empty(),
349 349 };
350 350 for path in repo_config_files {
351 351 // TODO: check if this file should be trusted:
352 352 // `mercurial/ui.py:427`
353 353 repo_config.add_trusted_file(path)?;
354 354 }
355 355 repo_config.layers.extend(cli_layers);
356 356 Ok(repo_config)
357 357 }
358 358
359 359 pub fn apply_plain(&mut self, plain: PlainInfo) {
360 360 self.plain = plain;
361 361 }
362 362
363 363 /// Returns the default value for the given config item, if any.
364 364 pub fn get_default(
365 365 &self,
366 366 section: &[u8],
367 367 item: &[u8],
368 368 ) -> Result<Option<&DefaultConfigItem>, HgError> {
369 369 let default_config = DEFAULT_CONFIG.as_ref().map_err(|e| {
370 370 HgError::abort(
371 371 e.to_string(),
372 372 crate::exit_codes::ABORT,
373 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 389 fn get_parse<'config, T: 'config>(
380 390 &'config self,
381 391 section: &[u8],
382 392 item: &[u8],
383 393 expected_type: &'static str,
384 394 parse: impl Fn(&'config [u8]) -> Option<T>,
385 395 fallback_to_default: bool,
386 396 ) -> Result<Option<T>, HgError>
387 397 where
388 398 Option<T>: TryFrom<&'config DefaultConfigItem, Error = HgError>,
389 399 {
390 400 match self.get_inner(section, item) {
391 401 Some((layer, v)) => match parse(&v.bytes) {
392 402 Some(b) => Ok(Some(b)),
393 403 None => Err(Box::new(ConfigValueParseErrorDetails {
394 404 origin: layer.origin.to_owned(),
395 405 line: v.line,
396 406 value: v.bytes.to_owned(),
397 407 section: section.to_owned(),
398 408 item: item.to_owned(),
399 409 expected_type,
400 410 })
401 411 .into()),
402 412 },
403 413 None => {
404 414 if !fallback_to_default {
405 415 return Ok(None);
406 416 }
407 417 match self.get_default(section, item)? {
408 418 Some(default) => Ok(default.try_into()?),
409 419 None => Ok(None),
410 420 }
411 421 }
412 422 }
413 423 }
414 424
415 425 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
416 426 /// Otherwise, returns an `Ok(value)` if found, or `None`.
417 427 pub fn get_str(
418 428 &self,
419 429 section: &[u8],
420 430 item: &[u8],
421 431 ) -> Result<Option<&str>, HgError> {
422 432 self.get_parse(
423 433 section,
424 434 item,
425 435 "ASCII or UTF-8 string",
426 436 |value| str::from_utf8(value).ok(),
427 437 true,
428 438 )
429 439 }
430 440
431 441 /// Same as `get_str`, but doesn't fall back to the default `configitem`
432 442 /// if not defined in the user config.
433 443 pub fn get_str_no_default(
434 444 &self,
435 445 section: &[u8],
436 446 item: &[u8],
437 447 ) -> Result<Option<&str>, HgError> {
438 448 self.get_parse(
439 449 section,
440 450 item,
441 451 "ASCII or UTF-8 string",
442 452 |value| str::from_utf8(value).ok(),
443 453 false,
444 454 )
445 455 }
446 456
447 457 /// Returns an `Err` if the first value found is not a valid unsigned
448 458 /// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
449 459 pub fn get_u32(
450 460 &self,
451 461 section: &[u8],
452 462 item: &[u8],
453 463 ) -> Result<Option<u32>, HgError> {
454 464 self.get_parse(
455 465 section,
456 466 item,
457 467 "valid integer",
458 468 |value| str::from_utf8(value).ok()?.parse().ok(),
459 469 true,
460 470 )
461 471 }
462 472
463 473 /// Returns an `Err` if the first value found is not a valid file size
464 474 /// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
465 475 /// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
466 476 pub fn get_byte_size(
467 477 &self,
468 478 section: &[u8],
469 479 item: &[u8],
470 480 ) -> Result<Option<u64>, HgError> {
471 481 self.get_parse(
472 482 section,
473 483 item,
474 484 "byte quantity",
475 485 values::parse_byte_size,
476 486 true,
477 487 )
478 488 }
479 489
480 490 /// Returns an `Err` if the first value found is not a valid boolean.
481 491 /// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
482 492 /// found, or `None`.
483 493 pub fn get_option(
484 494 &self,
485 495 section: &[u8],
486 496 item: &[u8],
487 497 ) -> Result<Option<bool>, HgError> {
488 498 self.get_parse(section, item, "boolean", values::parse_bool, true)
489 499 }
490 500
491 501 /// Same as `get_option`, but doesn't fall back to the default `configitem`
492 502 /// if not defined in the user config.
493 503 pub fn get_option_no_default(
494 504 &self,
495 505 section: &[u8],
496 506 item: &[u8],
497 507 ) -> Result<Option<bool>, HgError> {
498 508 self.get_parse(section, item, "boolean", values::parse_bool, false)
499 509 }
500 510
501 511 /// Returns the corresponding boolean in the config. Returns `Ok(false)`
502 512 /// if the value is not found, an `Err` if it's not a valid boolean.
503 513 pub fn get_bool(
504 514 &self,
505 515 section: &[u8],
506 516 item: &[u8],
507 517 ) -> Result<bool, HgError> {
508 518 Ok(self.get_option(section, item)?.unwrap_or(false))
509 519 }
510 520
511 521 /// Same as `get_bool`, but doesn't fall back to the default `configitem`
512 522 /// if not defined in the user config.
513 523 pub fn get_bool_no_default(
514 524 &self,
515 525 section: &[u8],
516 526 item: &[u8],
517 527 ) -> Result<bool, HgError> {
518 528 Ok(self.get_option_no_default(section, item)?.unwrap_or(false))
519 529 }
520 530
521 531 /// Returns `true` if the extension is enabled, `false` otherwise
522 532 pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
523 533 let value = self.get(b"extensions", extension);
524 534 match value {
525 535 Some(c) => !c.starts_with(b"!"),
526 536 None => false,
527 537 }
528 538 }
529 539
530 540 /// If there is an `item` value in `section`, parse and return a list of
531 541 /// byte strings.
532 542 pub fn get_list(
533 543 &self,
534 544 section: &[u8],
535 545 item: &[u8],
536 546 ) -> Option<Vec<Vec<u8>>> {
537 547 self.get(section, item).map(values::parse_list)
538 548 }
539 549
540 550 /// Returns the raw value bytes of the first one found, or `None`.
541 551 pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
542 552 self.get_inner(section, item)
543 553 .map(|(_, value)| value.bytes.as_ref())
544 554 }
545 555
546 556 /// Returns the raw value bytes of the first one found, or `None`.
547 557 pub fn get_with_origin(
548 558 &self,
549 559 section: &[u8],
550 560 item: &[u8],
551 561 ) -> Option<(&[u8], &ConfigOrigin)> {
552 562 self.get_inner(section, item)
553 563 .map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
554 564 }
555 565
556 566 /// Returns the layer and the value of the first one found, or `None`.
557 567 fn get_inner(
558 568 &self,
559 569 section: &[u8],
560 570 item: &[u8],
561 571 ) -> Option<(&ConfigLayer, &ConfigValue)> {
562 572 // Filter out the config items that are hidden by [PLAIN].
563 573 // This differs from python hg where we delete them from the config.
564 574 let should_ignore = should_ignore(&self.plain, section, item);
565 575 for layer in self.layers.iter().rev() {
566 576 if !layer.trusted {
567 577 continue;
568 578 }
569 579 //The [PLAIN] config should not affect the defaults.
570 580 //
571 581 // However, PLAIN should also affect the "tweaked" defaults (unless
572 582 // "tweakdefault" is part of "HGPLAINEXCEPT").
573 583 //
574 584 // In practice the tweak-default layer is only added when it is
575 585 // relevant, so we can safely always take it into
576 586 // account here.
577 587 if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
578 588 {
579 589 continue;
580 590 }
581 591 if let Some(v) = layer.get(section, item) {
582 592 return Some((layer, v));
583 593 }
584 594 }
585 595 None
586 596 }
587 597
588 598 /// Return all keys defined for the given section
589 599 pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
590 600 self.layers
591 601 .iter()
592 602 .flat_map(|layer| layer.iter_keys(section))
593 603 .collect()
594 604 }
595 605
596 606 /// Returns whether any key is defined in the given section
597 607 pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
598 608 self.layers
599 609 .iter()
600 610 .any(|layer| layer.has_non_empty_section(section))
601 611 }
602 612
603 613 /// Yields (key, value) pairs for everything in the given section
604 614 pub fn iter_section<'a>(
605 615 &'a self,
606 616 section: &'a [u8],
607 617 ) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
608 618 // Deduplicate keys redefined in multiple layers
609 619 let mut keys_already_seen = HashSet::new();
610 620 let mut key_is_new =
611 621 move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
612 622 keys_already_seen.insert(key)
613 623 };
614 624 // This is similar to `flat_map` + `filter_map`, except with a single
615 625 // closure that owns `key_is_new` (and therefore the
616 626 // `keys_already_seen` set):
617 627 let mut layer_iters = self
618 628 .layers
619 629 .iter()
620 630 .rev()
621 631 .map(move |layer| layer.iter_section(section))
622 632 .peekable();
623 633 std::iter::from_fn(move || loop {
624 634 if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
625 635 return Some(pair);
626 636 } else {
627 637 layer_iters.next();
628 638 }
629 639 })
630 640 }
631 641
632 642 /// Get raw values bytes from all layers (even untrusted ones) in order
633 643 /// of precedence.
634 644 #[cfg(test)]
635 645 fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
636 646 let mut res = vec![];
637 647 for layer in self.layers.iter().rev() {
638 648 if let Some(v) = layer.get(section, item) {
639 649 res.push(v.bytes.as_ref());
640 650 }
641 651 }
642 652 res
643 653 }
644 654
645 655 // a config layer that's introduced by ui.tweakdefaults
646 656 fn tweakdefaults_layer() -> ConfigLayer {
647 657 let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
648 658
649 659 let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
650 660 layer.add(
651 661 section[..].into(),
652 662 item[..].into(),
653 663 value[..].into(),
654 664 None,
655 665 );
656 666 };
657 667 // duplication of [tweakrc] from [ui.py]
658 668 add(b"ui", b"rollback", b"False");
659 669 add(b"ui", b"statuscopies", b"yes");
660 670 add(b"ui", b"interface", b"curses");
661 671 add(b"ui", b"relative-paths", b"yes");
662 672 add(b"commands", b"grep.all-files", b"True");
663 673 add(b"commands", b"update.check", b"noconflict");
664 674 add(b"commands", b"status.verbose", b"True");
665 675 add(b"commands", b"resolve.explicit-re-merge", b"True");
666 676 add(b"git", b"git", b"1");
667 677 add(b"git", b"showfunc", b"1");
668 678 add(b"git", b"word-diff", b"1");
669 679 layer
670 680 }
671 681
672 682 // introduce the tweaked defaults as implied by ui.tweakdefaults
673 683 pub fn tweakdefaults(&mut self) {
674 684 self.layers.insert(0, Config::tweakdefaults_layer());
675 685 }
676 686 }
677 687
678 688 #[cfg(test)]
679 689 mod tests {
680 690 use super::*;
681 691 use pretty_assertions::assert_eq;
682 692 use std::fs::File;
683 693 use std::io::Write;
684 694
685 695 #[test]
686 696 fn test_include_layer_ordering() {
687 697 let tmpdir = tempfile::tempdir().unwrap();
688 698 let tmpdir_path = tmpdir.path();
689 699 let mut included_file =
690 700 File::create(&tmpdir_path.join("included.rc")).unwrap();
691 701
692 702 included_file.write_all(b"[section]\nitem=value1").unwrap();
693 703 let base_config_path = tmpdir_path.join("base.rc");
694 704 let mut config_file = File::create(&base_config_path).unwrap();
695 705 let data =
696 706 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
697 707 [section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
698 708 config_file.write_all(data).unwrap();
699 709
700 710 let sources = vec![ConfigSource::AbsPath(base_config_path)];
701 711 let config = Config::load_from_explicit_sources(sources)
702 712 .expect("expected valid config");
703 713
704 714 let (_, value) = config.get_inner(b"section", b"item").unwrap();
705 715 assert_eq!(
706 716 value,
707 717 &ConfigValue {
708 718 bytes: b"value2".to_vec(),
709 719 line: Some(4)
710 720 }
711 721 );
712 722
713 723 let value = config.get(b"section", b"item").unwrap();
714 724 assert_eq!(value, b"value2",);
715 725 assert_eq!(
716 726 config.get_all(b"section", b"item"),
717 727 [b"value2", b"value1", b"value0"]
718 728 );
719 729
720 730 assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
721 731 assert_eq!(
722 732 config.get_byte_size(b"section2", b"size").unwrap(),
723 733 Some(1024 + 512)
724 734 );
725 735 assert!(config.get_u32(b"section2", b"not-count").is_err());
726 736 assert!(config.get_byte_size(b"section2", b"not-size").is_err());
727 737 }
728 738 }
@@ -1,171 +1,178 b''
1 1 //! Logging for repository events, including commands run in the repository.
2 2
3 3 use crate::CliInvocation;
4 4 use format_bytes::format_bytes;
5 5 use hg::errors::HgError;
6 6 use hg::repo::Repo;
7 7 use hg::utils::{files::get_bytes_from_os_str, shell_quote};
8 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 10 // Python does not support %.3f, only %f
17 11 const DEFAULT_DATE_FORMAT: &str = "%Y-%m-%d %H:%M:%S%.3f";
18 12
19 13 type DateTime = chrono::DateTime<chrono::Local>;
20 14
21 15 pub struct ProcessStartTime {
22 16 /// For measuring duration
23 17 monotonic_clock: std::time::Instant,
24 18 /// For formatting with year, month, day, etc.
25 19 calendar_based: DateTime,
26 20 }
27 21
28 22 impl ProcessStartTime {
29 23 pub fn now() -> Self {
30 24 Self {
31 25 monotonic_clock: std::time::Instant::now(),
32 26 calendar_based: chrono::Local::now(),
33 27 }
34 28 }
35 29 }
36 30
37 31 pub struct Blackbox<'a> {
38 32 process_start_time: &'a ProcessStartTime,
39 33 /// Do nothing if this is `None`
40 34 configured: Option<ConfiguredBlackbox<'a>>,
41 35 }
42 36
43 37 struct ConfiguredBlackbox<'a> {
44 38 repo: &'a Repo,
45 39 max_size: u64,
46 40 max_files: u32,
47 41 date_format: &'a str,
48 42 }
49 43
50 44 impl<'a> Blackbox<'a> {
51 45 pub fn new(
52 46 invocation: &'a CliInvocation<'a>,
53 47 process_start_time: &'a ProcessStartTime,
54 48 ) -> Result<Self, HgError> {
55 49 let configured = if let Ok(repo) = invocation.repo {
56 50 if invocation.config.get(b"extensions", b"blackbox").is_none() {
57 51 // The extension is not enabled
58 52 None
59 53 } else {
60 54 Some(ConfiguredBlackbox {
61 55 repo,
62 56 max_size: invocation
63 57 .config
64 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 62 max_files: invocation
67 63 .config
68 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 68 date_format: invocation
71 69 .config
72 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 83 } else {
77 84 // Without a local repository there’s no `.hg/blackbox.log` to
78 85 // write to.
79 86 None
80 87 };
81 88 Ok(Self {
82 89 process_start_time,
83 90 configured,
84 91 })
85 92 }
86 93
87 94 pub fn log_command_start<'arg>(
88 95 &self,
89 96 argv: impl Iterator<Item = &'arg OsString>,
90 97 ) {
91 98 if let Some(configured) = &self.configured {
92 99 let message = format_bytes!(b"(rust) {}", format_cli_args(argv));
93 100 configured.log(&self.process_start_time.calendar_based, &message);
94 101 }
95 102 }
96 103
97 104 pub fn log_command_end<'arg>(
98 105 &self,
99 106 argv: impl Iterator<Item = &'arg OsString>,
100 107 exit_code: i32,
101 108 ) {
102 109 if let Some(configured) = &self.configured {
103 110 let now = chrono::Local::now();
104 111 let duration = self
105 112 .process_start_time
106 113 .monotonic_clock
107 114 .elapsed()
108 115 .as_secs_f64();
109 116 let message = format_bytes!(
110 117 b"(rust) {} exited {} after {} seconds",
111 118 format_cli_args(argv),
112 119 exit_code,
113 120 format_bytes::Utf8(format_args!("{:.03}", duration))
114 121 );
115 122 configured.log(&now, &message);
116 123 }
117 124 }
118 125 }
119 126
120 127 impl ConfiguredBlackbox<'_> {
121 128 fn log(&self, date_time: &DateTime, message: &[u8]) {
122 129 let date = format_bytes::Utf8(date_time.format(self.date_format));
123 130 let user = get_bytes_from_os_str(whoami::username_os());
124 131 let rev = format_bytes::Utf8(match self.repo.dirstate_parents() {
125 132 Ok(parents) if parents.p2 == hg::revlog::node::NULL_NODE => {
126 133 format!("{:x}", parents.p1)
127 134 }
128 135 Ok(parents) => format!("{:x}+{:x}", parents.p1, parents.p2),
129 136 Err(_dirstate_corruption_error) => {
130 137 // TODO: log a non-fatal warning to stderr
131 138 "???".to_owned()
132 139 }
133 140 });
134 141 let pid = std::process::id();
135 142 let line = format_bytes!(
136 143 b"{} {} @{} ({})> {}\n",
137 144 date,
138 145 user,
139 146 rev,
140 147 pid,
141 148 message
142 149 );
143 150 let result =
144 151 hg::logging::LogFile::new(self.repo.hg_vfs(), "blackbox.log")
145 152 .max_size(Some(self.max_size))
146 153 .max_files(self.max_files)
147 154 .write(&line);
148 155 match result {
149 156 Ok(()) => {}
150 157 Err(_io_error) => {
151 158 // TODO: log a non-fatal warning to stderr
152 159 }
153 160 }
154 161 }
155 162 }
156 163
157 164 fn format_cli_args<'a>(
158 165 mut args: impl Iterator<Item = &'a OsString>,
159 166 ) -> Vec<u8> {
160 167 let _ = args.next(); // Skip the first (or zeroth) arg, the name of the `rhg` executable
161 168 let mut args = args.map(|arg| shell_quote(&get_bytes_from_os_str(arg)));
162 169 let mut formatted = Vec::new();
163 170 if let Some(arg) = args.next() {
164 171 formatted.extend(arg)
165 172 }
166 173 for arg in args {
167 174 formatted.push(b' ');
168 175 formatted.extend(arg)
169 176 }
170 177 formatted
171 178 }
General Comments 0
You need to be logged in to leave comments. Login now