pycodegate 0.1.3__tar.gz → 0.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.
- {pycodegate-0.1.3 → pycodegate-0.2.0}/.github/workflows/ci.yml +2 -2
- {pycodegate-0.1.3 → pycodegate-0.2.0}/PKG-INFO +1 -1
- {pycodegate-0.1.3 → pycodegate-0.2.0}/pyproject.toml +1 -1
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/__init__.py +1 -1
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/cli.py +14 -2
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/output.py +62 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/security.py +94 -0
- pycodegate-0.2.0/tests/rules/test_security_new.py +32 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_cli.py +11 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/.github/workflows/publish.yml +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/.gitignore +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/AGENTS.md +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/LICENSE +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/README.md +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/docs/superpowers/plans/2026-03-22-python-doctor.md +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/skills/pycodegate/SKILL.md +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/_compat.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/api.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/config.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/constants.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/discover.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/profile.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/__init__.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/architecture.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/base.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/complexity.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/correctness.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/dead_code.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/dependencies.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/django.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/fastapi.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/flask.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/imports.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/performance.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/rules/structure.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/scan.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/score.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/types.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/__init__.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/ast_helpers.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/badge.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/diff.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/file_discovery.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/fixer.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/src/pycodegate/utils/precommit.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/conftest.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/fixtures/basic_python/app.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/fixtures/clean_project/app.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/__init__.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_architecture.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_complexity.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_correctness.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_dead_code.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_dependencies.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_django.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_fastapi.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_flask.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_imports.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_performance.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_security.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/rules/test_structure.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_config.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_discover.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_fixer.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_integration.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_output.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_profile.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_scan.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_score.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/tests/test_types.py +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/uv.lock +0 -0
- {pycodegate-0.1.3 → pycodegate-0.2.0}/whitelist.py +0 -0
|
@@ -26,8 +26,8 @@ jobs:
|
|
|
26
26
|
- name: Run tests
|
|
27
27
|
run: uv run pytest --tb=short -q
|
|
28
28
|
|
|
29
|
-
- name: Run
|
|
30
|
-
run: uv run
|
|
29
|
+
- name: Run pycodegate on itself
|
|
30
|
+
run: uv run pycodegate . --fail-on error
|
|
31
31
|
|
|
32
32
|
lint:
|
|
33
33
|
runs-on: ubuntu-latest
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycodegate
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Trust, but verify. The quality gate for AI-generated Python code.
|
|
5
5
|
Project-URL: Homepage, https://github.com/themohitkhare/pycodegate
|
|
6
6
|
Project-URL: Repository, https://github.com/themohitkhare/pycodegate
|
|
@@ -9,7 +9,7 @@ import click
|
|
|
9
9
|
|
|
10
10
|
from pycodegate import __version__
|
|
11
11
|
from pycodegate.config import load_config
|
|
12
|
-
from pycodegate.output import output_json, print_scan_result
|
|
12
|
+
from pycodegate.output import output_json, output_sarif, print_scan_result
|
|
13
13
|
from pycodegate.scan import scan_project
|
|
14
14
|
from pycodegate.types import Severity
|
|
15
15
|
from pycodegate.utils.badge import generate_badge, generate_ci_workflow
|
|
@@ -35,6 +35,7 @@ class CliOptions:
|
|
|
35
35
|
ci_workflow: bool
|
|
36
36
|
precommit: bool
|
|
37
37
|
min_score: int | None
|
|
38
|
+
sarif_output: bool
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
def _handle_early_exits(opts: CliOptions) -> bool:
|
|
@@ -81,7 +82,9 @@ def _output_result(opts: CliOptions, result) -> None:
|
|
|
81
82
|
click.echo(generate_badge(result.score.value, result.score.label))
|
|
82
83
|
return
|
|
83
84
|
|
|
84
|
-
if opts.
|
|
85
|
+
if opts.sarif_output:
|
|
86
|
+
output_sarif(result)
|
|
87
|
+
elif opts.json_output:
|
|
85
88
|
output_json(result)
|
|
86
89
|
elif opts.score_only:
|
|
87
90
|
click.echo(str(result.score.value))
|
|
@@ -152,6 +155,13 @@ def _apply_exit_codes(opts: CliOptions, result) -> None:
|
|
|
152
155
|
default=None,
|
|
153
156
|
help="Minimum score threshold (exit 1 if below).",
|
|
154
157
|
)
|
|
158
|
+
@click.option(
|
|
159
|
+
"--sarif",
|
|
160
|
+
"sarif_output",
|
|
161
|
+
is_flag=True,
|
|
162
|
+
default=False,
|
|
163
|
+
help="Output in SARIF format for GitHub Code Scanning.",
|
|
164
|
+
)
|
|
155
165
|
def main(
|
|
156
166
|
directory: str,
|
|
157
167
|
lint: bool,
|
|
@@ -167,6 +177,7 @@ def main(
|
|
|
167
177
|
ci_workflow: bool,
|
|
168
178
|
precommit: bool,
|
|
169
179
|
min_score: int | None,
|
|
180
|
+
sarif_output: bool,
|
|
170
181
|
) -> None:
|
|
171
182
|
"""PyCodeGate — Diagnose your Python project's health."""
|
|
172
183
|
opts = CliOptions(
|
|
@@ -184,6 +195,7 @@ def main(
|
|
|
184
195
|
ci_workflow=ci_workflow,
|
|
185
196
|
precommit=precommit,
|
|
186
197
|
min_score=min_score,
|
|
198
|
+
sarif_output=sarif_output,
|
|
187
199
|
)
|
|
188
200
|
|
|
189
201
|
if _handle_early_exits(opts):
|
|
@@ -213,6 +213,68 @@ def output_json(result: ScanResult) -> None:
|
|
|
213
213
|
sys.stdout.write(json.dumps(payload, indent=2) + "\n")
|
|
214
214
|
|
|
215
215
|
|
|
216
|
+
def _sarif_rules(diagnostics: list[Diagnostic]) -> list[dict]:
|
|
217
|
+
"""Build SARIF rules registry from unique diagnostic rules."""
|
|
218
|
+
seen: dict[str, Diagnostic] = {}
|
|
219
|
+
for d in diagnostics:
|
|
220
|
+
if d.rule not in seen:
|
|
221
|
+
seen[d.rule] = d
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
"id": rule_id,
|
|
225
|
+
"name": rule_id,
|
|
226
|
+
"shortDescription": {"text": diag.message},
|
|
227
|
+
"helpUri": "",
|
|
228
|
+
"help": {"text": diag.help},
|
|
229
|
+
}
|
|
230
|
+
for rule_id, diag in seen.items()
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _sarif_results(diagnostics: list[Diagnostic]) -> list[dict]:
|
|
235
|
+
"""Build SARIF results array from diagnostics."""
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
"ruleId": d.rule,
|
|
239
|
+
"level": "error" if d.severity == Severity.ERROR else "warning",
|
|
240
|
+
"message": {"text": d.message},
|
|
241
|
+
"locations": [
|
|
242
|
+
{
|
|
243
|
+
"physicalLocation": {
|
|
244
|
+
"artifactLocation": {"uri": d.file_path},
|
|
245
|
+
"region": {
|
|
246
|
+
"startLine": d.line,
|
|
247
|
+
"startColumn": (d.column or 0) + 1,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
}
|
|
253
|
+
for d in diagnostics
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def output_sarif(result: ScanResult) -> None:
|
|
258
|
+
"""Output the scan result in SARIF 2.1.0 format for GitHub Code Scanning."""
|
|
259
|
+
sarif = {
|
|
260
|
+
"version": "2.1.0",
|
|
261
|
+
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
262
|
+
"runs": [
|
|
263
|
+
{
|
|
264
|
+
"tool": {
|
|
265
|
+
"driver": {
|
|
266
|
+
"name": "PyCodeGate",
|
|
267
|
+
"version": __version__,
|
|
268
|
+
"rules": _sarif_rules(result.diagnostics),
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
"results": _sarif_results(result.diagnostics),
|
|
272
|
+
}
|
|
273
|
+
],
|
|
274
|
+
}
|
|
275
|
+
sys.stdout.write(json.dumps(sarif, indent=2) + "\n")
|
|
276
|
+
|
|
277
|
+
|
|
216
278
|
def _print_rule_details(
|
|
217
279
|
console: Console,
|
|
218
280
|
rule_diags: list[Diagnostic],
|
|
@@ -33,6 +33,9 @@ class SecurityRules(BaseRules):
|
|
|
33
33
|
diags.extend(self._check_yaml(tree, filename))
|
|
34
34
|
diags.extend(self._check_hardcoded_secrets(tree, filename))
|
|
35
35
|
diags.extend(self._check_weak_hash(tree, filename))
|
|
36
|
+
diags.extend(self._check_os_system(tree, filename))
|
|
37
|
+
diags.extend(self._check_subprocess_shell(tree, filename))
|
|
38
|
+
diags.extend(self._check_tempfile_mktemp(tree, filename))
|
|
36
39
|
return diags
|
|
37
40
|
|
|
38
41
|
def _check_eval_exec(self, tree: ast.Module, filename: str) -> list[Diagnostic]:
|
|
@@ -142,6 +145,97 @@ class SecurityRules(BaseRules):
|
|
|
142
145
|
)
|
|
143
146
|
return diags
|
|
144
147
|
|
|
148
|
+
def _check_os_system(self, tree: ast.Module, filename: str) -> list[Diagnostic]:
|
|
149
|
+
diags: list[Diagnostic] = []
|
|
150
|
+
for node in ast.walk(tree):
|
|
151
|
+
if not isinstance(node, ast.Call):
|
|
152
|
+
continue
|
|
153
|
+
if not isinstance(node.func, ast.Attribute):
|
|
154
|
+
continue
|
|
155
|
+
if node.func.attr != "system":
|
|
156
|
+
continue
|
|
157
|
+
if not isinstance(node.func.value, ast.Name):
|
|
158
|
+
continue
|
|
159
|
+
if node.func.value.id != "os":
|
|
160
|
+
continue
|
|
161
|
+
diags.append(
|
|
162
|
+
Diagnostic(
|
|
163
|
+
file_path=filename,
|
|
164
|
+
rule="no-os-system",
|
|
165
|
+
severity=Severity.ERROR,
|
|
166
|
+
category=Category.SECURITY,
|
|
167
|
+
message="os.system() is vulnerable to shell injection",
|
|
168
|
+
help="Use subprocess.run() with a list of arguments instead",
|
|
169
|
+
line=node.lineno,
|
|
170
|
+
column=node.col_offset,
|
|
171
|
+
cost=4.0,
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
return diags
|
|
175
|
+
|
|
176
|
+
def _check_subprocess_shell(self, tree: ast.Module, filename: str) -> list[Diagnostic]:
|
|
177
|
+
_subprocess_funcs = {"run", "Popen", "call", "check_call", "check_output"}
|
|
178
|
+
diags: list[Diagnostic] = []
|
|
179
|
+
for node in ast.walk(tree):
|
|
180
|
+
if not isinstance(node, ast.Call):
|
|
181
|
+
continue
|
|
182
|
+
if not isinstance(node.func, ast.Attribute):
|
|
183
|
+
continue
|
|
184
|
+
if node.func.attr not in _subprocess_funcs:
|
|
185
|
+
continue
|
|
186
|
+
if not isinstance(node.func.value, ast.Name):
|
|
187
|
+
continue
|
|
188
|
+
if node.func.value.id != "subprocess":
|
|
189
|
+
continue
|
|
190
|
+
shell_true = any(
|
|
191
|
+
kw.arg == "shell" and isinstance(kw.value, ast.Constant) and kw.value.value is True
|
|
192
|
+
for kw in node.keywords
|
|
193
|
+
)
|
|
194
|
+
if not shell_true:
|
|
195
|
+
continue
|
|
196
|
+
diags.append(
|
|
197
|
+
Diagnostic(
|
|
198
|
+
file_path=filename,
|
|
199
|
+
rule="no-subprocess-shell",
|
|
200
|
+
severity=Severity.ERROR,
|
|
201
|
+
category=Category.SECURITY,
|
|
202
|
+
message="subprocess called with shell=True is a security risk",
|
|
203
|
+
help="Pass arguments as a list and remove shell=True",
|
|
204
|
+
line=node.lineno,
|
|
205
|
+
column=node.col_offset,
|
|
206
|
+
cost=4.0,
|
|
207
|
+
)
|
|
208
|
+
)
|
|
209
|
+
return diags
|
|
210
|
+
|
|
211
|
+
def _check_tempfile_mktemp(self, tree: ast.Module, filename: str) -> list[Diagnostic]:
|
|
212
|
+
diags: list[Diagnostic] = []
|
|
213
|
+
for node in ast.walk(tree):
|
|
214
|
+
if not isinstance(node, ast.Call):
|
|
215
|
+
continue
|
|
216
|
+
if not isinstance(node.func, ast.Attribute):
|
|
217
|
+
continue
|
|
218
|
+
if node.func.attr != "mktemp":
|
|
219
|
+
continue
|
|
220
|
+
if not isinstance(node.func.value, ast.Name):
|
|
221
|
+
continue
|
|
222
|
+
if node.func.value.id != "tempfile":
|
|
223
|
+
continue
|
|
224
|
+
diags.append(
|
|
225
|
+
Diagnostic(
|
|
226
|
+
file_path=filename,
|
|
227
|
+
rule="no-tempfile-race",
|
|
228
|
+
severity=Severity.WARNING,
|
|
229
|
+
category=Category.SECURITY,
|
|
230
|
+
message="tempfile.mktemp() is deprecated and vulnerable to race conditions",
|
|
231
|
+
help="Use tempfile.mkstemp() or tempfile.NamedTemporaryFile()",
|
|
232
|
+
line=node.lineno,
|
|
233
|
+
column=node.col_offset,
|
|
234
|
+
cost=2.0,
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
return diags
|
|
238
|
+
|
|
145
239
|
def _check_weak_hash(self, tree: ast.Module, filename: str) -> list[Diagnostic]:
|
|
146
240
|
diags: list[Diagnostic] = []
|
|
147
241
|
for node in ast.walk(tree):
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Tests for new security rules: os.system, subprocess shell=True, tempfile.mktemp."""
|
|
2
|
+
|
|
3
|
+
from pycodegate.rules.security import SecurityRules
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_os_system_detected():
|
|
7
|
+
diags = SecurityRules().check("import os\nos.system('ls')\n", "app.py")
|
|
8
|
+
assert any(d.rule == "no-os-system" for d in diags)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_subprocess_shell_true():
|
|
12
|
+
source = "import subprocess\nsubprocess.run('ls', shell=True)\n"
|
|
13
|
+
diags = SecurityRules().check(source, "app.py")
|
|
14
|
+
assert any(d.rule == "no-subprocess-shell" for d in diags)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_subprocess_shell_false_ok():
|
|
18
|
+
source = "import subprocess\nsubprocess.run(['ls'], shell=False)\n"
|
|
19
|
+
diags = SecurityRules().check(source, "app.py")
|
|
20
|
+
assert not any(d.rule == "no-subprocess-shell" for d in diags)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_tempfile_mktemp():
|
|
24
|
+
source = "import tempfile\ntempfile.mktemp()\n"
|
|
25
|
+
diags = SecurityRules().check(source, "app.py")
|
|
26
|
+
assert any(d.rule == "no-tempfile-race" for d in diags)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_tempfile_mkstemp_ok():
|
|
30
|
+
source = "import tempfile\ntempfile.mkstemp()\n"
|
|
31
|
+
diags = SecurityRules().check(source, "app.py")
|
|
32
|
+
assert not any(d.rule == "no-tempfile-race" for d in diags)
|
|
@@ -124,3 +124,14 @@ def test_ci_workflow(tmp_path):
|
|
|
124
124
|
assert result.exit_code == 0
|
|
125
125
|
assert "name: Py Gate Score" in result.output
|
|
126
126
|
assert "pycodegate" in result.output
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_cli_sarif_output(tmp_path):
|
|
130
|
+
(tmp_path / "app.py").write_text("x = 1\n")
|
|
131
|
+
runner = CliRunner()
|
|
132
|
+
result = runner.invoke(main, [str(tmp_path), "--sarif"])
|
|
133
|
+
assert result.exit_code == 0
|
|
134
|
+
data = json.loads(result.output)
|
|
135
|
+
assert data["version"] == "2.1.0"
|
|
136
|
+
assert "runs" in data
|
|
137
|
+
assert data["runs"][0]["tool"]["driver"]["name"] == "PyCodeGate"
|
|
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
|
|
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
|