execsql2 2.22.5__py3-none-any.whl → 2.22.7__py3-none-any.whl
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.
- execsql/format.py +120 -13
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/METADATA +2 -2
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/RECORD +21 -21
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/WHEEL +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/entry_points.txt +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.22.5.dist-info → execsql2-2.22.7.dist-info}/licenses/NOTICE +0 -0
execsql/format.py
CHANGED
|
@@ -278,6 +278,64 @@ def _sql_literal_texts(sql: str) -> list[str]:
|
|
|
278
278
|
return [sql[start:end] for start, end, _ in _iter_sql_literals(sql)]
|
|
279
279
|
|
|
280
280
|
|
|
281
|
+
def _literal_line_starts(lines: list[str]) -> list[int]:
|
|
282
|
+
"""Return the character offset at which each of *lines* starts once joined."""
|
|
283
|
+
starts: list[int] = []
|
|
284
|
+
offset = 0
|
|
285
|
+
for line in lines:
|
|
286
|
+
starts.append(offset)
|
|
287
|
+
offset += len(line) + 1 # +1 for the newline joined between lines
|
|
288
|
+
return starts
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _literal_interior_lines(lines: list[str]) -> set[int]:
|
|
292
|
+
"""Return indices of *lines* that begin inside a multi-line string literal.
|
|
293
|
+
|
|
294
|
+
The line that *opens* a literal is ordinary SQL up to the delimiter, so it
|
|
295
|
+
is indented with the statement as usual. Every later line of that literal
|
|
296
|
+
— through and including the one holding the closing delimiter — begins
|
|
297
|
+
inside the string, which makes its leading whitespace part of the stored
|
|
298
|
+
value rather than layout. Re-indenting those lines changes what the
|
|
299
|
+
database receives, so callers must emit them verbatim.
|
|
300
|
+
|
|
301
|
+
This covers every literal kind the scanner reports, not just dollar-quoted
|
|
302
|
+
bodies: an ordinary ``'…'`` string spanning lines carries its newlines and
|
|
303
|
+
indentation exactly the same way.
|
|
304
|
+
"""
|
|
305
|
+
if len(lines) < 2:
|
|
306
|
+
return set()
|
|
307
|
+
text = "\n".join(lines)
|
|
308
|
+
starts = _literal_line_starts(lines)
|
|
309
|
+
|
|
310
|
+
interior: set[int] = set()
|
|
311
|
+
for start, end, _kind in _iter_sql_literals(text):
|
|
312
|
+
interior.update(i for i, line_start in enumerate(starts) if start < line_start < end)
|
|
313
|
+
return interior
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _literal_spanning_lines(lines: list[str]) -> set[int]:
|
|
317
|
+
"""Return indices of every line that any multi-line literal touches.
|
|
318
|
+
|
|
319
|
+
Unlike :func:`_literal_interior_lines` this also includes the line that
|
|
320
|
+
*opens* the literal. Passes that move text between lines — rather than
|
|
321
|
+
only adjusting leading whitespace — must leave the whole span alone, since
|
|
322
|
+
the opening line holds literal content after its delimiter.
|
|
323
|
+
"""
|
|
324
|
+
if len(lines) < 2:
|
|
325
|
+
return set()
|
|
326
|
+
text = "\n".join(lines)
|
|
327
|
+
starts = _literal_line_starts(lines)
|
|
328
|
+
|
|
329
|
+
spanning: set[int] = set()
|
|
330
|
+
for start, end, _kind in _iter_sql_literals(text):
|
|
331
|
+
touched = [i for i, line_start in enumerate(starts) if start < line_start < end]
|
|
332
|
+
if not touched:
|
|
333
|
+
continue # single-line literal — nothing spans
|
|
334
|
+
spanning.update(touched)
|
|
335
|
+
spanning.add(min(touched) - 1) # the line that opens the literal
|
|
336
|
+
return spanning
|
|
337
|
+
|
|
338
|
+
|
|
281
339
|
# Placeholder standing in for a masked E'...' literal. It is a plain literal
|
|
282
340
|
# so that sqlglot sees a string where a string was, and lays the statement out
|
|
283
341
|
# the same way it would have for the real one.
|
|
@@ -398,7 +456,21 @@ def _sqlglot_format(
|
|
|
398
456
|
|
|
399
457
|
try:
|
|
400
458
|
with contextlib.redirect_stderr(io.StringIO()):
|
|
401
|
-
|
|
459
|
+
# Parse with RAISE, not IGNORE. Under IGNORE sqlglot answers a
|
|
460
|
+
# statement it cannot parse with a *partial* AST — the tokens it
|
|
461
|
+
# failed on are simply absent — and regenerating from that AST
|
|
462
|
+
# writes back SQL that no longer means what the author wrote.
|
|
463
|
+
# ``INSERT ... CROSS JOIN LATERAL (VALUES ...) AS g(a, b) ON
|
|
464
|
+
# CONFLICT (a) DO NOTHING`` came back with ``DO NOTHING`` deleted:
|
|
465
|
+
# still one statement, still 88% of the input's alphanumeric
|
|
466
|
+
# characters, no string literals to compare, so every guard below
|
|
467
|
+
# passed it through. The guards are quantitative and this loss is
|
|
468
|
+
# qualitative — a dropped ``DISTINCT``, ``CASCADE``, ``DESC`` or
|
|
469
|
+
# ``NOT`` is smaller still, and no threshold separates those from
|
|
470
|
+
# ordinary reformatting. Only the parser knows whether it
|
|
471
|
+
# understood the whole statement, so ask it: input that does not
|
|
472
|
+
# parse is left exactly as written.
|
|
473
|
+
ast = sqlglot.parse(protected, read="postgres", error_level=sqlglot_errors.ErrorLevel.RAISE)
|
|
402
474
|
statements: list[str] = []
|
|
403
475
|
for node in ast:
|
|
404
476
|
if node is None:
|
|
@@ -466,6 +538,10 @@ def _sqlglot_format(
|
|
|
466
538
|
if restored is None:
|
|
467
539
|
return sql_lines
|
|
468
540
|
return _restore_variables(restored, replacements).split("\n")
|
|
541
|
+
except sqlglot_errors.ParseError:
|
|
542
|
+
# Unparsable input: anything generated from a partial parse is
|
|
543
|
+
# untrustworthy, so emit the original lines untouched.
|
|
544
|
+
return sql_lines
|
|
469
545
|
except Exception:
|
|
470
546
|
return sql_lines
|
|
471
547
|
|
|
@@ -503,6 +579,14 @@ def _has_mid_statement_comments(lines: list[str]) -> bool:
|
|
|
503
579
|
|
|
504
580
|
_CMT_MARKER = "EXECSQL_CMTMARKER_"
|
|
505
581
|
_CMT_MARKER_RE = re.compile(rf"/\*\s*({re.escape(_CMT_MARKER)}\d+)\s*\*/")
|
|
582
|
+
# A whole run of adjacent markers plus the whitespace injected around them.
|
|
583
|
+
# Markers are written as ``/* marker */ line`` and several consecutive source
|
|
584
|
+
# comments attach several markers to one line, so removing them one at a time
|
|
585
|
+
# leaves a separator space behind for each. When sqlglot moves the run into
|
|
586
|
+
# the middle of an expression that residue shows up as ``AND RTRIM(`` and is
|
|
587
|
+
# only normalised on the following pass, which costs an extra pass to
|
|
588
|
+
# converge. Matching the run as a unit removes it cleanly. See issue #35.
|
|
589
|
+
_CMT_MARKER_RUN_RE = re.compile(rf"\s*(?:/\*\s*{re.escape(_CMT_MARKER)}\d+\s*\*/\s*)+")
|
|
506
590
|
|
|
507
591
|
|
|
508
592
|
def _format_preserving_comments(
|
|
@@ -570,12 +654,16 @@ def _format_preserving_comments(
|
|
|
570
654
|
markers_here = _CMT_MARKER_RE.findall(fline)
|
|
571
655
|
if markers_here:
|
|
572
656
|
# Strip markers to get the underlying SQL line and its indent
|
|
573
|
-
cleaned =
|
|
574
|
-
#
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
657
|
+
cleaned = _CMT_MARKER_RUN_RE.sub(" ", fline).strip()
|
|
658
|
+
# Indent comes from the line's own leading whitespace, never from
|
|
659
|
+
# what is left after removing the marker. Markers are injected as
|
|
660
|
+
# ``/* marker */ line`` — with a separating space — so measuring
|
|
661
|
+
# after removal counts that space as indentation. Whenever
|
|
662
|
+
# sqlglot leaves the line untouched (an unparsable statement falls
|
|
663
|
+
# back verbatim) the extra space is re-measured and re-added on the
|
|
664
|
+
# next run, growing the indent by one on every pass and never
|
|
665
|
+
# converging. See issue #35.
|
|
666
|
+
line_indent = fline[: len(fline) - len(fline.lstrip())]
|
|
579
667
|
for m in markers_here:
|
|
580
668
|
if m in comment_store:
|
|
581
669
|
orig = comment_store[m]
|
|
@@ -671,12 +759,24 @@ def format_sql_block(
|
|
|
671
759
|
if not non_empty:
|
|
672
760
|
return [""] * len(lines)
|
|
673
761
|
|
|
674
|
-
|
|
762
|
+
# Lines that begin inside a multi-line literal carry content, not layout —
|
|
763
|
+
# dedenting or indenting them changes the value the database stores.
|
|
764
|
+
# Exclude them from the indent arithmetic and emit them verbatim.
|
|
765
|
+
interior = _literal_interior_lines(lines)
|
|
766
|
+
if interior:
|
|
767
|
+
# format_file already withholds dollar-quoted blocks from sqlglot; keep
|
|
768
|
+
# that true here so the line indices below stay aligned with the output.
|
|
769
|
+
use_sql = False
|
|
770
|
+
|
|
771
|
+
layout_lines = [line for i, line in enumerate(lines) if line.strip() and i not in interior]
|
|
772
|
+
base = min((len(line) - len(line.lstrip()) for line in layout_lines), default=0)
|
|
675
773
|
target_prefix = " " * (depth * indent)
|
|
676
|
-
rebased = [line[base:] if line.strip() else "" for line in lines]
|
|
774
|
+
rebased = [line if i in interior else (line[base:] if line.strip() else "") for i, line in enumerate(lines)]
|
|
677
775
|
|
|
678
776
|
if not use_sql:
|
|
679
|
-
return [
|
|
777
|
+
return [
|
|
778
|
+
line if i in interior else (target_prefix + line if line.strip() else "") for i, line in enumerate(rebased)
|
|
779
|
+
]
|
|
680
780
|
|
|
681
781
|
# When comments appear mid-statement, use the marker-based approach
|
|
682
782
|
# which preserves both comments AND sqlglot formatting. When all
|
|
@@ -747,6 +847,9 @@ def _normalize_to_trailing_comma(text: str) -> str:
|
|
|
747
847
|
every invocation. Comments between the two SQL lines stay in place.
|
|
748
848
|
"""
|
|
749
849
|
lines = text.split("\n")
|
|
850
|
+
# Symmetric with _apply_leading_comma: a leading comma inside a multi-line
|
|
851
|
+
# literal is data, not layout, so the whole span is left alone.
|
|
852
|
+
protected = _literal_spanning_lines(lines)
|
|
750
853
|
|
|
751
854
|
def find_prev_sql_line(idx: int) -> int:
|
|
752
855
|
k = idx - 1
|
|
@@ -756,7 +859,7 @@ def _normalize_to_trailing_comma(text: str) -> str:
|
|
|
756
859
|
|
|
757
860
|
for i, line in enumerate(lines):
|
|
758
861
|
stripped = line.lstrip()
|
|
759
|
-
if not stripped.startswith(",") or _is_comment_only(line):
|
|
862
|
+
if not stripped.startswith(",") or _is_comment_only(line) or i in protected:
|
|
760
863
|
continue
|
|
761
864
|
# Don't try to rewrite leading-comma inside a comment-only line.
|
|
762
865
|
# Move the comma onto the previous SQL line as a trailing `,`.
|
|
@@ -792,15 +895,19 @@ def _apply_leading_comma(text: str) -> str:
|
|
|
792
895
|
TestIdempotency).
|
|
793
896
|
"""
|
|
794
897
|
lines = text.split("\n")
|
|
898
|
+
# A comma inside a multi-line string literal is data. Moving it to the next
|
|
899
|
+
# line would rewrite the stored value, so leave every line such a literal
|
|
900
|
+
# touches — including the one that opens it — exactly as it is.
|
|
901
|
+
protected = _literal_spanning_lines(lines)
|
|
795
902
|
i = 0
|
|
796
903
|
n = len(lines)
|
|
797
904
|
while i < n:
|
|
798
905
|
rstripped = lines[i].rstrip()
|
|
799
|
-
if rstripped.endswith(",") and not _is_comment_only(lines[i]):
|
|
906
|
+
if rstripped.endswith(",") and not _is_comment_only(lines[i]) and i not in protected:
|
|
800
907
|
j = i + 1
|
|
801
908
|
while j < n and (not lines[j].strip() or _is_comment_only(lines[j])):
|
|
802
909
|
j += 1
|
|
803
|
-
if j < n:
|
|
910
|
+
if j < n and j not in protected:
|
|
804
911
|
stripped = lines[i].rstrip()
|
|
805
912
|
comma_idx = stripped.rfind(",")
|
|
806
913
|
lines[i] = stripped[:comma_idx] + stripped[comma_idx + 1 :]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.22.
|
|
3
|
+
Version: 2.22.7
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -384,7 +384,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
384
384
|
```yaml
|
|
385
385
|
repos:
|
|
386
386
|
- repo: https://github.com/geocoug/execsql
|
|
387
|
-
rev: v2.22.
|
|
387
|
+
rev: v2.22.7
|
|
388
388
|
hooks:
|
|
389
389
|
- id: execsql-format
|
|
390
390
|
```
|
|
@@ -3,7 +3,7 @@ execsql/__main__.py,sha256=HdbK-SAhyUmfB6xINY5AzxdMSxGzWSGEG_2dv42Jn64,315
|
|
|
3
3
|
execsql/api.py,sha256=EOtBwMnm-8fmB_KmvxyjglR6C7CjdYhCPBisJ9CFfJg,22227
|
|
4
4
|
execsql/config.py,sha256=txKziUJLV7UPonj2sGFgkmlinDdSV50aHmR_cuCbEIo,34369
|
|
5
5
|
execsql/exceptions.py,sha256=GlphAiR8iZu4UR4U50WgSqEUMd9_8AeQJ_hhPcJOeQU,9381
|
|
6
|
-
execsql/format.py,sha256=
|
|
6
|
+
execsql/format.py,sha256=oJvAxDaWi-TQAnj_enwuEH-wtjZylKf67QWwHrSNEUU,47254
|
|
7
7
|
execsql/models.py,sha256=-DWTVrPNwM-n1h3e3rXMoBv9-wpWVdZahyy4RB7fRRE,15502
|
|
8
8
|
execsql/parser.py,sha256=aOxZ7Mzgvk7G4-nvy9Bgz7xlaZm6oo81cffDeBk_LCk,15597
|
|
9
9
|
execsql/plugins.py,sha256=l82VWa5NpvOQWD6vcO-WKP-vtn0wp9__wWDFAbwxZg0,12838
|
|
@@ -101,23 +101,23 @@ execsql/utils/numeric.py,sha256=_7bi5R6pHjMCkOPne_Sh5nUj-B8e9g6WFmjptFHaunA,1573
|
|
|
101
101
|
execsql/utils/regex.py,sha256=B3mSBhRRx_DpUnX_t9IoYd05KvWAebiLMCZqQAmEwcw,3885
|
|
102
102
|
execsql/utils/strings.py,sha256=UQNjpRCEFa1UO6feU-M-9e24wWAvizs_iu_4fFusLxo,8516
|
|
103
103
|
execsql/utils/timer.py,sha256=eDYf5VzCNFk7oo90InJucUm3XcBdhYMogjZMqeg9xzc,1899
|
|
104
|
-
execsql2-2.22.
|
|
105
|
-
execsql2-2.22.
|
|
106
|
-
execsql2-2.22.
|
|
107
|
-
execsql2-2.22.
|
|
108
|
-
execsql2-2.22.
|
|
109
|
-
execsql2-2.22.
|
|
110
|
-
execsql2-2.22.
|
|
111
|
-
execsql2-2.22.
|
|
112
|
-
execsql2-2.22.
|
|
113
|
-
execsql2-2.22.
|
|
114
|
-
execsql2-2.22.
|
|
115
|
-
execsql2-2.22.
|
|
116
|
-
execsql2-2.22.
|
|
117
|
-
execsql2-2.22.
|
|
118
|
-
execsql2-2.22.
|
|
119
|
-
execsql2-2.22.
|
|
120
|
-
execsql2-2.22.
|
|
121
|
-
execsql2-2.22.
|
|
122
|
-
execsql2-2.22.
|
|
123
|
-
execsql2-2.22.
|
|
104
|
+
execsql2-2.22.7.data/data/execsql2_extras/README.md,sha256=vX4NTL095dUoA_hesyRMGYBorEZ_Y_tJ9qrd-MVV09I,5032
|
|
105
|
+
execsql2-2.22.7.data/data/execsql2_extras/config_settings.sqlite,sha256=aY5cxR7Q7J6zJ4bC9lu5mHUrhy211Cq3MNKPQVCt02E,20480
|
|
106
|
+
execsql2-2.22.7.data/data/execsql2_extras/example_config_prompt.sql,sha256=2e8KzzVWhho8KxYVHETSVmZdhW7wodioDsqBLSL6m4s,7487
|
|
107
|
+
execsql2-2.22.7.data/data/execsql2_extras/make_config_db.sql,sha256=WwyC6dK-Eh5CAVppiBCDHqiI1_wEI9U95Ytpr4lsZkg,8726
|
|
108
|
+
execsql2-2.22.7.data/data/execsql2_extras/md_compare.sql,sha256=qYYVAjSeHZzjszxV3Bv6bg8Ckbq2kMHl87_gh4sywMU,24140
|
|
109
|
+
execsql2-2.22.7.data/data/execsql2_extras/md_glossary.sql,sha256=hkZ2Onn57LAKKsuXxzhR8tPtcWXkmWEQkwPE58-Tm2k,10796
|
|
110
|
+
execsql2-2.22.7.data/data/execsql2_extras/md_upsert.sql,sha256=_CAK4BzEboRXTNy03SJR-oOjcEdSNMuRBPL6noWUptY,112560
|
|
111
|
+
execsql2-2.22.7.data/data/execsql2_extras/pg_compare.sql,sha256=1zJd4hVUKHR0tncc2qTBC9B4qVV4Us2ITkJpsjN3tMw,24352
|
|
112
|
+
execsql2-2.22.7.data/data/execsql2_extras/pg_glossary.sql,sha256=IKuwna-_8b20ljSkXZruuiQigrCpo7ueQdUqd1MXiuI,9908
|
|
113
|
+
execsql2-2.22.7.data/data/execsql2_extras/pg_upsert.sql,sha256=HpPJtTHvpEjQy03j-3iPxDEOHMRkudOg7O4D4YR38UI,108315
|
|
114
|
+
execsql2-2.22.7.data/data/execsql2_extras/script_template.sql,sha256=2J35ddZPguJ-vwTsz83wErv0jiWUyJcdW_JM0mNKDXA,11155
|
|
115
|
+
execsql2-2.22.7.data/data/execsql2_extras/ss_compare.sql,sha256=j1qVNUPXQsEU7-DoVgDJCGcE0EuIl7whLBT3fgeiMAo,24833
|
|
116
|
+
execsql2-2.22.7.data/data/execsql2_extras/ss_glossary.sql,sha256=2gLxv34xzKt0vy7hSzJH7a9JiMC3ETrv9MofxQwAibU,13065
|
|
117
|
+
execsql2-2.22.7.data/data/execsql2_extras/ss_upsert.sql,sha256=G_8rQ0VzuKIZHWs24O_WrfzpC5S27R1JsL-bFBR3SUQ,117730
|
|
118
|
+
execsql2-2.22.7.dist-info/METADATA,sha256=DbIBWCsTnJIRQx1h0UKA9kB5kXFMybxn7U9j-xWG8P4,20382
|
|
119
|
+
execsql2-2.22.7.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
|
|
120
|
+
execsql2-2.22.7.dist-info/entry_points.txt,sha256=sUOxkM-dN1eBGGpSpDLsAaE0yNXYQKWZAfxPOlMkQyk,90
|
|
121
|
+
execsql2-2.22.7.dist-info/licenses/LICENSE.txt,sha256=LBdhuxejF8_bLCHZ2kWfmDXpDGUu914Gbd6_3JjCRe0,676
|
|
122
|
+
execsql2-2.22.7.dist-info/licenses/NOTICE,sha256=McYzgxYav3U1OaVsY4Su1sfBrfmplpRdA9b6-gCDQCg,342
|
|
123
|
+
execsql2-2.22.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{execsql2-2.22.5.data → execsql2-2.22.7.data}/data/execsql2_extras/example_config_prompt.sql
RENAMED
|
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
|