This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new 35ea1f2d07b [v3-1-test] Fix .airflowignore order precedence (#56509) 
(#56832)
35ea1f2d07b is described below

commit 35ea1f2d07b66669d7b87c01fb4a40248b980ec8
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Sun Oct 19 01:52:48 2025 +0200

    [v3-1-test] Fix .airflowignore order precedence (#56509) (#56832)
    
    * Bug fix: proper include-exclude .airflowignore order precedence
    
    * remove lingering commented code
    
    * Remove commented unit test line
    (cherry picked from commit da66c417269683ad0f45ada76c85ebd5cc41a9a4)
    
    Co-authored-by: Zach <[email protected]>
---
 airflow-core/src/airflow/utils/file.py             | 23 +++++++-------
 airflow-core/tests/unit/dags/.airflowignore_glob   |  7 +++--
 .../unit/dags/subdir1/test_explicit_dont_ignore.py | 29 ++++++++++++++++++
 .../dags/subdir2/subdir3/should_ignore_this.py     | 17 +++++++++++
 .../dags/subdir2/subdir3/test_explicit_ignore.py   | 16 ++++++++++
 airflow-core/tests/unit/utils/test_file.py         | 35 +++++++++++++++-------
 6 files changed, 102 insertions(+), 25 deletions(-)

diff --git a/airflow-core/src/airflow/utils/file.py 
b/airflow-core/src/airflow/utils/file.py
index 21bd5c0511a..d546c0ac352 100644
--- a/airflow-core/src/airflow/utils/file.py
+++ b/airflow-core/src/airflow/utils/file.py
@@ -102,24 +102,21 @@ class _GlobIgnoreRule(NamedTuple):
             relative_to = definition_file.parent
 
         ignore_pattern = GitWildMatchPattern(pattern)
-        return _GlobIgnoreRule(ignore_pattern, relative_to)
+        return _GlobIgnoreRule(wild_match_pattern=ignore_pattern, 
relative_to=relative_to)
 
     @staticmethod
     def match(path: Path, rules: list[_IgnoreRule]) -> bool:
-        """Match a list of ignore rules against the supplied path."""
+        """Match a list of ignore rules against the supplied path, accounting 
for exclusion rules and ordering."""
         matched = False
-        for r in rules:
-            if not isinstance(r, _GlobIgnoreRule):
-                raise ValueError(f"_GlobIgnoreRule cannot match rules of type: 
{type(r)}")
-            rule: _GlobIgnoreRule = r  # explicit typing to make mypy play 
nicely
+        for rule in rules:
+            if not isinstance(rule, _GlobIgnoreRule):
+                raise ValueError(f"_GlobIgnoreRule cannot match rules of type: 
{type(rule)}")
             rel_path = str(path.relative_to(rule.relative_to) if 
rule.relative_to else path.name)
-            matched = rule.wild_match_pattern.match_file(rel_path) is not None
-
-            if matched:
-                if not rule.wild_match_pattern.include:
-                    return False
-
-                return matched
+            if (
+                rule.wild_match_pattern.include is not None
+                and rule.wild_match_pattern.match_file(rel_path) is not None
+            ):
+                matched = rule.wild_match_pattern.include
 
         return matched
 
diff --git a/airflow-core/tests/unit/dags/.airflowignore_glob 
b/airflow-core/tests/unit/dags/.airflowignore_glob
index b773879d2f0..ee7ee40eebf 100644
--- a/airflow-core/tests/unit/dags/.airflowignore_glob
+++ b/airflow-core/tests/unit/dags/.airflowignore_glob
@@ -5,12 +5,15 @@
 *_invalid_*      # skip invalid files
 
 # test ignoring files at all levels
-**/*_dont_*                # ignore all python files at all levels with "dont" 
in their name
-!**/*_negate_ignore.py
+**/*should_ignore_*                # ignore all python files at all levels 
with "should_ignore" in their name
+subdir2/subdir3/test_explicit_ignore.py  # ignore this explicit path
 subdir2/**/test_nested*.py # ignore files in subdir2/subdir3
+!subdir2/**/*_negate_ignore.py  # do not ignore files ending with 
'_negate_ignore.py'
 
 # test matching and ignoring of path separators
 subdir1/*         # ignore all of subdir1
+!subdir1/test_explicit_dont_ignore.py  # do not ignore this explicit path
+!subdir1/*_negate_ignore.py  # Do not ignore this one file in subdir1
 subdir2*test*    # this should not match anything in the subdir2 directory
 subdir2?test*    # this should not match anything in the subdir2 directory
 
diff --git a/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py 
b/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py
new file mode 100644
index 00000000000..cbacd930ca0
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir1/test_explicit_dont_ignore.py
@@ -0,0 +1,29 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+from __future__ import annotations
+
+from datetime import datetime
+
+from airflow.models.dag import DAG
+from airflow.providers.standard.operators.bash import BashOperator
+
+DEFAULT_DATE = datetime(2019, 12, 1)
+
+dag = DAG(dag_id="test_dag_explicit_dont_ignore", start_date=DEFAULT_DATE, 
schedule=None)
+task = BashOperator(task_id="task1", bash_command='echo "test dag explicitly 
dont ignore"', dag=dag)
diff --git a/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py 
b/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py
new file mode 100644
index 00000000000..217e5db9607
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir2/subdir3/should_ignore_this.py
@@ -0,0 +1,17 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git 
a/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py 
b/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py
new file mode 100644
index 00000000000..13a83393a91
--- /dev/null
+++ b/airflow-core/tests/unit/dags/subdir2/subdir3/test_explicit_ignore.py
@@ -0,0 +1,16 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
diff --git a/airflow-core/tests/unit/utils/test_file.py 
b/airflow-core/tests/unit/utils/test_file.py
index 47c89b2466b..ca6c7e534b9 100644
--- a/airflow-core/tests/unit/utils/test_file.py
+++ b/airflow-core/tests/unit/utils/test_file.py
@@ -20,6 +20,7 @@ from __future__ import annotations
 import os
 import zipfile
 from pathlib import Path
+from pprint import pformat
 from unittest import mock
 
 import pytest
@@ -125,22 +126,32 @@ class TestListPyFilesPath:
         assert all(os.path.basename(file) not in should_ignore for file in 
files)
 
     def test_find_path_from_directory_glob_ignore(self):
-        should_ignore = [
+        should_ignore = {
+            "should_ignore_this.py",
+            "test_explicit_ignore.py",
             "test_invalid_cron.py",
             "test_invalid_param.py",
             "test_ignore_this.py",
             "test_prev_dagrun_dep.py",
             "test_nested_dag.py",
-            "test_dont_ignore.py",
             ".airflowignore",
-        ]
-        should_not_ignore = ["test_on_kill.py", "test_negate_ignore.py", 
"test_nested_negate_ignore.py"]
-        files = list(find_path_from_directory(TEST_DAGS_FOLDER, 
".airflowignore_glob", "glob"))
+        }
+        should_not_ignore = {
+            "test_on_kill.py",
+            "test_negate_ignore.py",
+            "test_dont_ignore_this.py",
+            "test_nested_negate_ignore.py",
+            "test_explicit_dont_ignore.py",
+        }
+        actual_files = list(find_path_from_directory(TEST_DAGS_FOLDER, 
".airflowignore_glob", "glob"))
 
-        assert files
-        assert all(os.path.basename(file) not in should_ignore for file in 
files)
-        assert sum(1 for file in files if os.path.basename(file) in 
should_not_ignore) == len(
-            should_not_ignore
+        assert actual_files
+        assert all(os.path.basename(file) not in should_ignore for file in 
actual_files)
+        actual_included_filenames = set(
+            [os.path.basename(f) for f in actual_files if os.path.basename(f) 
in should_not_ignore]
+        )
+        assert actual_included_filenames == should_not_ignore, (
+            f"actual_included_filenames: 
{pformat(actual_included_filenames)}\nexpected_included_filenames: 
{pformat(should_not_ignore)}"
         )
 
     def test_find_path_from_directory_respects_symlinks_regexp_ignore(self, 
test_dir):
@@ -223,6 +234,8 @@ class TestListPyFilesPath:
         # No_dags is empty, _invalid_ is ignored by .airflowignore
         ignored_files = {
             "no_dags.py",
+            "should_ignore_this.py",
+            "test_explicit_ignore.py",
             "test_invalid_cron.py",
             "test_invalid_dup_task.py",
             "test_ignore_this.py",
@@ -243,7 +256,9 @@ class TestListPyFilesPath:
                     if file_name not in ignored_files:
                         expected_files.add(f"{root}/{file_name}")
         detected_files = set(list_py_file_paths(TEST_DAG_FOLDER))
-        assert detected_files == expected_files
+        assert detected_files == expected_files, (
+            f"Detected files mismatched expected files:\ndetected_files: 
{pformat(detected_files)}\nexpected_files: {pformat(expected_files)}"
+        )
 
 
 @pytest.mark.parametrize(

Reply via email to