Ejemplo n.º 1
0
def test_different_config_file(base_arguments: List[str]) -> None:
    """Ensures an alternate config_file can be used."""
    diff_config = cli.get_config(base_arguments +
                                 ["-c", "test/fixtures/ansible-config.yml"])
    no_config = cli.get_config(base_arguments + ["-v"])

    assert diff_config.verbosity == no_config.verbosity
Ejemplo n.º 2
0
def test_config_can_be_overridden(base_arguments: List[str]) -> None:
    no_override = cli.get_config(base_arguments + ["-t", "bad_tag"])

    overridden = cli.get_config(
        base_arguments + ["-t", "bad_tag", "-c", "test/fixtures/tags.yml"])

    assert no_override.tags + ["skip_ansible_lint"] == overridden.tags
Ejemplo n.º 3
0
def test_discover_lintables_silent(is_in_git: bool, monkeypatch: MonkeyPatch,
                                   capsys: CaptureFixture[str]) -> None:
    """Verify that no stderr output is displayed while discovering yaml files.

    (when the verbosity is off, regardless of the Git or Git-repo presence)

    Also checks expected number of files are detected.
    """
    options = cli.get_config([])
    test_dir = Path(__file__).resolve().parent
    lint_path = test_dir / '..' / 'examples' / 'roles' / 'test-role'
    if not is_in_git:
        monkeypatch.setenv('GIT_DIR', '')

    yaml_count = len(list(lint_path.glob('**/*.yml'))) + len(
        list(lint_path.glob('**/*.yaml')))

    monkeypatch.chdir(str(lint_path))
    files = file_utils.discover_lintables(options)
    stderr = capsys.readouterr().err
    assert not stderr, 'No stderr output is expected when the verbosity is off'
    assert (
        len(files) == yaml_count
    ), "Expected to find {yaml_count} yaml files in {lint_path}".format_map(
        locals(), )
Ejemplo n.º 4
0
def initialize_options(arguments: List[str]):
    """Load config options and store them inside options module."""
    new_options = cli.get_config(arguments)
    new_options.cwd = pathlib.Path.cwd()

    if new_options.version:
        ansible_version, err = check_ansible_presence()
        print('ansible-lint {ver!s} using ansible {ansible_ver!s}'.format(
            ver=__version__, ansible_ver=ansible_version))
        if err:
            print(err, file=sys.stderr)
            sys.exit(ANSIBLE_MISSING_RC)
        sys.exit(0)

    if new_options.colored is None:
        new_options.colored = should_do_markup()

    # persist loaded configuration inside options module
    for k, v in new_options.__dict__.items():
        setattr(options, k, v)

    # rename deprecated ids/tags to newer names
    options.tags = [normalize_tag(tag) for tag in options.tags]
    options.skip_list = [normalize_tag(tag) for tag in options.skip_list]
    options.warn_list = [normalize_tag(tag) for tag in options.warn_list]
Ejemplo n.º 5
0
def initialize_options(arguments: Optional[List[str]] = None) -> None:
    """Load config options and store them inside options module."""
    new_options = cli.get_config(arguments or [])
    new_options.cwd = pathlib.Path.cwd()

    if new_options.version:
        ansible_version, err = check_ansible_presence()
        print('ansible-lint {ver!s} using ansible {ansible_ver!s}'.format(
            ver=__version__, ansible_ver=ansible_version))
        if err:
            _logger.error(err)
            sys.exit(ANSIBLE_MISSING_RC)
        sys.exit(0)

    if new_options.colored is None:
        new_options.colored = should_do_markup()

    # persist loaded configuration inside options module
    for k, v in new_options.__dict__.items():
        setattr(options, k, v)

    # rename deprecated ids/tags to newer names
    options.tags = [normalize_tag(tag) for tag in options.tags]
    options.skip_list = [normalize_tag(tag) for tag in options.skip_list]
    options.warn_list = [normalize_tag(tag) for tag in options.warn_list]

    options.configured = True
    # 6 chars of entropy should be enough
    cache_key = hashlib.sha256(os.path.abspath(
        options.project_dir).encode()).hexdigest()[:6]
    options.cache_dir = "%s/ansible-lint/%s" % (
        os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")),
        cache_key,
    )
def test_expand_path_user_and_vars_config_file(base_arguments):
    """Ensure user and vars are expanded when specified as exclude_paths."""
    config1 = cli.get_config(
        base_arguments +
        ["-c", "test/fixtures/exclude-paths-with-expands.yml"])
    config2 = cli.get_config(
        base_arguments +
        ["--exclude", "~/.ansible/roles", "--exclude", "$HOME/.ansible/roles"])

    assert str(
        config1.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles")
    assert str(
        config2.exclude_paths[0]) == os.path.expanduser("~/.ansible/roles")
    assert str(
        config1.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles")
    assert str(
        config2.exclude_paths[1]) == os.path.expandvars("$HOME/.ansible/roles")
Ejemplo n.º 7
0
def test_auto_detect_exclude(monkeypatch: MonkeyPatch) -> None:
    """Verify that exclude option can be used to narrow down detection."""
    options = cli.get_config(['--exclude', 'foo'])

    def mockreturn(options: Namespace) -> List[str]:
        return ['foo/playbook.yml', 'bar/playbook.yml']

    monkeypatch.setattr(utils, 'discover_lintables', mockreturn)
    result = utils.get_lintables(options)
    assert result == [Lintable('bar/playbook.yml', kind='playbook')]
Ejemplo n.º 8
0
def test_auto_detect_exclude(monkeypatch):
    """Verify that exclude option can be used to narrow down detection."""
    options = cli.get_config(['--exclude', 'foo'])

    def mockreturn(options):
        return ['foo/playbook.yml', 'bar/playbook.yml']

    monkeypatch.setattr(utils, 'get_yaml_files', mockreturn)
    result = utils.get_playbooks_and_roles(options)
    assert result == ['bar/playbook.yml']
Ejemplo n.º 9
0
def test_get_yaml_files_umlaut(monkeypatch):
    """Verify that filenames containing German umlauts are not garbled by the get_yaml_files."""
    options = cli.get_config([])
    test_dir = Path(__file__).resolve().parent
    lint_path = test_dir / '..' / 'examples' / 'playbooks'

    monkeypatch.chdir(str(lint_path))
    files = file_utils.get_yaml_files(options)
    assert '"with-umlaut-\\303\\244.yml"' not in files
    assert 'with-umlaut-ä.yml' in files
Ejemplo n.º 10
0
def test_auto_detect(monkeypatch, path: str, kind: FileType) -> None:
    """Verify auto-detection logic."""
    options = cli.get_config([])

    def mockreturn(options):
        return [path]

    monkeypatch.setattr(utils, 'get_yaml_files', mockreturn)
    result = utils.get_lintables(options)
    assert result == [Lintable(path, kind=kind)]
Ejemplo n.º 11
0
def test_get_yaml_files_git_verbose(reset_env_var, message_prefix, monkeypatch,
                                    caplog):
    options = cli.get_config(['-v'])
    utils.initialize_logger(options.verbosity)
    monkeypatch.setenv(reset_env_var, '')
    utils.get_yaml_files(options)

    expected_info = ("ansiblelint.utils", logging.INFO,
                     'Discovering files to lint: git ls-files *.yaml *.yml')

    assert expected_info in caplog.record_tuples
    assert any(m.startswith(message_prefix) for m in caplog.messages)
Ejemplo n.º 12
0
def test_logger_debug(caplog):
    """Test that the double verbosity arg causes logger to be DEBUG."""
    options = cli.get_config(['-vv'])
    initialize_logger(options.verbosity)

    expected_info = (
        "ansiblelint.__main__",
        logging.DEBUG,
        'Logging initialized to level 10',
    )

    assert expected_info in caplog.record_tuples
Ejemplo n.º 13
0
def test_get_yaml_files_git_verbose(reset_env_var, message_prefix, monkeypatch, caplog):
    """Ensure that autodiscovery lookup failures are logged."""
    options = cli.get_config(['-v'])
    initialize_logger(options.verbosity)
    monkeypatch.setenv(reset_env_var, '')
    file_utils.get_yaml_files(options)

    expected_info = (
        "ansiblelint",
        logging.INFO,
        'Discovering files to lint: git ls-files -z',
    )

    assert expected_info in caplog.record_tuples
    assert any(m.startswith(message_prefix) for m in caplog.messages)
Ejemplo n.º 14
0
def test_discover_lintables_git_verbose(
    reset_env_var: str,
    message_prefix: str,
    monkeypatch: MonkeyPatch,
    caplog: LogCaptureFixture,
) -> None:
    """Ensure that autodiscovery lookup failures are logged."""
    options = cli.get_config(['-v'])
    initialize_logger(options.verbosity)
    monkeypatch.setenv(reset_env_var, '')
    file_utils.discover_lintables(options)

    assert any(m[2].startswith("Looking up for files")
               for m in caplog.record_tuples)
    assert any(m.startswith(message_prefix) for m in caplog.messages)
Ejemplo n.º 15
0
def test_auto_detect(monkeypatch, path: str, kind: FileType) -> None:
    """Verify auto-detection logic."""
    options = cli.get_config([])

    def mockreturn(options):
        return [path]

    # assert Lintable is able to determine file type
    lintable_detected = Lintable(path)
    lintable_expected = Lintable(path, kind=kind)
    assert lintable_detected == lintable_expected

    monkeypatch.setattr(utils, 'get_yaml_files', mockreturn)
    result = utils.get_lintables(options)
    # get_lintable could return additional files and we only care to see
    # that the given file is among the returned list.
    assert lintable_expected in result
Ejemplo n.º 16
0
def test_default_kinds(monkeypatch: MonkeyPatch, path: str,
                       kind: FileType) -> None:
    """Verify auto-detection logic based on DEFAULT_KINDS."""
    options = cli.get_config([])

    def mockreturn(options: Namespace) -> Dict[str, Any]:
        return {path: kind}

    # assert Lintable is able to determine file type
    lintable_detected = Lintable(path)
    lintable_expected = Lintable(path, kind=kind)
    assert lintable_detected == lintable_expected

    monkeypatch.setattr(file_utils, 'discover_lintables', mockreturn)
    result = file_utils.discover_lintables(options)
    # get_lintable could return additional files and we only care to see
    # that the given file is among the returned list.
    assert lintable_detected.name in result
    assert lintable_detected.kind == result[lintable_expected.name]
Ejemplo n.º 17
0
def initialize_options(arguments: List[str]):
    """Load config options and store them inside options module."""
    new_options = cli.get_config(arguments)
    new_options.cwd = pathlib.Path.cwd()

    if new_options.version:
        print('ansible-lint {ver!s}'.format(ver=__version__))
        # assure we fail if ansible is missing, even for version printing
        check_ansible_presence()
        sys.exit(0)

    if new_options.colored is None:
        new_options.colored = should_do_markup()

    # persist loaded configuration inside options module
    for k, v in new_options.__dict__.items():
        setattr(options, k, v)

    # rename deprecated ids/tags to newer names
    options.tags = [normalize_tag(tag) for tag in options.tags]
    options.skip_list = [normalize_tag(tag) for tag in options.skip_list]
    options.warn_list = [normalize_tag(tag) for tag in options.warn_list]
Ejemplo n.º 18
0
def main() -> int:
    """Linter CLI entry point."""
    cwd = pathlib.Path.cwd()

    options = cli.get_config(sys.argv[1:])

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    formatter_factory: Any = formatters.Formatter
    if options.quiet:
        formatter_factory = formatters.QuietFormatter

    if options.parseable:
        formatter_factory = formatters.ParseableFormatter

    if options.parseable_severity:
        formatter_factory = formatters.ParseableSeverityFormatter

    formatter = formatter_factory(cwd, options.display_relative_path)

    if options.use_default_rules:
        rulesdirs = options.rulesdir + [DEFAULT_RULESDIR]
    else:
        rulesdirs = options.rulesdir or [DEFAULT_RULESDIR]
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        formatted_rules = rules if options.format == 'plain' else rules_as_rst(
            rules)
        print(formatted_rules)
        return 0

    if options.listtags:
        print(rules.listtags())
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    skip = set()
    for s in options.skip_list:
        skip.update(str(s).split(','))
    options.skip_list = frozenset(skip)

    if not options.playbook:
        # no args triggers auto-detection mode
        playbooks = get_playbooks_and_roles(options=options)
    else:
        playbooks = sorted(set(options.playbook))

    matches = list()
    checked_files: Set[str] = set()
    for playbook in playbooks:
        runner = Runner(rules, playbook, options.tags, options.skip_list,
                        options.exclude_paths, options.verbosity,
                        checked_files)
        matches.extend(runner.run())

    # Assure we do not print duplicates and the order is consistent
    matches = sorted(set(matches))

    for match in matches:
        print(formatter.format(match, options.colored))

    # If run under GitHub Actions we also want to emit output recognized by it.
    if os.getenv('GITHUB_ACTIONS') == 'true' and os.getenv('GITHUB_WORKFLOW'):
        formatter = formatters.AnnotationsFormatter(cwd, True)
        for match in matches:
            print(formatter.format(match))

    if matches:
        return 2
    else:
        return 0
Ejemplo n.º 19
0
def test_extra_vars_loaded(base_arguments: List[str]) -> None:
    """Ensure ``extra_vars`` option is loaded from file config."""
    config = cli.get_config(base_arguments +
                            ["-c", "test/fixtures/config-with-extra-vars.yml"])

    assert config.extra_vars == {'foo': 'bar', 'knights_favorite_word': 'NI'}
Ejemplo n.º 20
0
def test_config_failure(base_arguments: List[str], config_file: str) -> None:
    """Ensures specific config files produce error code 2."""
    with pytest.raises(SystemExit, match="^2$"):
        cli.get_config(base_arguments + ["-c", config_file])
Ejemplo n.º 21
0
def main():
    cwd = pathlib.Path.cwd()

    options = cli.get_config(sys.argv[1:])

    initialize_logger(options.verbosity)

    formatter_factory = formatters.Formatter
    if options.quiet:
        formatter_factory = formatters.QuietFormatter

    if options.parseable:
        formatter_factory = formatters.ParseableFormatter

    if options.parseable_severity:
        formatter_factory = formatters.ParseableSeverityFormatter

    formatter = formatter_factory(cwd, options.display_relative_path)

    # no args triggers auto-detection mode
    if not options.playbook and not (options.listrules or options.listtags):
        cli.print_help(file=sys.stderr)
        return 1

    if options.use_default_rules:
        rulesdirs = options.rulesdir + [default_rulesdir]
    else:
        rulesdirs = options.rulesdir or [default_rulesdir]
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        print(rules)
        return 0

    if options.listtags:
        print(rules.listtags())
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    skip = set()
    for s in options.skip_list:
        skip.update(str(s).split(','))
    options.skip_list = frozenset(skip)

    playbooks = sorted(set(options.playbook))
    matches = list()
    checked_files = set()
    for playbook in playbooks:
        runner = Runner(rules, playbook, options.tags,
                        options.skip_list, options.exclude_paths,
                        options.verbosity, checked_files)
        matches.extend(runner.run())

    matches.sort(key=lambda x: (normpath(x.filename), x.linenumber, x.rule.id))

    for match in matches:
        print(formatter.format(match, options.colored))

    if len(matches):
        return 2
    else:
        return 0
Ejemplo n.º 22
0
def main() -> int:
    """Linter CLI entry point."""
    cwd = pathlib.Path.cwd()

    options = cli.get_config(sys.argv[1:])

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    formatter_factory = choose_formatter_factory(options)
    formatter = formatter_factory(cwd, options.display_relative_path)

    rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir],
                               options.use_default_rules)
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        console.print(_rule_format_map[options.format](rules), highlight=False)
        return 0

    if options.listtags:
        print(rules.listtags())
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    skip = set()
    for s in options.skip_list:
        skip.update(str(s).split(','))
    options.skip_list = frozenset(skip)

    matches = _get_matches(rules, options)

    # Assure we do not print duplicates and the order is consistent
    matches = sorted(set(matches))

    mark_as_success = False
    if matches and options.progressive:
        _logger.info(
            "Matches found, running again on previous revision in order to detect regressions"
        )
        with _previous_revision():
            old_matches = _get_matches(rules, options)
            # remove old matches from current list
            matches_delta = list(set(matches) - set(old_matches))
            if len(matches_delta) == 0:
                _logger.warning(
                    "Total violations not increased since previous "
                    "commit, will mark result as success. (%s -> %s)",
                    len(old_matches), len(matches_delta))
                mark_as_success = True

            ignored = 0
            for match in matches:
                # if match is not new, mark is as ignored
                if match not in matches_delta:
                    match.ignored = True
                    ignored += 1
            if ignored:
                _logger.warning(
                    "Marked %s previously known violation(s) as ignored due to"
                    " progressive mode.", ignored)

    _render_matches(matches, options, formatter, cwd)

    if matches and not mark_as_success:
        return report_outcome(matches, options=options)
    else:
        return 0
Ejemplo n.º 23
0
def main(argv: List[str] = None) -> int:
    """Linter CLI entry point."""
    if argv is None:
        argv = sys.argv

    options = cli.get_config(argv[1:])
    options.cwd = pathlib.Path.cwd()

    if options.version:
        print('ansible-lint {ver!s}'.format(ver=__version__))
        # assure we fail if ansible is missing, even for version printing
        check_ansible_presence()
        sys.exit(0)

    if options.colored is None:
        options.colored = should_do_markup()
    console_options["force_terminal"] = options.colored
    reconfigure(console_options)

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    app = App(options=options)

    prepare_environment()
    check_ansible_presence()

    # On purpose lazy-imports to avoid pre-loading Ansible
    # pylint: disable=import-outside-toplevel
    from ansiblelint.generate_docs import rules_as_rich, rules_as_rst
    from ansiblelint.rules import RulesCollection

    rules = RulesCollection(options.rulesdirs)

    if options.listrules:

        _rule_format_map = {
            'plain': str,
            'rich': rules_as_rich,
            'rst': rules_as_rst
        }

        console.print(_rule_format_map[options.format](rules), highlight=False)
        return 0

    if options.listtags:
        console.print(render_yaml(rules.listtags()))
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    from ansiblelint.runner import _get_matches
    result = _get_matches(rules, options)

    mark_as_success = False
    if result.matches and options.progressive:
        _logger.info(
            "Matches found, running again on previous revision in order to detect regressions"
        )
        with _previous_revision():
            old_result = _get_matches(rules, options)
            # remove old matches from current list
            matches_delta = list(set(result.matches) - set(old_result.matches))
            if len(matches_delta) == 0:
                _logger.warning(
                    "Total violations not increased since previous "
                    "commit, will mark result as success. (%s -> %s)",
                    len(old_result.matches), len(matches_delta))
                mark_as_success = True

            ignored = 0
            for match in result.matches:
                # if match is not new, mark is as ignored
                if match not in matches_delta:
                    match.ignored = True
                    ignored += 1
            if ignored:
                _logger.warning(
                    "Marked %s previously known violation(s) as ignored due to"
                    " progressive mode.", ignored)

    app.render_matches(result.matches)

    return report_outcome(result,
                          mark_as_success=mark_as_success,
                          options=options)
Ejemplo n.º 24
0
def main() -> int:
    """Linter CLI entry point."""
    cwd = pathlib.Path.cwd()

    options = cli.get_config(sys.argv[1:])

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    formatter_factory = choose_formatter_factory(options)
    formatter = formatter_factory(cwd, options.display_relative_path)

    rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir],
                               options.use_default_rules)
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        console.print(_rule_format_map[options.format](rules), highlight=False)
        return 0

    if options.listtags:
        print(rules.listtags())
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    skip = set()
    for s in options.skip_list:
        skip.update(str(s).split(','))
    options.skip_list = frozenset(skip)

    if not options.playbook:
        # no args triggers auto-detection mode
        playbooks = get_playbooks_and_roles(options=options)
    else:
        playbooks = sorted(set(options.playbook))

    matches = list()
    checked_files: Set[str] = set()
    for playbook in playbooks:
        runner = Runner(rules, playbook, options.tags, options.skip_list,
                        options.exclude_paths, options.verbosity,
                        checked_files)
        matches.extend(runner.run())

    # Assure we do not print duplicates and the order is consistent
    matches = sorted(set(matches))

    for match in matches:
        print(formatter.format(match, options.colored))

    # If run under GitHub Actions we also want to emit output recognized by it.
    if os.getenv('GITHUB_ACTIONS') == 'true' and os.getenv('GITHUB_WORKFLOW'):
        formatter = formatters.AnnotationsFormatter(cwd, True)
        for match in matches:
            print(formatter.format(match))

    if matches:
        return report_outcome(matches, options=options)
    else:
        return 0
Ejemplo n.º 25
0
def main() -> int:
    """Linter CLI entry point."""
    cwd = pathlib.Path.cwd()

    options = cli.get_config(sys.argv[1:])

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    formatter_factory: Any = formatters.Formatter
    if options.quiet:
        formatter_factory = formatters.QuietFormatter

    if options.parseable:
        formatter_factory = formatters.ParseableFormatter

    if options.parseable_severity:
        formatter_factory = formatters.ParseableSeverityFormatter

    formatter = formatter_factory(cwd, options.display_relative_path)

    if options.use_default_rules:
        rulesdirs = options.rulesdir + [DEFAULT_RULESDIR]
    else:
        rulesdirs = options.rulesdir or [DEFAULT_RULESDIR]
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        formatted_rules = rules if options.format == 'plain' else rules_as_rst(rules)
        print(formatted_rules)
        return 0

    if options.listtags:
        print(rules.listtags())
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    skip = set()
    for s in options.skip_list:
        skip.update(str(s).split(','))
    options.skip_list = frozenset(skip)

    if not options.playbook:
        # no args triggers auto-detection mode
        playbooks = get_playbooks_and_roles(options=options)
    else:
        playbooks = sorted(set(options.playbook))

    matches = list()
    checked_files: Set[Any] = set()
    for playbook in playbooks:
        runner = Runner(rules, playbook, options.tags,
                        options.skip_list, options.exclude_paths,
                        options.verbosity, checked_files)
        matches.extend(runner.run())

    for match in sorted(matches):
        print(formatter.format(match, options.colored))

    if matches:
        return 2
    else:
        return 0
Ejemplo n.º 26
0
def main(argv: List[str] = None) -> int:
    """Linter CLI entry point."""
    if argv is None:
        argv = sys.argv

    cwd = pathlib.Path.cwd()

    options = cli.get_config(argv[1:])

    if options.colored is None:
        options.colored = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
    if options.colored is not None:
        console_options["force_terminal"] = options.colored
        reconfigure(console_options)

    initialize_logger(options.verbosity)
    _logger.debug("Options: %s", options)

    formatter_factory = choose_formatter_factory(options)
    formatter = formatter_factory(cwd, options.display_relative_path)

    rulesdirs = get_rules_dirs([str(rdir) for rdir in options.rulesdir],
                               options.use_default_rules)
    rules = RulesCollection(rulesdirs)

    if options.listrules:
        console.print(
            _rule_format_map[options.format](rules),
            highlight=False)
        return 0

    if options.listtags:
        console.print(
            render_yaml(rules.listtags())
            )
        return 0

    if isinstance(options.tags, str):
        options.tags = options.tags.split(',')

    options.skip_list = _sanitize_list_options(options.skip_list)
    options.warn_list = _sanitize_list_options(options.warn_list)

    result = _get_matches(rules, options)

    mark_as_success = False
    if result.matches and options.progressive:
        _logger.info(
            "Matches found, running again on previous revision in order to detect regressions")
        with _previous_revision():
            old_result = _get_matches(rules, options)
            # remove old matches from current list
            matches_delta = list(set(result.matches) - set(old_result.matches))
            if len(matches_delta) == 0:
                _logger.warning(
                    "Total violations not increased since previous "
                    "commit, will mark result as success. (%s -> %s)",
                    len(old_result.matches), len(matches_delta))
                mark_as_success = True

            ignored = 0
            for match in result.matches:
                # if match is not new, mark is as ignored
                if match not in matches_delta:
                    match.ignored = True
                    ignored += 1
            if ignored:
                _logger.warning(
                    "Marked %s previously known violation(s) as ignored due to"
                    " progressive mode.", ignored)

    _render_matches(result.matches, options, formatter, cwd)

    return report_outcome(result, mark_as_success=mark_as_success, options=options)