pglift-cli 3.1.0__tar.gz → 3.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/PKG-INFO +1 -1
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/main.py +42 -28
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/model.py +3 -10
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/pgconf.py +1 -1
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/util.py +2 -2
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-help.t +7 -5
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_model.py +31 -3
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/.gitignore +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/README.md +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/hatch.toml +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/pyproject.toml +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/pytest.ini +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/__init__.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/__main__.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/_settings.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/_site.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/base.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/console.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/database.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/hookspecs.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/instance.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/patroni.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/pgbackrest/__init__.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/pgbackrest/repo_path.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/pghba.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/pm.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/postgres.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/prometheus.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/py.typed +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/role.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/src/pglift_cli/wal.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/.gitignore +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-base.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-cli-walkthrough.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-demote.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-instance-name.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-port-validation.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-prometheus.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-standby-pgbackrest.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-standby.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-transactions.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/expect/test-upgrade.t +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/__init__.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/conftest.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test__site.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_audit.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_cli.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_main.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_pm.py +0 -0
- {pglift_cli-3.1.0 → pglift_cli-3.2.0}/tests/unit/test_util.py +0 -0
|
@@ -11,7 +11,7 @@ import warnings
|
|
|
11
11
|
from functools import partial
|
|
12
12
|
from importlib.metadata import version
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Literal
|
|
14
|
+
from typing import Any, Literal
|
|
15
15
|
|
|
16
16
|
import click
|
|
17
17
|
import click.exceptions
|
|
@@ -24,7 +24,6 @@ from rich.highlighter import NullHighlighter
|
|
|
24
24
|
from rich.syntax import Syntax
|
|
25
25
|
|
|
26
26
|
from pglift import ui
|
|
27
|
-
from pglift._compat import assert_never
|
|
28
27
|
from pglift.system import install
|
|
29
28
|
from pglift.types import LogLevel
|
|
30
29
|
|
|
@@ -234,35 +233,50 @@ def site_settings(
|
|
|
234
233
|
console.print(syntax)
|
|
235
234
|
|
|
236
235
|
|
|
237
|
-
@cli.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
"
|
|
243
|
-
|
|
244
|
-
|
|
236
|
+
@cli.group(hidden=True)
|
|
237
|
+
def site_configure(**kwargs: Any) -> None:
|
|
238
|
+
"""Manage installation of extra data files for pglift.
|
|
239
|
+
|
|
240
|
+
This is an INTERNAL command.
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@site_configure.command("install")
|
|
245
|
+
@click.option(
|
|
246
|
+
"--force",
|
|
247
|
+
is_flag=True,
|
|
248
|
+
default=False,
|
|
249
|
+
help="Force the overwrite of installed files",
|
|
245
250
|
)
|
|
246
251
|
@click.pass_obj
|
|
252
|
+
@async_command
|
|
253
|
+
async def site_configure_install(obj: Obj, force: bool) -> None:
|
|
254
|
+
with obj.lock, audit():
|
|
255
|
+
await install.do(_site.SETTINGS, force=force)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@site_configure.command("uninstall")
|
|
259
|
+
@click.pass_obj
|
|
260
|
+
@async_command
|
|
261
|
+
async def site_configure_uninstall(obj: Obj) -> None:
|
|
262
|
+
with obj.lock, audit():
|
|
263
|
+
await install.undo(_site.SETTINGS)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@site_configure.command("check")
|
|
267
|
+
@click.pass_obj
|
|
247
268
|
@click.pass_context
|
|
248
269
|
@async_command
|
|
249
|
-
async def
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
270
|
+
async def site_configure_check(context: click.Context, obj: Obj) -> None:
|
|
271
|
+
with obj.lock, audit():
|
|
272
|
+
if not install.check(_site.SETTINGS):
|
|
273
|
+
context.exit(1)
|
|
253
274
|
|
|
254
|
-
|
|
255
|
-
|
|
275
|
+
|
|
276
|
+
@site_configure.command("list")
|
|
277
|
+
@click.pass_obj
|
|
278
|
+
@async_command
|
|
279
|
+
async def site_configure_list(obj: Obj) -> None:
|
|
256
280
|
with obj.lock, audit():
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
elif action == "uninstall":
|
|
260
|
-
await install.undo(_site.SETTINGS)
|
|
261
|
-
elif action == "check":
|
|
262
|
-
if not install.check(_site.SETTINGS):
|
|
263
|
-
context.exit(1)
|
|
264
|
-
elif action == "list":
|
|
265
|
-
for p in install.ls(_site.SETTINGS):
|
|
266
|
-
click.echo(p)
|
|
267
|
-
else:
|
|
268
|
-
assert_never(action)
|
|
281
|
+
for p in install.ls(_site.SETTINGS):
|
|
282
|
+
click.echo(p)
|
|
@@ -18,7 +18,7 @@ import pydantic
|
|
|
18
18
|
from pydantic.fields import FieldInfo
|
|
19
19
|
from pydantic_core import ErrorDetails
|
|
20
20
|
|
|
21
|
-
from pglift.annotations import cli
|
|
21
|
+
from pglift.annotations import CreateOnly, cli
|
|
22
22
|
from pglift.exceptions import MutuallyExclusiveError
|
|
23
23
|
from pglift.models.helpers import is_optional, optional_type
|
|
24
24
|
from pglift.models.interface import PresenceState
|
|
@@ -169,7 +169,7 @@ class ParamSpec(ABC):
|
|
|
169
169
|
loc: tuple[str, ...]
|
|
170
170
|
description: str | None = None
|
|
171
171
|
|
|
172
|
-
objtype: ClassVar[type[click.Parameter]]
|
|
172
|
+
objtype: ClassVar[type[click.Parameter]]
|
|
173
173
|
|
|
174
174
|
@property
|
|
175
175
|
def param(self) -> click.Parameter:
|
|
@@ -340,11 +340,7 @@ def _paramspecs_from_model(
|
|
|
340
340
|
for fname, field in model_type.model_fields.items():
|
|
341
341
|
if field_annotation(field, cli.Hidden):
|
|
342
342
|
continue
|
|
343
|
-
if (
|
|
344
|
-
operation == "update"
|
|
345
|
-
and isinstance(field.json_schema_extra, dict)
|
|
346
|
-
and field.json_schema_extra.get("readOnly")
|
|
347
|
-
):
|
|
343
|
+
if field_annotation(field, CreateOnly) and operation == "update":
|
|
348
344
|
continue
|
|
349
345
|
|
|
350
346
|
modelname = argname = field.alias or fname
|
|
@@ -372,9 +368,6 @@ def _paramspecs_from_model(
|
|
|
372
368
|
if not _parents and required:
|
|
373
369
|
if origin_type is typing.Literal:
|
|
374
370
|
choices = list(typing.get_args(ftype))
|
|
375
|
-
if config is not None:
|
|
376
|
-
assert isinstance(config, cli.Choices)
|
|
377
|
-
choices = config.choices
|
|
378
371
|
attrs["type"] = click.Choice(choices)
|
|
379
372
|
if config is not None and isinstance(config, cli.Option):
|
|
380
373
|
attrs["required"] = True
|
|
@@ -58,7 +58,7 @@ def show_configuration_changes(
|
|
|
58
58
|
@cli.command("show")
|
|
59
59
|
@click.argument("parameter", nargs=-1)
|
|
60
60
|
@pass_postgresql_instance
|
|
61
|
-
def show(instance: PostgreSQLInstance, parameter: tuple[str]) -> None:
|
|
61
|
+
def show(instance: PostgreSQLInstance, parameter: tuple[str, ...]) -> None:
|
|
62
62
|
"""Show configuration (all parameters or specified ones).
|
|
63
63
|
|
|
64
64
|
Only uncommented parameters are shown when no PARAMETER is specified. When
|
|
@@ -273,7 +273,7 @@ def nameversion_from_id(instance_id: str) -> tuple[str, PostgreSQLVersion | None
|
|
|
273
273
|
|
|
274
274
|
|
|
275
275
|
def postgresql_instance_lookup(
|
|
276
|
-
context: click.Context, param: click.Parameter, value: None | str | tuple[str]
|
|
276
|
+
context: click.Context, param: click.Parameter, value: None | str | tuple[str, ...]
|
|
277
277
|
) -> PostgreSQLInstance | tuple[PostgreSQLInstance, ...]:
|
|
278
278
|
"""Return one or more PostgreSQLInstance, possibly guessed if there
|
|
279
279
|
is only one on system, depending on 'param' variadic flag (nargs).
|
|
@@ -764,7 +764,7 @@ class PluggableCommandGroup(abc.ABC, Group):
|
|
|
764
764
|
if obj is None:
|
|
765
765
|
obj = context.ensure_object(Obj)
|
|
766
766
|
if obj is None:
|
|
767
|
-
return
|
|
767
|
+
return # type: ignore[unreachable]
|
|
768
768
|
self.register_plugin_commands(obj)
|
|
769
769
|
self._plugin_commands_loaded = True
|
|
770
770
|
|
|
@@ -249,13 +249,13 @@ Instance commands
|
|
|
249
249
|
--locale LOCALE Default locale.
|
|
250
250
|
--encoding ENCODING Character encoding of the PostgreSQL
|
|
251
251
|
instance.
|
|
252
|
-
--auth-local [trust|reject|md5|password|scram-sha-256|sspi|ident|
|
|
252
|
+
--auth-local [trust|reject|md5|password|scram-sha-256|sspi|ident|pam|ldap|radius|peer]
|
|
253
253
|
Authentication method for local-socket
|
|
254
254
|
connections.
|
|
255
|
-
--auth-host [trust|reject|md5|password|scram-sha-256|
|
|
255
|
+
--auth-host [trust|reject|md5|password|scram-sha-256|sspi|ident|pam|ldap|radius|gss]
|
|
256
256
|
Authentication method for local TCP/IP
|
|
257
257
|
connections.
|
|
258
|
-
--auth-hostssl [trust|reject|md5|password|scram-sha-256|
|
|
258
|
+
--auth-hostssl [trust|reject|md5|password|scram-sha-256|sspi|ident|pam|ldap|radius|gss|cert]
|
|
259
259
|
Authentication method for SSL-encrypted
|
|
260
260
|
TCP/IP connections.
|
|
261
261
|
--surole-password PASSWORD Super-user role password.
|
|
@@ -947,7 +947,8 @@ HBA configuration management commands
|
|
|
947
947
|
--database DATABASE Database name(s). Multiple database names
|
|
948
948
|
can be supplied by separating them with
|
|
949
949
|
commas.
|
|
950
|
-
--method
|
|
950
|
+
--method [trust|reject|md5|password|scram-sha-256|sspi|ident|pam|ldap|radius|peer|gss|cert]
|
|
951
|
+
Authentication method. [required]
|
|
951
952
|
--user USER User name(s). Multiple user names can be
|
|
952
953
|
supplied by separating them with commas.
|
|
953
954
|
--dry-run Simulate change operations.
|
|
@@ -970,7 +971,8 @@ HBA configuration management commands
|
|
|
970
971
|
--database DATABASE Database name(s). Multiple database names
|
|
971
972
|
can be supplied by separating them with
|
|
972
973
|
commas.
|
|
973
|
-
--method
|
|
974
|
+
--method [trust|reject|md5|password|scram-sha-256|sspi|ident|pam|ldap|radius|peer|gss|cert]
|
|
975
|
+
Authentication method. [required]
|
|
974
976
|
--user USER User name(s). Multiple user names can be
|
|
975
977
|
supplied by separating them with commas.
|
|
976
978
|
--dry-run Simulate change operations.
|
|
@@ -76,6 +76,8 @@ def test_as_parameters(runner: CliRunner) -> None:
|
|
|
76
76
|
"Options:\n"
|
|
77
77
|
" --exclude-none\n"
|
|
78
78
|
" --nickname TEXT Your secret nickname. [required]\n"
|
|
79
|
+
" --gender [female|male|nonbinary|prefer not to say]\n"
|
|
80
|
+
" Gender. [required]\n"
|
|
79
81
|
" --age AGE Age.\n"
|
|
80
82
|
" --address-street STREET Street lines. (Can be used multiple times.)\n"
|
|
81
83
|
" --address-zip-code ZIP_CODE ZIP code.\n"
|
|
@@ -102,6 +104,7 @@ def test_as_parameters(runner: CliRunner) -> None:
|
|
|
102
104
|
"alice",
|
|
103
105
|
"friend",
|
|
104
106
|
"--exclude-none",
|
|
107
|
+
"--gender=female",
|
|
105
108
|
"--age=42",
|
|
106
109
|
"--address-street=bd montparnasse",
|
|
107
110
|
"--address-street=far far away",
|
|
@@ -126,6 +129,7 @@ def test_as_parameters(runner: CliRunner) -> None:
|
|
|
126
129
|
"zip_code": 0,
|
|
127
130
|
"primary": True,
|
|
128
131
|
},
|
|
132
|
+
"gender": "female",
|
|
129
133
|
"age": 42,
|
|
130
134
|
"birth": {"date": "1981-02-18"},
|
|
131
135
|
"is_dead": False,
|
|
@@ -157,6 +161,7 @@ def test_as_parameters(runner: CliRunner) -> None:
|
|
|
157
161
|
[
|
|
158
162
|
"foo",
|
|
159
163
|
"friend",
|
|
164
|
+
"--gender=female",
|
|
160
165
|
"--age=17",
|
|
161
166
|
"--birth-date=1987-06-05",
|
|
162
167
|
"--nickname=aaa",
|
|
@@ -186,6 +191,8 @@ def test_as_parameters_update() -> None:
|
|
|
186
191
|
"\n"
|
|
187
192
|
"Options:\n"
|
|
188
193
|
" --nickname TEXT Your secret nickname. [required]\n"
|
|
194
|
+
" --gender [female|male|nonbinary|prefer not to say]\n"
|
|
195
|
+
" Gender. [required]\n"
|
|
189
196
|
" --age AGE Age.\n"
|
|
190
197
|
" --address-zip-code ZIP_CODE ZIP code.\n"
|
|
191
198
|
" --address-town CITY City.\n"
|
|
@@ -209,16 +216,24 @@ def test_as_parameters_update() -> None:
|
|
|
209
216
|
["alice", "--age=5", "--birthdate=2042-02-31"],
|
|
210
217
|
)
|
|
211
218
|
assert result.exit_code == 2, result.output
|
|
212
|
-
assert "Error: No such option
|
|
219
|
+
assert "Error: No such option '--birthdate'" in result.output
|
|
213
220
|
|
|
214
221
|
result = runner.invoke(
|
|
215
222
|
update_person,
|
|
216
|
-
[
|
|
223
|
+
[
|
|
224
|
+
"alice",
|
|
225
|
+
"other",
|
|
226
|
+
"--nickname=a",
|
|
227
|
+
"--gender=female",
|
|
228
|
+
"--age=5",
|
|
229
|
+
"--birth-date=1987-06-05",
|
|
230
|
+
],
|
|
217
231
|
)
|
|
218
232
|
assert result.exit_code == 0, result.output
|
|
219
233
|
assert json.loads(result.output) == {
|
|
220
234
|
"name": "alice",
|
|
221
235
|
"nickname": "**********",
|
|
236
|
+
"gender": "female",
|
|
222
237
|
"relation": "other",
|
|
223
238
|
"age": 5,
|
|
224
239
|
"birth": {"date": "1987-06-05"},
|
|
@@ -228,7 +243,14 @@ def test_as_parameters_update() -> None:
|
|
|
228
243
|
|
|
229
244
|
result = runner.invoke(
|
|
230
245
|
update_person,
|
|
231
|
-
[
|
|
246
|
+
[
|
|
247
|
+
"alice",
|
|
248
|
+
"friend",
|
|
249
|
+
"--nickname=a",
|
|
250
|
+
"--gender=female",
|
|
251
|
+
"--age=abc",
|
|
252
|
+
"--birth-date=2010-02-03",
|
|
253
|
+
],
|
|
232
254
|
)
|
|
233
255
|
assert result.exit_code == 2
|
|
234
256
|
assert (
|
|
@@ -242,6 +264,7 @@ def test_as_parameters_update() -> None:
|
|
|
242
264
|
"bob",
|
|
243
265
|
"family",
|
|
244
266
|
"--nickname=b",
|
|
267
|
+
"--gender=male",
|
|
245
268
|
"--birth-date=1987-06-05",
|
|
246
269
|
"--address-town=laville",
|
|
247
270
|
"--address-country=be",
|
|
@@ -262,6 +285,7 @@ def test_as_parameters_update() -> None:
|
|
|
262
285
|
"marcel",
|
|
263
286
|
"other",
|
|
264
287
|
"--nickname=a",
|
|
288
|
+
"--gender=male",
|
|
265
289
|
"--age=46",
|
|
266
290
|
"--birth-date=1978-03-09",
|
|
267
291
|
"--add-pet=snoopy",
|
|
@@ -275,6 +299,7 @@ def test_as_parameters_update() -> None:
|
|
|
275
299
|
"name": "marcel",
|
|
276
300
|
"nickname": "**********",
|
|
277
301
|
"relation": "other",
|
|
302
|
+
"gender": "male",
|
|
278
303
|
"age": 46,
|
|
279
304
|
"birth": {"date": "1978-03-09"},
|
|
280
305
|
"pets": [
|
|
@@ -339,6 +364,7 @@ def test_parse_params_as() -> None:
|
|
|
339
364
|
"name": "alice",
|
|
340
365
|
"relation": "other",
|
|
341
366
|
"nickname": "la malice",
|
|
367
|
+
"gender": "female",
|
|
342
368
|
"age": 42,
|
|
343
369
|
"address": address_params,
|
|
344
370
|
"birth": {"date": "1976-05-04"},
|
|
@@ -347,6 +373,7 @@ def test_parse_params_as() -> None:
|
|
|
347
373
|
name="alice",
|
|
348
374
|
nickname="la malice",
|
|
349
375
|
relation="other",
|
|
376
|
+
gender="female",
|
|
350
377
|
age=42,
|
|
351
378
|
address=address,
|
|
352
379
|
birth=models.BirthInformation(date=date(1976, 5, 4)), # type: ignore[call-arg]
|
|
@@ -357,6 +384,7 @@ def test_parse_params_as() -> None:
|
|
|
357
384
|
"name": "alice",
|
|
358
385
|
"relation": "other",
|
|
359
386
|
"nickname": "la malice",
|
|
387
|
+
"gender": "female",
|
|
360
388
|
"age": 42,
|
|
361
389
|
"birth_date": "1976-05-04",
|
|
362
390
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|