azdev 0.1.89__tar.gz → 0.1.92__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.
- {azdev-0.1.89 → azdev-0.1.92}/HISTORY.rst +13 -0
- {azdev-0.1.89/azdev.egg-info → azdev-0.1.92}/PKG-INFO +14 -1
- {azdev-0.1.89 → azdev-0.1.92}/azdev/__init__.py +1 -1
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/breaking_change/__init__.py +18 -6
- azdev-0.1.92/azdev/operations/breaking_change/markdown_template.jinja2 +37 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/constant.py +6 -1
- azdev-0.1.92/azdev/operations/linter/data/cmd_example_config.json +10 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/linter.py +72 -2
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/command_coverage_rules.py +9 -1
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/command_group_rules.py +5 -3
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/command_rules.py +5 -3
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/parameter_rules.py +5 -3
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/util.py +27 -1
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/regex.py +31 -2
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/testtool/__init__.py +17 -10
- {azdev-0.1.89 → azdev-0.1.92/azdev.egg-info}/PKG-INFO +14 -1
- {azdev-0.1.89 → azdev-0.1.92}/azdev.egg-info/SOURCES.txt +1 -0
- {azdev-0.1.89 → azdev-0.1.92}/setup.py +1 -0
- azdev-0.1.89/azdev/operations/breaking_change/markdown_template.jinja2 +0 -28
- {azdev-0.1.89 → azdev-0.1.92}/LICENSE +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/MANIFEST.in +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/README.md +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/README.rst +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/__main__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/commands.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/completer.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/config/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/config/cli.flake8 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/config/cli_pylintrc +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/config/ext.flake8 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/config/ext_pylintrc +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/help.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/HISTORY.rst +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/README.rst +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/_client_factory.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/_help.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/_params.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/_validators.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/azext_metadata.json +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/blank__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/commands.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/custom.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/module__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/pkg_declare__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/setup.cfg +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/setup.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/mod_templates/test_service_scenario.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/_macros.j2 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/cmdcov.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/component.css +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/component.js +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/favicon.ico +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/index.j2 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/index2.j2 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/cmdcov/module.j2 +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/code_gen.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/command_change/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/command_change/custom.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/command_change/util.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/extensions/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/extensions/util.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/extensions/version_upgrade.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/help/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/help/refdoc/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/help/refdoc/conf.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/legal.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/pylint_checkers/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/pylint_checkers/show_command.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rule_decorators.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/ci_exclusions.yml +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/help_rules.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/linter/rules/linter_exclusions.yml +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/performance.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/pypi.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/python_sdk.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/resource.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/secret.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/setup.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/statistics/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/statistics/util.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/style.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/testtool/incremental_strategy.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/testtool/profile_context.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/operations/testtool/pytest_runner.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/params.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/transformers.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/__init__.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/command.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/config.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/const.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/display.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/git_util.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/path.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/pypi.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/testing.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev/utilities/tools.py +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev.egg-info/dependency_links.txt +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev.egg-info/entry_points.txt +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev.egg-info/requires.txt +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/azdev.egg-info/top_level.txt +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/pyproject.toml +0 -0
- {azdev-0.1.89 → azdev-0.1.92}/setup.cfg +0 -0
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
Release History
|
|
4
4
|
===============
|
|
5
|
+
0.1.92
|
|
6
|
+
++++++
|
|
7
|
+
* `azdev linter`: Add `missing_command_example` rule
|
|
8
|
+
* `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
|
|
9
|
+
|
|
10
|
+
0.1.91
|
|
11
|
+
++++++
|
|
12
|
+
* `azdev generate-breaking-change-report`: Update report report template.
|
|
13
|
+
|
|
14
|
+
0.1.90
|
|
15
|
+
++++++
|
|
16
|
+
* `azdev cmdcov`: Fix incorrect detection of code changes as new commands
|
|
17
|
+
|
|
5
18
|
0.1.89
|
|
6
19
|
++++++
|
|
7
20
|
* `azdev scan/mask`: Add `--continue-on-failure` support
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: azdev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.92
|
|
4
4
|
Summary: Microsoft Azure CLI Developer Tools
|
|
5
5
|
Home-page: https://github.com/Azure/azure-cli-dev-tools
|
|
6
6
|
Author: Microsoft Corporation
|
|
@@ -148,6 +148,19 @@ License
|
|
|
148
148
|
|
|
149
149
|
Release History
|
|
150
150
|
===============
|
|
151
|
+
0.1.92
|
|
152
|
+
++++++
|
|
153
|
+
* `azdev linter`: Add `missing_command_example` rule
|
|
154
|
+
* `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
|
|
155
|
+
|
|
156
|
+
0.1.91
|
|
157
|
+
++++++
|
|
158
|
+
* `azdev generate-breaking-change-report`: Update report report template.
|
|
159
|
+
|
|
160
|
+
0.1.90
|
|
161
|
+
++++++
|
|
162
|
+
* `azdev cmdcov`: Fix incorrect detection of code changes as new commands
|
|
163
|
+
|
|
151
164
|
0.1.89
|
|
152
165
|
++++++
|
|
153
166
|
* `azdev scan/mask`: Add `--continue-on-failure` support
|
|
@@ -25,6 +25,15 @@ class BreakingChangeItem:
|
|
|
25
25
|
self.command = command
|
|
26
26
|
self.detail = detail
|
|
27
27
|
self.target_version = target_version
|
|
28
|
+
self.group_ref = None
|
|
29
|
+
|
|
30
|
+
def calc_ref(self, loader):
|
|
31
|
+
if not loader:
|
|
32
|
+
return
|
|
33
|
+
if self.command in loader.command_group_table:
|
|
34
|
+
self.group_ref = self.command.split()
|
|
35
|
+
else:
|
|
36
|
+
self.group_ref = self.command.split()[:-1]
|
|
28
37
|
|
|
29
38
|
|
|
30
39
|
def _load_commands():
|
|
@@ -247,7 +256,9 @@ def _handle_upcoming_breaking_changes(selected_mod_names, source):
|
|
|
247
256
|
yield from _handle_core(source)
|
|
248
257
|
|
|
249
258
|
for module, loader in _iter_and_prepare_module_loader(command_loader, selected_mod_names):
|
|
250
|
-
|
|
259
|
+
for bc_item in _handle_module(module, loader, source):
|
|
260
|
+
bc_item.calc_ref(loader)
|
|
261
|
+
yield bc_item
|
|
251
262
|
|
|
252
263
|
|
|
253
264
|
def _filter_breaking_changes(iterator, max_version=None):
|
|
@@ -276,18 +287,19 @@ def _group_breaking_change_items(iterator, group_by_version=False):
|
|
|
276
287
|
if group_by_version:
|
|
277
288
|
upcoming_breaking_changes = defaultdict( # module to command
|
|
278
289
|
lambda: defaultdict( # command to version
|
|
279
|
-
lambda: defaultdict( # version to list of breaking changes
|
|
280
|
-
lambda: [])))
|
|
290
|
+
lambda: {'group_ref': None, 'items': defaultdict( # version to list of breaking changes
|
|
291
|
+
lambda: [])}))
|
|
281
292
|
else:
|
|
282
293
|
upcoming_breaking_changes = defaultdict( # module to command
|
|
283
294
|
lambda: defaultdict( # command to list of breaking changes
|
|
284
|
-
lambda: []))
|
|
295
|
+
lambda: {'group_ref': None, 'items': []}))
|
|
285
296
|
for item in iterator:
|
|
286
297
|
version = item.target_version if item.target_version else 'Unspecific'
|
|
298
|
+
upcoming_breaking_changes[item.module][item.command]['group_ref'] = item.group_ref
|
|
287
299
|
if group_by_version:
|
|
288
|
-
upcoming_breaking_changes[item.module][item.command][version].append(item.detail)
|
|
300
|
+
upcoming_breaking_changes[item.module][item.command]['items'][version].append(item.detail)
|
|
289
301
|
else:
|
|
290
|
-
upcoming_breaking_changes[item.module][item.command].append(item.detail)
|
|
302
|
+
upcoming_breaking_changes[item.module][item.command]['items'].append(item.detail)
|
|
291
303
|
return upcoming_breaking_changes
|
|
292
304
|
|
|
293
305
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Upcoming breaking changes in Azure CLI
|
|
2
|
+
|
|
3
|
+
The breaking changes listed in this article are planned for the next major release of the Azure CLI unless otherwise noted. Per our [Support lifecycle](./azure-cli-support-lifecycle.md), breaking changes in Azure CLI Core reference groups occur twice a year.
|
|
4
|
+
|
|
5
|
+
{% for module, command_bc in module_bc.items() -%}
|
|
6
|
+
## {{ module }}
|
|
7
|
+
|
|
8
|
+
{% for command, multi_version_bcs in command_bc.items() -%}
|
|
9
|
+
{% if not (module == 'core' and command == 'core') -%}
|
|
10
|
+
### `{{ command }}`
|
|
11
|
+
|
|
12
|
+
{% endif -%}
|
|
13
|
+
{% if multi_version_bcs.group_ref -%}
|
|
14
|
+
[Link to {{ multi_version_bcs.group_ref|join(' ') }} reference group](/cli/azure/{{ multi_version_bcs.group_ref|join('/') }})
|
|
15
|
+
|
|
16
|
+
{% endif -%}
|
|
17
|
+
{% if multi_version_bcs['items'] is mapping -%}
|
|
18
|
+
{% for version, bcs in multi_version_bcs['items'] | dictsort -%}
|
|
19
|
+
###{%- if not (module == 'core' and command == 'core') -%}#{%- endif %} Deprecated in {{ version }}
|
|
20
|
+
|
|
21
|
+
{% for bc in bcs -%}
|
|
22
|
+
- {{ bc.detail }}
|
|
23
|
+
{% endfor %}
|
|
24
|
+
|
|
25
|
+
{% endfor -%}
|
|
26
|
+
{% else -%}
|
|
27
|
+
|
|
28
|
+
{% for bc in multi_version_bcs['items'] -%}
|
|
29
|
+
- {{ bc }}
|
|
30
|
+
{% endfor %}
|
|
31
|
+
|
|
32
|
+
{% endif -%}
|
|
33
|
+
{% endfor -%}
|
|
34
|
+
{% endfor -%}
|
|
35
|
+
|
|
36
|
+
> [!NOTE]
|
|
37
|
+
> This article provides information on upcoming breaking changes. For previously published breaking changes, see [Azure CLI release notes](./release-notes-azure-cli.md).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# license information.
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
6
|
# pylint: disable=line-too-long
|
|
7
|
-
|
|
7
|
+
import os
|
|
8
8
|
ENCODING = 'utf-8'
|
|
9
9
|
|
|
10
10
|
# Base on https://github.com/Azure/azure-cli/blob/dev/.github/CODEOWNERS
|
|
@@ -259,3 +259,8 @@ VERSION_PREVIEW_TAG = "preview"
|
|
|
259
259
|
PREVIEW_INIT_SUFFIX = "b1"
|
|
260
260
|
|
|
261
261
|
CLI_EXTENSION_INDEX_URL = "https://azcliextensionsync.blob.core.windows.net/index1/index.json"
|
|
262
|
+
|
|
263
|
+
CMD_EXAMPLE_CONFIG_FILE = "./data/cmd_example_config.json"
|
|
264
|
+
CMD_EXAMPLE_CONFIG_FILE_PATH = f"{os.path.dirname(os.path.realpath(__file__))}/linter/{CMD_EXAMPLE_CONFIG_FILE}"
|
|
265
|
+
CMD_EXAMPLE_CONFIG_FILE_URL = "https://azcmdchangemgmt.blob.core.windows.net/azure-cli-dev-tool-config/cmd_example_config.json"
|
|
266
|
+
CMD_EXAMPLE_DEFAULT = 1
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Licensed under the MIT License. See License.txt in the project root for
|
|
4
4
|
# license information.
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
|
-
|
|
6
|
+
# pylint: disable=line-too-long
|
|
7
7
|
from difflib import context_diff
|
|
8
8
|
from enum import Enum
|
|
9
9
|
from importlib import import_module
|
|
@@ -17,6 +17,7 @@ from knack.log import get_logger
|
|
|
17
17
|
|
|
18
18
|
from azdev.operations.regex import (
|
|
19
19
|
get_all_tested_commands_from_regex,
|
|
20
|
+
search_aaz_raw_command, search_aaz_custom_command,
|
|
20
21
|
search_argument,
|
|
21
22
|
search_argument_context,
|
|
22
23
|
search_command,
|
|
@@ -24,7 +25,8 @@ from azdev.operations.regex import (
|
|
|
24
25
|
search_command_group)
|
|
25
26
|
from azdev.utilities import diff_branches_detail, diff_branch_file_patch
|
|
26
27
|
from azdev.utilities.path import get_cli_repo_path, get_ext_repo_paths
|
|
27
|
-
from .util import share_element, exclude_commands, LinterError
|
|
28
|
+
from .util import (share_element, exclude_commands, LinterError, get_cmd_example_configurations,
|
|
29
|
+
get_cmd_example_threshold)
|
|
28
30
|
|
|
29
31
|
PACKAGE_NAME = 'azdev.operations.linter'
|
|
30
32
|
_logger = get_logger(__name__)
|
|
@@ -224,6 +226,30 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
224
226
|
all_tested_command = self._detect_tested_command(diff_index)
|
|
225
227
|
return self._run_parameter_test_coverage(parameters, all_tested_command)
|
|
226
228
|
|
|
229
|
+
def check_missing_command_example(self):
|
|
230
|
+
_exclude_commands = self._get_cmd_exclusions(rule_name="missing_command_example")
|
|
231
|
+
cmd_example_config = get_cmd_example_configurations()
|
|
232
|
+
commands = self._detect_modified_command()
|
|
233
|
+
violations = []
|
|
234
|
+
for cmd in commands:
|
|
235
|
+
if cmd in _exclude_commands:
|
|
236
|
+
continue
|
|
237
|
+
cmd_help = self._loaded_help.get(cmd, None)
|
|
238
|
+
if not cmd_help:
|
|
239
|
+
continue
|
|
240
|
+
# parameters = cmd_help.parameters
|
|
241
|
+
# add if future parameter set required
|
|
242
|
+
cmd_suffix = cmd.split()[-1]
|
|
243
|
+
cmd_example_threshold = get_cmd_example_threshold(cmd_suffix, cmd_example_config)
|
|
244
|
+
if cmd_example_threshold == 0:
|
|
245
|
+
continue
|
|
246
|
+
if not hasattr(cmd_help, "examples") or len(cmd_help.examples) < cmd_example_threshold:
|
|
247
|
+
violations.append(f'Command `{cmd}` should have at least {cmd_example_threshold} example(s)')
|
|
248
|
+
if violations:
|
|
249
|
+
violations.insert(0, 'Check command example failed.')
|
|
250
|
+
violations.extend(['Please add examples for the modified command or add the command in rule_exclusions: missing_command_example in linter_exclusions.yml'])
|
|
251
|
+
return violations
|
|
252
|
+
|
|
227
253
|
def _get_exclusions(self):
|
|
228
254
|
_exclude_commands = set()
|
|
229
255
|
_exclude_parameters = set()
|
|
@@ -238,6 +264,16 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
238
264
|
_logger.debug('exclude_comands: %s', _exclude_commands)
|
|
239
265
|
return _exclude_commands, _exclude_parameters
|
|
240
266
|
|
|
267
|
+
def _get_cmd_exclusions(self, rule_name=None):
|
|
268
|
+
_exclude_commands = set()
|
|
269
|
+
if not rule_name:
|
|
270
|
+
return _exclude_commands
|
|
271
|
+
for command, details in self.exclusions.items():
|
|
272
|
+
if 'rule_exclusions' in details and rule_name in details['rule_exclusions']:
|
|
273
|
+
_exclude_commands.add(command)
|
|
274
|
+
_logger.debug('exclude_commands: %s', _exclude_commands)
|
|
275
|
+
return _exclude_commands
|
|
276
|
+
|
|
241
277
|
def _split_path(self, path: str):
|
|
242
278
|
parts = path.rsplit('/', maxsplit=1)
|
|
243
279
|
return parts if len(parts) == 2 else ('', parts[0])
|
|
@@ -387,6 +423,40 @@ class Linter: # pylint: disable=too-many-public-methods, too-many-instance-attr
|
|
|
387
423
|
'Or add the parameter with missing_parameter_test_coverage rule in linter_exclusions.yml'])
|
|
388
424
|
return exec_state, violations
|
|
389
425
|
|
|
426
|
+
def _detect_modified_command(self):
|
|
427
|
+
modified_commands = set()
|
|
428
|
+
diff_patches = diff_branch_file_patch(repo=self.git_repo, target=self.git_target, source=self.git_source)
|
|
429
|
+
for change in diff_patches:
|
|
430
|
+
file_path, filename = self._split_path(change.a_path)
|
|
431
|
+
if "commands.py" not in filename and "aaz" not in file_path:
|
|
432
|
+
continue
|
|
433
|
+
current_lines = self._read_blob_lines(change.b_blob)
|
|
434
|
+
patch = change.diff.decode("utf-8")
|
|
435
|
+
patch_lines = patch.splitlines()
|
|
436
|
+
if 'commands.py' in filename:
|
|
437
|
+
added_lines = [line for line in patch_lines if line.startswith('+') and not line.startswith('+++')]
|
|
438
|
+
for line in added_lines:
|
|
439
|
+
if aaz_custom_command := search_aaz_custom_command(line):
|
|
440
|
+
modified_commands.add(aaz_custom_command)
|
|
441
|
+
|
|
442
|
+
for row_num, line in enumerate(patch_lines):
|
|
443
|
+
if not line.startswith("+") or line.startswith('+++'):
|
|
444
|
+
continue
|
|
445
|
+
manual_command_suffix = search_command(line)
|
|
446
|
+
if manual_command_suffix:
|
|
447
|
+
idx = self._get_line_number(patch_lines, row_num, r'@@ -(\d+),(?:\d+) \+(?:\d+),(?:\d+) @@')
|
|
448
|
+
manual_command = search_command_group(idx, current_lines, manual_command_suffix)
|
|
449
|
+
if manual_command:
|
|
450
|
+
modified_commands.add(manual_command)
|
|
451
|
+
|
|
452
|
+
if "aaz" in file_path:
|
|
453
|
+
if aaz_raw_command := search_aaz_raw_command(patch):
|
|
454
|
+
modified_commands.add(aaz_raw_command)
|
|
455
|
+
|
|
456
|
+
commands = list(modified_commands)
|
|
457
|
+
_logger.debug('Modified commands: %s', modified_commands)
|
|
458
|
+
return commands
|
|
459
|
+
|
|
390
460
|
def _get_diffed_patches(self):
|
|
391
461
|
if not self.git_source or not self.git_target or not self.git_repo:
|
|
392
462
|
return
|
|
@@ -8,7 +8,7 @@ from ..rule_decorators import CommandCoverageRule
|
|
|
8
8
|
from ..linter import RuleError, LinterSeverity
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
@CommandCoverageRule(LinterSeverity.
|
|
11
|
+
@CommandCoverageRule(LinterSeverity.MEDIUM)
|
|
12
12
|
def missing_command_test_coverage(linter):
|
|
13
13
|
exec_state, violations = linter.get_command_test_coverage()
|
|
14
14
|
if not exec_state:
|
|
@@ -22,3 +22,11 @@ def missing_parameter_test_coverage(linter):
|
|
|
22
22
|
if not exec_state:
|
|
23
23
|
violation_msg = "\n\t".join(violations)
|
|
24
24
|
raise RuleError(violation_msg + "\n")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@CommandCoverageRule(LinterSeverity.HIGH)
|
|
28
|
+
def missing_command_example(linter):
|
|
29
|
+
violations = linter.check_missing_command_example()
|
|
30
|
+
if violations:
|
|
31
|
+
violation_msg = "\n\t".join(violations)
|
|
32
|
+
raise RuleError(violation_msg + "\n")
|
|
@@ -45,7 +45,7 @@ def require_wait_command_if_no_wait(linter, command_group_name):
|
|
|
45
45
|
raise RuleError("Group does not have a 'wait' command, yet '{}' exposes '--no-wait'".format(cmd))
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
@CommandGroupRule(LinterSeverity.
|
|
48
|
+
@CommandGroupRule(LinterSeverity.HIGH)
|
|
49
49
|
def disallowed_html_tag_from_command_group(linter, command_group_name):
|
|
50
50
|
if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
|
|
51
51
|
return
|
|
@@ -69,9 +69,11 @@ def broken_site_link_from_command_group(linter, command_group_name):
|
|
|
69
69
|
if command_group_name == '' or not linter.get_loaded_help_entry(command_group_name):
|
|
70
70
|
return
|
|
71
71
|
help_entry = linter.get_loaded_help_entry(command_group_name)
|
|
72
|
-
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary
|
|
72
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
|
|
73
|
+
linter.diffed_lines)):
|
|
73
74
|
raise RuleError("Broken links {} in short summary. "
|
|
74
75
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
75
|
-
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary
|
|
76
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
|
|
77
|
+
linter.diffed_lines)):
|
|
76
78
|
raise RuleError("Broken links {} in long summary. "
|
|
77
79
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -40,7 +40,7 @@ def group_delete_commands_should_confirm(linter, command_name):
|
|
|
40
40
|
"Please make sure to ask for confirmation.")
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
@CommandRule(LinterSeverity.
|
|
43
|
+
@CommandRule(LinterSeverity.HIGH)
|
|
44
44
|
def disallowed_html_tag_from_command(linter, command_name):
|
|
45
45
|
if command_name == '' or not linter.get_loaded_help_entry(command_name):
|
|
46
46
|
return
|
|
@@ -64,9 +64,11 @@ def broken_site_link_from_command(linter, command_name):
|
|
|
64
64
|
if command_name == '' or not linter.get_loaded_help_entry(command_name):
|
|
65
65
|
return
|
|
66
66
|
help_entry = linter.get_loaded_help_entry(command_name)
|
|
67
|
-
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary
|
|
67
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
|
|
68
|
+
linter.diffed_lines)):
|
|
68
69
|
raise RuleError("Broken links {} in short summary. "
|
|
69
70
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
70
|
-
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary
|
|
71
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
|
|
72
|
+
linter.diffed_lines)):
|
|
71
73
|
raise RuleError("Broken links {} in long summary. "
|
|
72
74
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -174,7 +174,7 @@ def option_should_not_contain_under_score(linter, command_name, parameter_name):
|
|
|
174
174
|
raise RuleError("Argument's option {} contains '_' which should be '-' instead.".format(option))
|
|
175
175
|
|
|
176
176
|
|
|
177
|
-
@ParameterRule(LinterSeverity.
|
|
177
|
+
@ParameterRule(LinterSeverity.HIGH)
|
|
178
178
|
def disallowed_html_tag_from_parameter(linter, command_name, parameter_name):
|
|
179
179
|
if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
|
|
180
180
|
return
|
|
@@ -199,9 +199,11 @@ def broken_site_link_from_parameter(linter, command_name, parameter_name):
|
|
|
199
199
|
if linter.command_expired(command_name) or not linter.get_parameter_help_info(command_name, parameter_name):
|
|
200
200
|
return
|
|
201
201
|
help_entry = linter.get_parameter_help_info(command_name, parameter_name)
|
|
202
|
-
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary
|
|
202
|
+
if help_entry.short_summary and (broken_links := has_broken_site_links(help_entry.short_summary,
|
|
203
|
+
linter.diffed_lines)):
|
|
203
204
|
raise RuleError("Broken links {} in short summary. "
|
|
204
205
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
205
|
-
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary
|
|
206
|
+
if help_entry.long_summary and (broken_links := has_broken_site_links(help_entry.long_summary,
|
|
207
|
+
linter.diffed_lines)):
|
|
206
208
|
raise RuleError("Broken links {} in long summary. "
|
|
207
209
|
"If link is an example, please wrap it with backtick. ".format(broken_links))
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
import copy
|
|
8
8
|
import re
|
|
9
|
+
import os
|
|
10
|
+
import json
|
|
9
11
|
import requests
|
|
10
12
|
|
|
11
13
|
from knack.log import get_logger
|
|
12
14
|
|
|
13
15
|
from azdev.utilities import get_name_index
|
|
14
|
-
from azdev.operations.constant import ALLOWED_HTML_TAG
|
|
16
|
+
from azdev.operations.constant import (ALLOWED_HTML_TAG, CMD_EXAMPLE_CONFIG_FILE_URL,
|
|
17
|
+
CMD_EXAMPLE_CONFIG_FILE_PATH, CMD_EXAMPLE_DEFAULT)
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
logger = get_logger(__name__)
|
|
@@ -155,3 +158,26 @@ def has_broken_site_links(help_message, filtered_lines=None):
|
|
|
155
158
|
if filtered_lines:
|
|
156
159
|
invalid_urls = [s for s in invalid_urls if any(s in diff_line for diff_line in filtered_lines)]
|
|
157
160
|
return invalid_urls
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_cmd_example_configurations():
|
|
164
|
+
cmd_example_threshold = {}
|
|
165
|
+
remote_res = requests.get(CMD_EXAMPLE_CONFIG_FILE_URL)
|
|
166
|
+
if remote_res.status_code != 200:
|
|
167
|
+
logger.warning("remote cmd example configuration fetch error, use local dict")
|
|
168
|
+
if not os.path.exists(CMD_EXAMPLE_CONFIG_FILE_PATH):
|
|
169
|
+
logger.info("cmd_example_config.json not exist, skipped")
|
|
170
|
+
return cmd_example_threshold
|
|
171
|
+
with open(CMD_EXAMPLE_CONFIG_FILE_PATH, "r") as f_in:
|
|
172
|
+
cmd_example_threshold = json.load(f_in)
|
|
173
|
+
else:
|
|
174
|
+
logger.info("remote cmd example configuration fetch success")
|
|
175
|
+
cmd_example_threshold = remote_res.json()
|
|
176
|
+
return cmd_example_threshold
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_cmd_example_threshold(cmd_suffix, cmd_example_config):
|
|
180
|
+
for cmd_type, threshold in cmd_example_config.items():
|
|
181
|
+
if cmd_suffix.find(cmd_type) != -1:
|
|
182
|
+
return threshold
|
|
183
|
+
return CMD_EXAMPLE_DEFAULT
|
|
@@ -144,6 +144,7 @@ def search_argument(line):
|
|
|
144
144
|
def search_command_group(row_num, lines, command):
|
|
145
145
|
cmd = ''
|
|
146
146
|
while row_num > 0:
|
|
147
|
+
row_num = len(lines) - 1 if row_num >= len(lines) else row_num
|
|
147
148
|
row_num -= 1
|
|
148
149
|
# Match `with self.command_group('local-context',` and `with self.command_group('xxx')`
|
|
149
150
|
sub_pattern = r'with self.command_group\(\'(.*?)\',?'
|
|
@@ -166,9 +167,37 @@ def search_command(line):
|
|
|
166
167
|
|
|
167
168
|
def search_deleted_command(line):
|
|
168
169
|
command = ''
|
|
169
|
-
# Match
|
|
170
|
-
pattern = r'
|
|
170
|
+
# Match `[-!] g.*command(xxx)`
|
|
171
|
+
pattern = r'[-!]\s+g.(?:\w+)?command\((.*)\)'
|
|
171
172
|
ref = re.findall(pattern, line)
|
|
172
173
|
if ref:
|
|
173
174
|
command = ref[0].split(',')[0].strip("'")
|
|
174
175
|
return command
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def search_aaz_custom_command(line):
|
|
179
|
+
"""
|
|
180
|
+
re match pattern
|
|
181
|
+
+ self.command_table['monitor autoscale update'] = AutoScaleUpdate(loader=self)
|
|
182
|
+
"""
|
|
183
|
+
cmd = ''
|
|
184
|
+
aaz_custom_cmd_pattern = r"\+.*\.command_table\[['\"](.*?)['\"]\]"
|
|
185
|
+
ref = re.findall(aaz_custom_cmd_pattern, line)
|
|
186
|
+
if ref:
|
|
187
|
+
cmd = ref[0].strip()
|
|
188
|
+
return cmd
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def search_aaz_raw_command(lines):
|
|
192
|
+
"""
|
|
193
|
+
re match pattern
|
|
194
|
+
+@register_command(
|
|
195
|
+
+ "monitor autoscale update",
|
|
196
|
+
+)
|
|
197
|
+
"""
|
|
198
|
+
cmd = ''
|
|
199
|
+
aaz_raw_cmd_pattern = r"\+@register_command\([\s\S]*?\+.*?['\"](.*?)['\"]"
|
|
200
|
+
ref = re.findall(aaz_raw_cmd_pattern, str(lines))
|
|
201
|
+
if ref:
|
|
202
|
+
cmd = ref[0].strip()
|
|
203
|
+
return cmd
|
|
@@ -44,8 +44,7 @@ def run_tests(tests, xml_path=None, discover=False, in_series=False,
|
|
|
44
44
|
heading('Run Tests')
|
|
45
45
|
|
|
46
46
|
path_table = get_path_table()
|
|
47
|
-
|
|
48
|
-
test_index = _get_test_index(profile or current_profile(), discover)
|
|
47
|
+
target_tests = set()
|
|
49
48
|
|
|
50
49
|
if not tests:
|
|
51
50
|
tests = list(path_table['mod'].keys()) + list(path_table['core'].keys()) + list(path_table['ext'].keys())
|
|
@@ -53,6 +52,10 @@ def run_tests(tests, xml_path=None, discover=False, in_series=False,
|
|
|
53
52
|
tests = list(path_table['mod'].keys()) + list(path_table['core'].keys())
|
|
54
53
|
elif tests == ['EXT']:
|
|
55
54
|
tests = list(path_table['ext'].keys())
|
|
55
|
+
else:
|
|
56
|
+
target_tests = set(tests)
|
|
57
|
+
|
|
58
|
+
test_index = _get_test_index(profile or current_profile(), discover, target_tests=target_tests)
|
|
56
59
|
|
|
57
60
|
# filter out tests whose modules haven't changed
|
|
58
61
|
modified_mods = _filter_by_git_diff(tests, test_index, git_source, git_target, git_repo)
|
|
@@ -193,7 +196,7 @@ def _discover_module_tests(mod_name, mod_data):
|
|
|
193
196
|
|
|
194
197
|
|
|
195
198
|
# pylint: disable=too-many-statements, too-many-locals
|
|
196
|
-
def _discover_tests(profile):
|
|
199
|
+
def _discover_tests(profile, target_tests):
|
|
197
200
|
""" Builds an index of tests so that the user can simply supply the name they wish to test instead of the
|
|
198
201
|
full path.
|
|
199
202
|
"""
|
|
@@ -274,13 +277,17 @@ def _discover_tests(profile):
|
|
|
274
277
|
mod2 = extract_module_name(test_index[key])
|
|
275
278
|
if mod1 != mod2:
|
|
276
279
|
# resolve conflicted keys by prefixing with the module name and a dot (.)
|
|
277
|
-
|
|
278
|
-
|
|
280
|
+
if key in target_tests or mod1 in target_tests or mod2 in target_tests:
|
|
281
|
+
logger.warning("'%s' exists in both '%s' and '%s'. Resolve using `%s.%s` or `%s.%s`"
|
|
282
|
+
"Duplication exists in: \n\t%s\n\t%s\n",
|
|
283
|
+
key, mod1, mod2, mod1, key, mod2, key, path, test_index[key])
|
|
279
284
|
test_index['{}.{}'.format(mod1, key)] = path
|
|
280
285
|
test_index['{}.{}'.format(mod2, key)] = test_index[key]
|
|
281
286
|
else:
|
|
282
|
-
|
|
283
|
-
|
|
287
|
+
if key in target_tests or mod1 in target_tests:
|
|
288
|
+
logger.error("'%s' exists twice in the '%s' module. "
|
|
289
|
+
"Please rename one or both and re-run --discover. "
|
|
290
|
+
"Duplication exists in: \n\t%s\n\t%s\n", key, mod1, test_index[key], path)
|
|
284
291
|
else:
|
|
285
292
|
test_index[key] = path
|
|
286
293
|
|
|
@@ -310,14 +317,14 @@ def _discover_tests(profile):
|
|
|
310
317
|
return test_index
|
|
311
318
|
|
|
312
319
|
|
|
313
|
-
def _get_test_index(profile, discover):
|
|
320
|
+
def _get_test_index(profile, discover, target_tests):
|
|
314
321
|
config_dir = get_azdev_config_dir()
|
|
315
322
|
test_index_dir = os.path.join(config_dir, 'test_index')
|
|
316
323
|
make_dirs(test_index_dir)
|
|
317
324
|
test_index_path = os.path.join(test_index_dir, '{}.json'.format(profile))
|
|
318
325
|
test_index = {}
|
|
319
326
|
if discover:
|
|
320
|
-
test_index = _discover_tests(profile)
|
|
327
|
+
test_index = _discover_tests(profile, target_tests)
|
|
321
328
|
with open(test_index_path, 'w') as f:
|
|
322
329
|
f.write(json.dumps(test_index))
|
|
323
330
|
display('\ntest index updated: {}'.format(test_index_path))
|
|
@@ -326,7 +333,7 @@ def _get_test_index(profile, discover):
|
|
|
326
333
|
test_index = json.loads(''.join(f.readlines()))
|
|
327
334
|
display('\ntest index found: {}'.format(test_index_path))
|
|
328
335
|
else:
|
|
329
|
-
test_index = _discover_tests(profile)
|
|
336
|
+
test_index = _discover_tests(profile, target_tests)
|
|
330
337
|
with open(test_index_path, 'w') as f:
|
|
331
338
|
f.write(json.dumps(test_index))
|
|
332
339
|
display('\ntest index created: {}'.format(test_index_path))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: azdev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.92
|
|
4
4
|
Summary: Microsoft Azure CLI Developer Tools
|
|
5
5
|
Home-page: https://github.com/Azure/azure-cli-dev-tools
|
|
6
6
|
Author: Microsoft Corporation
|
|
@@ -148,6 +148,19 @@ License
|
|
|
148
148
|
|
|
149
149
|
Release History
|
|
150
150
|
===============
|
|
151
|
+
0.1.92
|
|
152
|
+
++++++
|
|
153
|
+
* `azdev linter`: Add `missing_command_example` rule
|
|
154
|
+
* `azdev linter`: Set `disallowed_html_tags` and `broken_site_link` detection in `high` severity to block future format issues
|
|
155
|
+
|
|
156
|
+
0.1.91
|
|
157
|
+
++++++
|
|
158
|
+
* `azdev generate-breaking-change-report`: Update report report template.
|
|
159
|
+
|
|
160
|
+
0.1.90
|
|
161
|
+
++++++
|
|
162
|
+
* `azdev cmdcov`: Fix incorrect detection of code changes as new commands
|
|
163
|
+
|
|
151
164
|
0.1.89
|
|
152
165
|
++++++
|
|
153
166
|
* `azdev scan/mask`: Add `--continue-on-failure` support
|
|
@@ -75,6 +75,7 @@ azdev/operations/linter/__init__.py
|
|
|
75
75
|
azdev/operations/linter/linter.py
|
|
76
76
|
azdev/operations/linter/rule_decorators.py
|
|
77
77
|
azdev/operations/linter/util.py
|
|
78
|
+
azdev/operations/linter/data/cmd_example_config.json
|
|
78
79
|
azdev/operations/linter/pylint_checkers/__init__.py
|
|
79
80
|
azdev/operations/linter/pylint_checkers/show_command.py
|
|
80
81
|
azdev/operations/linter/rules/__init__.py
|
|
@@ -93,6 +93,7 @@ setup(
|
|
|
93
93
|
'azdev.config': ['*.*', 'cli_pylintrc', 'ext_pylintrc'],
|
|
94
94
|
'azdev.mod_templates': ['*.*'],
|
|
95
95
|
'azdev.operations.linter.rules': ['ci_exclusions.yml'],
|
|
96
|
+
'azdev.operations.linter': ["data/*"],
|
|
96
97
|
'azdev.operations.cmdcov': ['*.*'],
|
|
97
98
|
'azdev.operations.breaking_change': ['*.*'],
|
|
98
99
|
},
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Upcoming breaking changes in Azure CLI
|
|
2
|
-
|
|
3
|
-
{% for module, command_bc in module_bc.items() -%}
|
|
4
|
-
## {{ module }}
|
|
5
|
-
|
|
6
|
-
{% for command, multi_version_bcs in command_bc.items() -%}
|
|
7
|
-
{% if not (module == 'core' and command == 'core') -%}
|
|
8
|
-
### `{{ command }}`
|
|
9
|
-
|
|
10
|
-
{% endif -%}
|
|
11
|
-
{% if multi_version_bcs is mapping -%}
|
|
12
|
-
{% for version, bcs in multi_version_bcs | dictsort -%}
|
|
13
|
-
###{%- if not (module == 'core' and command == 'core') -%}#{%- endif %} Deprecated in {{ version }}
|
|
14
|
-
|
|
15
|
-
{% for bc in bcs -%}
|
|
16
|
-
- {{ bc }}
|
|
17
|
-
{% endfor %}
|
|
18
|
-
|
|
19
|
-
{% endfor -%}
|
|
20
|
-
{% else -%}
|
|
21
|
-
|
|
22
|
-
{% for bc in multi_version_bcs -%}
|
|
23
|
-
- {{ bc }}
|
|
24
|
-
{% endfor %}
|
|
25
|
-
|
|
26
|
-
{% endif -%}
|
|
27
|
-
{% endfor -%}
|
|
28
|
-
{% endfor -%}
|
|
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
|
|
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
|