Show More
@@ -0,0 +1,36 | |||
|
1 | # configuration related constants | |
|
2 | ||
|
3 | from __future__ import annotations | |
|
4 | ||
|
5 | from typing import ( | |
|
6 | List, | |
|
7 | Tuple, | |
|
8 | Union, | |
|
9 | ) | |
|
10 | ||
|
11 | # keep typing simple for now | |
|
12 | ConfigLevelT = str | |
|
13 | LEVEL_USER = 'user' # "user" is the default level and never passed explicitly | |
|
14 | LEVEL_LOCAL = 'local' | |
|
15 | LEVEL_GLOBAL = 'global' | |
|
16 | LEVEL_SHARED = 'shared' | |
|
17 | LEVEL_NON_SHARED = 'non_shared' | |
|
18 | EDIT_LEVELS = ( | |
|
19 | LEVEL_USER, | |
|
20 | LEVEL_LOCAL, | |
|
21 | LEVEL_GLOBAL, | |
|
22 | LEVEL_SHARED, | |
|
23 | LEVEL_NON_SHARED, | |
|
24 | ) | |
|
25 | ||
|
26 | ConfigItemT = Tuple[bytes, bytes, bytes, bytes] | |
|
27 | ResourceIDT = Tuple[bytes, bytes] | |
|
28 | FileRCT = bytes | |
|
29 | ComponentT = Tuple[ | |
|
30 | bytes, | |
|
31 | Union[ | |
|
32 | List[ConfigItemT], | |
|
33 | FileRCT, | |
|
34 | ResourceIDT, | |
|
35 | ], | |
|
36 | ] |
@@ -1,201 +1,196 | |||
|
1 | 1 | # Gather code related to command dealing with configuration. |
|
2 | 2 | |
|
3 | 3 | from __future__ import annotations |
|
4 | 4 | |
|
5 | 5 | import os |
|
6 | 6 | |
|
7 | 7 | from typing import Any, Collection, Dict, Optional |
|
8 | 8 | |
|
9 | 9 | from ..i18n import _ |
|
10 | 10 | |
|
11 | 11 | from .. import ( |
|
12 | 12 | cmdutil, |
|
13 | 13 | error, |
|
14 | 14 | formatter, |
|
15 | 15 | pycompat, |
|
16 | 16 | requirements, |
|
17 | 17 | ui as uimod, |
|
18 | 18 | util, |
|
19 | 19 | vfs as vfsmod, |
|
20 | 20 | ) |
|
21 | 21 | |
|
22 |
from . import |
|
|
22 | from . import ( | |
|
23 | ConfigLevelT, | |
|
24 | EDIT_LEVELS, | |
|
25 | LEVEL_GLOBAL, | |
|
26 | LEVEL_LOCAL, | |
|
27 | LEVEL_NON_SHARED, | |
|
28 | LEVEL_SHARED, | |
|
29 | LEVEL_USER, | |
|
30 | rcutil, | |
|
31 | ) | |
|
23 | 32 | |
|
24 | 33 | EDIT_FLAG = 'edit' |
|
25 | 34 | |
|
26 | 35 | |
|
27 | # keep typing simple for now | |
|
28 | ConfigLevelT = str | |
|
29 | LEVEL_USER = 'user' # "user" is the default level and never passed explicitly | |
|
30 | LEVEL_LOCAL = 'local' | |
|
31 | LEVEL_GLOBAL = 'global' | |
|
32 | LEVEL_SHARED = 'shared' | |
|
33 | LEVEL_NON_SHARED = 'non_shared' | |
|
34 | EDIT_LEVELS = ( | |
|
35 | LEVEL_USER, | |
|
36 | LEVEL_LOCAL, | |
|
37 | LEVEL_GLOBAL, | |
|
38 | LEVEL_SHARED, | |
|
39 | LEVEL_NON_SHARED, | |
|
40 | ) | |
|
41 | ||
|
42 | ||
|
43 | 36 | def find_edit_level( |
|
44 | ui: uimod.ui, repo, opts: Dict[str, Any] | |
|
37 | ui: uimod.ui, | |
|
38 | repo, | |
|
39 | opts: Dict[str, Any], | |
|
45 | 40 | ) -> Optional[ConfigLevelT]: |
|
46 | 41 | """return the level we should edit, if any. |
|
47 | 42 | |
|
48 | 43 | Parse the command option to detect when an edit is requested, and if so the |
|
49 | 44 | configuration level we should edit. |
|
50 | 45 | """ |
|
51 | 46 | if opts.get(EDIT_FLAG) or any(opts.get(o) for o in EDIT_LEVELS): |
|
52 | 47 | cmdutil.check_at_most_one_arg(opts, *EDIT_LEVELS) |
|
53 | 48 | for level in EDIT_LEVELS: |
|
54 | 49 | if opts.get(level): |
|
55 | 50 | return level |
|
56 | 51 | return EDIT_LEVELS[0] |
|
57 | 52 | return None |
|
58 | 53 | |
|
59 | 54 | |
|
60 | 55 | def edit_config(ui: uimod.ui, repo, level: ConfigLevelT) -> None: |
|
61 | 56 | """let the user edit configuration file for the given level""" |
|
62 | 57 | |
|
63 | 58 | if level == LEVEL_USER: |
|
64 | 59 | paths = rcutil.userrcpath() |
|
65 | 60 | elif level == LEVEL_GLOBAL: |
|
66 | 61 | paths = rcutil.systemrcpath() |
|
67 | 62 | elif level == LEVEL_LOCAL: |
|
68 | 63 | if not repo: |
|
69 | 64 | raise error.InputError(_(b"can't use --local outside a repository")) |
|
70 | 65 | paths = [repo.vfs.join(b'hgrc')] |
|
71 | 66 | elif level == LEVEL_NON_SHARED: |
|
72 | 67 | paths = [repo.vfs.join(b'hgrc-not-shared')] |
|
73 | 68 | elif level == LEVEL_SHARED: |
|
74 | 69 | if not repo.shared(): |
|
75 | 70 | raise error.InputError( |
|
76 | 71 | _(b"repository is not shared; can't use --shared") |
|
77 | 72 | ) |
|
78 | 73 | if requirements.SHARESAFE_REQUIREMENT not in repo.requirements: |
|
79 | 74 | raise error.InputError( |
|
80 | 75 | _( |
|
81 | 76 | b"share safe feature not enabled; " |
|
82 | 77 | b"unable to edit shared source repository config" |
|
83 | 78 | ) |
|
84 | 79 | ) |
|
85 | 80 | paths = [vfsmod.vfs(repo.sharedpath).join(b'hgrc')] |
|
86 | 81 | else: |
|
87 | 82 | msg = 'unknown config level: %s' % level |
|
88 | 83 | raise error.ProgrammingError(msg) |
|
89 | 84 | |
|
90 | 85 | for f in paths: |
|
91 | 86 | if os.path.exists(f): |
|
92 | 87 | break |
|
93 | 88 | else: |
|
94 | 89 | if LEVEL_GLOBAL: |
|
95 | 90 | samplehgrc = uimod.samplehgrcs[b'global'] |
|
96 | 91 | elif LEVEL_LOCAL: |
|
97 | 92 | samplehgrc = uimod.samplehgrcs[b'local'] |
|
98 | 93 | else: |
|
99 | 94 | samplehgrc = uimod.samplehgrcs[b'user'] |
|
100 | 95 | |
|
101 | 96 | f = paths[0] |
|
102 | 97 | util.writefile(f, util.tonativeeol(samplehgrc)) |
|
103 | 98 | |
|
104 | 99 | editor = ui.geteditor() |
|
105 | 100 | ui.system( |
|
106 | 101 | b"%s \"%s\"" % (editor, f), |
|
107 | 102 | onerr=error.InputError, |
|
108 | 103 | errprefix=_(b"edit failed"), |
|
109 | 104 | blockedtag=b'config_edit', |
|
110 | 105 | ) |
|
111 | 106 | |
|
112 | 107 | |
|
113 | 108 | def show_component(ui: uimod.ui, repo) -> None: |
|
114 | 109 | """show the component used to build the config |
|
115 | 110 | |
|
116 | 111 | XXX this skip over various source and ignore the repository config, so it |
|
117 | 112 | XXX is probably useless old code. |
|
118 | 113 | """ |
|
119 | 114 | for t, f in rcutil.rccomponents(): |
|
120 | 115 | if t == b'path': |
|
121 | 116 | ui.debug(b'read config from: %s\n' % f) |
|
122 | 117 | elif t == b'resource': |
|
123 | 118 | ui.debug(b'read config from: resource:%s.%s\n' % (f[0], f[1])) |
|
124 | 119 | elif t == b'items': |
|
125 | 120 | # Don't print anything for 'items'. |
|
126 | 121 | pass |
|
127 | 122 | else: |
|
128 | 123 | raise error.ProgrammingError(b'unknown rctype: %s' % t) |
|
129 | 124 | |
|
130 | 125 | |
|
131 | 126 | def show_config( |
|
132 | 127 | ui: uimod.ui, |
|
133 | 128 | repo, |
|
134 | 129 | value_filters: Collection[bytes], |
|
135 | 130 | formatter_options: dict, |
|
136 | 131 | untrusted: bool = False, |
|
137 | 132 | all_known: bool = False, |
|
138 | 133 | show_source: bool = False, |
|
139 | 134 | ) -> bool: |
|
140 | 135 | """Display config value to the user |
|
141 | 136 | |
|
142 | 137 | The display is done using a dedicated `formatter` object. |
|
143 | 138 | |
|
144 | 139 | |
|
145 | 140 | :value_filters: |
|
146 | 141 | if non-empty filter the display value according to these filters. If |
|
147 | 142 | the filter does not match any value, the function return False. True |
|
148 | 143 | otherwise. |
|
149 | 144 | |
|
150 | 145 | :formatter_option: |
|
151 | 146 | options passed to the formatter |
|
152 | 147 | |
|
153 | 148 | :untrusted: |
|
154 | 149 | When set, use untrusted value instead of ignoring them |
|
155 | 150 | |
|
156 | 151 | :all_known: |
|
157 | 152 | Display all known config item, not just the one with an explicit value. |
|
158 | 153 | |
|
159 | 154 | :show_source: |
|
160 | 155 | Show where each value has been defined. |
|
161 | 156 | """ |
|
162 | 157 | fm = ui.formatter(b'config', formatter_options) |
|
163 | 158 | selsections = selentries = [] |
|
164 | 159 | filtered = False |
|
165 | 160 | if value_filters: |
|
166 | 161 | selsections = [v for v in value_filters if b'.' not in v] |
|
167 | 162 | selentries = [v for v in value_filters if b'.' in v] |
|
168 | 163 | filtered = True |
|
169 | 164 | uniquesel = len(selentries) == 1 and not selsections |
|
170 | 165 | selsections = set(selsections) |
|
171 | 166 | selentries = set(selentries) |
|
172 | 167 | |
|
173 | 168 | matched = False |
|
174 | 169 | entries = ui.walkconfig(untrusted=untrusted, all_known=all_known) |
|
175 | 170 | for section, name, value in entries: |
|
176 | 171 | source = ui.configsource(section, name, untrusted) |
|
177 | 172 | value = pycompat.bytestr(value) |
|
178 | 173 | defaultvalue = ui.configdefault(section, name) |
|
179 | 174 | if fm.isplain(): |
|
180 | 175 | source = source or b'none' |
|
181 | 176 | value = value.replace(b'\n', b'\\n') |
|
182 | 177 | entryname = section + b'.' + name |
|
183 | 178 | if filtered and not (section in selsections or entryname in selentries): |
|
184 | 179 | continue |
|
185 | 180 | fm.startitem() |
|
186 | 181 | fm.condwrite(show_source, b'source', b'%s: ', source) |
|
187 | 182 | if uniquesel: |
|
188 | 183 | fm.data(name=entryname) |
|
189 | 184 | fm.write(b'value', b'%s\n', value) |
|
190 | 185 | else: |
|
191 | 186 | fm.write(b'name value', b'%s=%s\n', entryname, value) |
|
192 | 187 | if formatter.isprintable(defaultvalue): |
|
193 | 188 | fm.data(defaultvalue=defaultvalue) |
|
194 | 189 | elif isinstance(defaultvalue, list) and all( |
|
195 | 190 | formatter.isprintable(e) for e in defaultvalue |
|
196 | 191 | ): |
|
197 | 192 | fm.data(defaultvalue=fm.formatlist(defaultvalue, name=b'value')) |
|
198 | 193 | # TODO: no idea how to process unsupported defaultvalue types |
|
199 | 194 | matched = True |
|
200 | 195 | fm.end() |
|
201 | 196 | return matched |
@@ -1,173 +1,165 | |||
|
1 | 1 | # rcutil.py - utilities about config paths, special config sections etc. |
|
2 | 2 | # |
|
3 | 3 | # Copyright Mercurial Contributors |
|
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 | from __future__ import annotations |
|
9 | 9 | |
|
10 | 10 | import os |
|
11 | 11 | |
|
12 | 12 | from typing import ( |
|
13 | 13 | Dict, |
|
14 | 14 | List, |
|
15 | 15 | Optional, |
|
16 | Tuple, | |
|
17 | Union, | |
|
18 | 16 | ) |
|
19 | 17 | |
|
20 | 18 | from .. import ( |
|
19 | configuration as conf_mod, | |
|
21 | 20 | encoding, |
|
22 | 21 | localrepo, |
|
23 | 22 | pycompat, |
|
24 | 23 | requirements as requirementsmod, |
|
25 | 24 | util, |
|
26 | 25 | vfs, |
|
27 | 26 | ) |
|
28 | 27 | |
|
29 | 28 | from ..utils import resourceutil |
|
30 | 29 | |
|
31 | 30 | if pycompat.iswindows: |
|
32 | 31 | from .. import scmwindows as scmplatform |
|
33 | 32 | else: |
|
34 | 33 | from .. import scmposix as scmplatform |
|
35 | 34 | |
|
36 | 35 | fallbackpager = scmplatform.fallbackpager |
|
37 | 36 | systemrcpath = scmplatform.systemrcpath |
|
38 | 37 | userrcpath = scmplatform.userrcpath |
|
39 | 38 | |
|
40 | ConfigItemT = Tuple[bytes, bytes, bytes, bytes] | |
|
41 | ResourceIDT = Tuple[bytes, bytes] | |
|
42 | FileRCT = bytes | |
|
43 | ComponentT = Tuple[ | |
|
44 | bytes, | |
|
45 | Union[ | |
|
46 | List[ConfigItemT], | |
|
47 | FileRCT, | |
|
48 | ResourceIDT, | |
|
49 | ], | |
|
50 | ] | |
|
39 | ComponentT = conf_mod.ComponentT | |
|
40 | ConfigItemT = conf_mod.ConfigItemT | |
|
41 | FileRCT = conf_mod.FileRCT | |
|
42 | ResourceIDT = conf_mod.ResourceIDT | |
|
51 | 43 | |
|
52 | 44 | |
|
53 | 45 | def _expandrcpath(path: bytes) -> List[FileRCT]: |
|
54 | 46 | '''path could be a file or a directory. return a list of file paths''' |
|
55 | 47 | p = util.expandpath(path) |
|
56 | 48 | if os.path.isdir(p): |
|
57 | 49 | join = os.path.join |
|
58 | 50 | return sorted( |
|
59 | 51 | join(p, f) for f, k in util.listdir(p) if f.endswith(b'.rc') |
|
60 | 52 | ) |
|
61 | 53 | return [p] |
|
62 | 54 | |
|
63 | 55 | |
|
64 | 56 | def envrcitems(env: Optional[Dict[bytes, bytes]] = None) -> List[ConfigItemT]: |
|
65 | 57 | """Return [(section, name, value, source)] config items. |
|
66 | 58 | |
|
67 | 59 | The config items are extracted from environment variables specified by env, |
|
68 | 60 | used to override systemrc, but not userrc. |
|
69 | 61 | |
|
70 | 62 | If env is not provided, encoding.environ will be used. |
|
71 | 63 | """ |
|
72 | 64 | if env is None: |
|
73 | 65 | env = encoding.environ |
|
74 | 66 | checklist = [ |
|
75 | 67 | (b'EDITOR', b'ui', b'editor'), |
|
76 | 68 | (b'VISUAL', b'ui', b'editor'), |
|
77 | 69 | (b'PAGER', b'pager', b'pager'), |
|
78 | 70 | ] |
|
79 | 71 | result = [] |
|
80 | 72 | for envname, section, configname in checklist: |
|
81 | 73 | if envname not in env: |
|
82 | 74 | continue |
|
83 | 75 | result.append((section, configname, env[envname], b'$%s' % envname)) |
|
84 | 76 | return result |
|
85 | 77 | |
|
86 | 78 | |
|
87 | 79 | def default_rc_resources() -> List[ResourceIDT]: |
|
88 | 80 | """return rc resource IDs in defaultrc""" |
|
89 | 81 | rsrcs = resourceutil.contents(b'mercurial.defaultrc') |
|
90 | 82 | return [ |
|
91 | 83 | (b'mercurial.defaultrc', r) |
|
92 | 84 | for r in sorted(rsrcs) |
|
93 | 85 | if resourceutil.is_resource(b'mercurial.defaultrc', r) |
|
94 | 86 | and r.endswith(b'.rc') |
|
95 | 87 | ] |
|
96 | 88 | |
|
97 | 89 | |
|
98 | 90 | def rccomponents() -> List[ComponentT]: |
|
99 | 91 | """return an ordered [(type, obj)] about where to load configs. |
|
100 | 92 | |
|
101 | 93 | respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is |
|
102 | 94 | used. if $HGRCPATH is not set, the platform default will be used. |
|
103 | 95 | |
|
104 | 96 | if a directory is provided, *.rc files under it will be used. |
|
105 | 97 | |
|
106 | 98 | type could be either 'path', 'items' or 'resource'. If type is 'path', |
|
107 | 99 | obj is a string, and is the config file path. if type is 'items', obj is a |
|
108 | 100 | list of (section, name, value, source) that should fill the config directly. |
|
109 | 101 | If type is 'resource', obj is a tuple of (package name, resource name). |
|
110 | 102 | """ |
|
111 | 103 | envrc = (b'items', envrcitems()) |
|
112 | 104 | |
|
113 | 105 | if b'HGRCPATH' in encoding.environ: |
|
114 | 106 | # assume HGRCPATH is all about user configs so environments can be |
|
115 | 107 | # overridden. |
|
116 | 108 | _rccomponents = [envrc] |
|
117 | 109 | for p in encoding.environ[b'HGRCPATH'].split(pycompat.ospathsep): |
|
118 | 110 | if not p: |
|
119 | 111 | continue |
|
120 | 112 | _rccomponents.extend((b'path', p) for p in _expandrcpath(p)) |
|
121 | 113 | else: |
|
122 | 114 | _rccomponents = [(b'resource', r) for r in default_rc_resources()] |
|
123 | 115 | |
|
124 | 116 | normpaths = lambda paths: [ |
|
125 | 117 | (b'path', os.path.normpath(p)) for p in paths |
|
126 | 118 | ] |
|
127 | 119 | _rccomponents.extend(normpaths(systemrcpath())) |
|
128 | 120 | _rccomponents.append(envrc) |
|
129 | 121 | _rccomponents.extend(normpaths(userrcpath())) |
|
130 | 122 | return _rccomponents |
|
131 | 123 | |
|
132 | 124 | |
|
133 | 125 | def _shared_source_component(path: bytes) -> List[FileRCT]: |
|
134 | 126 | """if the current repository is shared one, this tries to read |
|
135 | 127 | .hg/hgrc of shared source if we are in share-safe mode |
|
136 | 128 | |
|
137 | 129 | This should be called before reading .hg/hgrc or the main repo |
|
138 | 130 | as that overrides config set in shared source""" |
|
139 | 131 | try: |
|
140 | 132 | with open(os.path.join(path, b".hg", b"requires"), "rb") as fp: |
|
141 | 133 | requirements = set(fp.read().splitlines()) |
|
142 | 134 | if not ( |
|
143 | 135 | requirementsmod.SHARESAFE_REQUIREMENT in requirements |
|
144 | 136 | and requirementsmod.SHARED_REQUIREMENT in requirements |
|
145 | 137 | ): |
|
146 | 138 | return [] |
|
147 | 139 | hgvfs = vfs.vfs(os.path.join(path, b".hg")) |
|
148 | 140 | sharedvfs = localrepo._getsharedvfs(hgvfs, requirements) |
|
149 | 141 | return [sharedvfs.join(b"hgrc")] |
|
150 | 142 | except IOError: |
|
151 | 143 | pass |
|
152 | 144 | return [] |
|
153 | 145 | |
|
154 | 146 | |
|
155 | 147 | def repo_components(repo_path: bytes) -> List[ComponentT]: |
|
156 | 148 | """return the list of config file to read for a repository""" |
|
157 | 149 | components = [] |
|
158 | 150 | components.extend(_shared_source_component(repo_path)) |
|
159 | 151 | components.append(os.path.join(repo_path, b".hg", b"hgrc")) |
|
160 | 152 | components.append(os.path.join(repo_path, b".hg", b"hgrc-not-shared")) |
|
161 | 153 | return [(b'path', c) for c in components] |
|
162 | 154 | |
|
163 | 155 | |
|
164 | 156 | def defaultpagerenv() -> Dict[bytes, bytes]: |
|
165 | 157 | """return a dict of default environment variables and their values, |
|
166 | 158 | intended to be set before starting a pager. |
|
167 | 159 | """ |
|
168 | 160 | return {b'LESS': b'FRX', b'LV': b'-c'} |
|
169 | 161 | |
|
170 | 162 | |
|
171 | 163 | def use_repo_hgrc() -> bool: |
|
172 | 164 | """True if repositories `.hg/hgrc` config should be read""" |
|
173 | 165 | return b'HGRCSKIPREPO' not in encoding.environ |
General Comments 0
You need to be logged in to leave comments.
Login now