Просмотр исходного кода

Add tests for consistent license preamble, and to prevent os.environ use in lower layers

Jelmer Vernooij 1 месяц назад
Родитель
Сommit
63c51b2e14

+ 0 - 1
dulwich/__init__.py

@@ -20,7 +20,6 @@
 # License, Version 2.0.
 # License, Version 2.0.
 #
 #
 
 
-
 """Python implementation of the Git file formats and protocols."""
 """Python implementation of the Git file formats and protocols."""
 
 
 from collections.abc import Callable
 from collections.abc import Callable

+ 21 - 0
dulwich/__main__.py

@@ -1,3 +1,24 @@
+# __main__.py -- Entry point for running dulwich as a module
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Entry point for running dulwich as a module.
 """Entry point for running dulwich as a module.
 
 
 This module allows dulwich to be run as a Python module using the -m flag:
 This module allows dulwich to be run as a Python module using the -m flag:

+ 21 - 0
dulwich/_typing.py

@@ -1,3 +1,24 @@
+# _typing.py -- Common type definitions for Dulwich
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Common type definitions for Dulwich."""
 """Common type definitions for Dulwich."""
 
 
 import sys
 import sys

+ 15 - 12
dulwich/annotate.py

@@ -1,20 +1,23 @@
 # annotate.py -- Annotate files with last changed revision
 # annotate.py -- Annotate files with last changed revision
 # Copyright (C) 2015 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2015 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License
-# as published by the Free Software Foundation; either version 2
-# or (at your option) a later version of the License.
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
 #
 #
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
 #
 #
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-# MA  02110-1301, USA.
 
 
 """Annotate file contents indicating when they were last changed.
 """Annotate file contents indicating when they were last changed.
 
 

+ 1 - 0
dulwich/attrs.py

@@ -2,6 +2,7 @@
 # Copyright (C) 2019-2020 Collabora Ltd
 # Copyright (C) 2019-2020 Collabora Ltd
 # Copyright (C) 2019-2020 Andrej Shadura <andrew.shadura@collabora.co.uk>
 # Copyright (C) 2019-2020 Andrej Shadura <andrew.shadura@collabora.co.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 1 - 0
dulwich/bisect.py

@@ -1,6 +1,7 @@
 # bisect.py -- Git bisect algorithm implementation
 # bisect.py -- Git bisect algorithm implementation
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 21 - 0
dulwich/cloud/__init__.py

@@ -1,3 +1,24 @@
+# __init__.py -- Cloud storage backends for dulwich
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Cloud storage backends for dulwich.
 """Cloud storage backends for dulwich.
 
 
 This package provides support for storing Git repositories in various
 This package provides support for storing Git repositories in various

+ 12 - 0
dulwich/commit_graph.py

@@ -6,6 +6,18 @@
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or
 # modify it under the terms of either of these two licenses.
 # modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
 
 
 """Git commit graph file format support.
 """Git commit graph file format support.
 
 

+ 1 - 1
dulwich/contrib/requests_vendor.py

@@ -17,7 +17,7 @@
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # License, Version 2.0.
 # License, Version 2.0.
-
+#
 
 
 """Requests HTTP client support for Dulwich.
 """Requests HTTP client support for Dulwich.
 
 

+ 1 - 0
dulwich/diff.py

@@ -1,6 +1,7 @@
 # diff.py -- Diff functionality for Dulwich
 # diff.py -- Diff functionality for Dulwich
 # Copyright (C) 2025 Dulwich contributors
 # Copyright (C) 2025 Dulwich contributors
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 21 - 0
dulwich/gc.py

@@ -1,3 +1,24 @@
+# gc.py -- Git garbage collection implementation
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Git garbage collection implementation."""
 """Git garbage collection implementation."""
 
 
 __all__ = [
 __all__ = [

+ 1 - 0
dulwich/graph.py

@@ -17,6 +17,7 @@
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # License, Version 2.0.
 # License, Version 2.0.
+#
 
 
 """Implementation of merge-base following the approach of git."""
 """Implementation of merge-base following the approach of git."""
 
 

+ 21 - 0
dulwich/maintenance.py

@@ -1,3 +1,24 @@
+# maintenance.py -- Git maintenance implementation
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Git maintenance implementation.
 """Git maintenance implementation.
 
 
 This module provides the git maintenance functionality for optimizing
 This module provides the git maintenance functionality for optimizing

+ 21 - 0
dulwich/merge.py

@@ -1,3 +1,24 @@
+# merge.py -- Git merge implementation
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Git merge implementation."""
 """Git merge implementation."""
 
 
 __all__ = [
 __all__ = [

+ 1 - 0
dulwich/merge_drivers.py

@@ -1,6 +1,7 @@
 # merge_drivers.py -- Merge driver support for dulwich
 # merge_drivers.py -- Merge driver support for dulwich
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 1 - 0
dulwich/notes.py

@@ -1,6 +1,7 @@
 # notes.py -- Git notes handling
 # notes.py -- Git notes handling
 # Copyright (C) 2024 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2024 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 21 - 0
dulwich/reftable.py

@@ -1,3 +1,24 @@
+# reftable.py -- Implementation of the reftable refs storage format
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Implementation of the reftable refs storage format.
 """Implementation of the reftable refs storage format.
 
 
 The reftable format is a binary format for storing Git refs that provides
 The reftable format is a binary format for storing Git refs that provides

+ 21 - 0
dulwich/rerere.py

@@ -1,3 +1,24 @@
+# rerere.py -- Git rerere (reuse recorded resolution) implementation
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
 """Git rerere (reuse recorded resolution) implementation.
 """Git rerere (reuse recorded resolution) implementation.
 
 
 This module implements Git's rerere functionality, which records and reuses
 This module implements Git's rerere functionality, which records and reuses

+ 2 - 0
dulwich/stripspace.py

@@ -1,6 +1,7 @@
 # stripspace.py -- Git stripspace functionality
 # stripspace.py -- Git stripspace functionality
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or
@@ -16,6 +17,7 @@
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
 # License, Version 2.0.
 # License, Version 2.0.
+#
 
 
 """Git stripspace functionality for cleaning up text and commit messages."""
 """Git stripspace functionality for cleaning up text and commit messages."""
 
 

+ 1 - 0
dulwich/trailers.py

@@ -1,6 +1,7 @@
 # trailers.py -- Git trailers parsing and manipulation
 # trailers.py -- Git trailers parsing and manipulation
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 # Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
 #
 #
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
 # General Public License as published by the Free Software Foundation; version 2.0
 # General Public License as published by the Free Software Foundation; version 2.0
 # or (at your option) any later version. You can redistribute it and/or
 # or (at your option) any later version. You can redistribute it and/or

+ 1 - 0
tests/__init__.py

@@ -179,6 +179,7 @@ def self_test_suite() -> unittest.TestSuite:
         "repository",
         "repository",
         "rerere",
         "rerere",
         "server",
         "server",
+        "source",
         "sparse_patterns",
         "sparse_patterns",
         "stash",
         "stash",
         "stripspace",
         "stripspace",

+ 228 - 0
tests/test_source.py

@@ -0,0 +1,228 @@
+# test_source.py -- Tests for scanning dulwich source code
+# Copyright (C) 2025 Jelmer Vernooij <jelmer@jelmer.uk>
+#
+# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
+# General Public License as published by the Free Software Foundation; version 2.0
+# or (at your option) any later version. You can redistribute it and/or
+# modify it under the terms of either of these two licenses.
+#
+# 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.
+#
+# You should have received a copy of the licenses; if not, see
+# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
+# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
+# License, Version 2.0.
+#
+
+"""Tests for scanning dulwich source code for compliance."""
+
+import os
+import re
+import unittest
+from pathlib import Path
+
+# Files that are allowed to not have the standard preamble
+PREAMBLE_EXCEPTIONS = [
+    "dulwich/diffstat.py",  # MIT licensed file
+]
+
+# Files that are allowed to use os.environ (beyond cli.py and porcelain/)
+OS_ENVIRON_EXCEPTIONS = [
+    "dulwich/client.py",  # Git protocol environment variables
+    "dulwich/repo.py",  # User identity environment variables
+    "dulwich/log_utils.py",  # GIT_TRACE environment variable
+    "dulwich/config.py",  # Git configuration environment variables
+    "dulwich/gc.py",  # GIT_AUTO_GC environment variable
+    "dulwich/contrib/swift.py",  # DULWICH_SWIFT_CFG environment variable
+    "dulwich/hooks.py",  # Git hooks environment setup
+]
+
+# Standard license block that must appear in all files
+STANDARD_LICENSE_BLOCK = [
+    "# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later\n",
+    "# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU\n",
+    "# General Public License as published by the Free Software Foundation; version 2.0\n",
+    "# or (at your option) any later version. You can redistribute it and/or\n",
+    "# modify it under the terms of either of these two licenses.\n",
+    "#\n",
+    "# Unless required by applicable law or agreed to in writing, software\n",
+    '# distributed under the License is distributed on an "AS IS" BASIS,\n',
+    "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+    "# See the License for the specific language governing permissions and\n",
+    "# limitations under the License.\n",
+    "#\n",
+    "# You should have received a copy of the licenses; if not, see\n",
+    "# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License\n",
+    "# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache\n",
+    "# License, Version 2.0.\n",
+    "#\n",
+]
+
+
+class SourceCodeComplianceTests(unittest.TestCase):
+    """Tests to ensure dulwich source code follows project standards."""
+
+    @staticmethod
+    def _get_dulwich_python_files():
+        """Get all Python files in the dulwich package.
+
+        Returns:
+            List of tuples of (Path object, relative path from project root)
+        """
+        project_root = Path(__file__).parent.parent
+        dulwich_dir = project_root / "dulwich"
+        if not dulwich_dir.exists():
+            raise RuntimeError(f"dulwich directory not found at {dulwich_dir}")
+
+        python_files = []
+        for root, dirs, files in os.walk(dulwich_dir):
+            # Skip build directories
+            if "build" in root or "__pycache__" in root:
+                continue
+
+            for file in files:
+                if file.endswith(".py"):
+                    file_path = Path(root) / file
+                    rel_path = file_path.relative_to(project_root)
+                    python_files.append((file_path, rel_path))
+
+        return python_files
+
+    @classmethod
+    def _has_standard_preamble(cls, file_path: Path) -> tuple[bool, str]:
+        """Check if a file has the standard dulwich preamble.
+
+        The standard preamble consists of:
+        - First line: # filename -- Description (or similar)
+        - Copyright line(s): # Copyright (C) ...
+        - Empty comment: #
+        - Standard license block (exact match required)
+
+        Args:
+            file_path: Path to the Python file to check
+
+        Returns:
+            Tuple of (has_preamble, error_message)
+        """
+        with open(file_path, encoding="utf-8") as f:
+            lines = f.readlines()
+
+        if len(lines) < 21:
+            return False, "File too short to contain standard preamble"
+
+        # Check first line starts with #
+        if not lines[0].startswith("#"):
+            return False, "First line does not start with #"
+
+        # Find the SPDX line (should be within first 10 lines)
+        spdx_line_idx = None
+        for i in range(min(10, len(lines))):
+            if "SPDX-License-Identifier" in lines[i]:
+                spdx_line_idx = i
+                break
+
+        if spdx_line_idx is None:
+            return False, "SPDX-License-Identifier line not found in first 10 lines"
+
+        # Check that we have enough lines after the SPDX line
+        if len(lines) < spdx_line_idx + len(STANDARD_LICENSE_BLOCK):
+            return (
+                False,
+                "File too short to contain complete license block after SPDX line",
+            )
+
+        # Extract the license block from the file
+        file_license_block = lines[
+            spdx_line_idx : spdx_line_idx + len(STANDARD_LICENSE_BLOCK)
+        ]
+
+        # Compare with standard license block
+        for i, (expected, actual) in enumerate(
+            zip(STANDARD_LICENSE_BLOCK, file_license_block)
+        ):
+            if expected != actual:
+                return (
+                    False,
+                    f"License block mismatch at line {spdx_line_idx + i + 1}: expected {expected!r}, got {actual!r}",
+                )
+
+        return True, ""
+
+    def test_all_files_have_preamble(self):
+        """Test that all dulwich Python files have the standard preamble."""
+        python_files = self._get_dulwich_python_files()
+        self.assertGreater(len(python_files), 0, "No Python files found in dulwich/")
+
+        files_without_preamble = []
+
+        for file_path, rel_path in python_files:
+            # Convert to forward slashes for consistency
+            rel_path_str = str(rel_path).replace(os.sep, "/")
+
+            # Skip exceptions
+            if rel_path_str in PREAMBLE_EXCEPTIONS:
+                continue
+
+            has_preamble, error_msg = self._has_standard_preamble(file_path)
+            if not has_preamble:
+                files_without_preamble.append(f"{rel_path_str}: {error_msg}")
+
+        if files_without_preamble:
+            self.fail(
+                "The following files are missing the standard preamble:\n"
+                + "\n".join(f"  - {f}" for f in files_without_preamble)
+            )
+
+    def test_os_environ_usage_restricted(self):
+        """Test that os.environ is only used in allowed files."""
+        python_files = self._get_dulwich_python_files()
+        self.assertGreater(len(python_files), 0, "No Python files found in dulwich/")
+
+        # Files allowed to use os.environ
+        allowed_files = {
+            "dulwich/cli.py",
+            "dulwich/porcelain/",
+        }
+        # Add exception files
+        allowed_files.update(OS_ENVIRON_EXCEPTIONS)
+
+        files_with_violations = []
+
+        # Pattern to match os.environ usage
+        os_environ_pattern = re.compile(r"\bos\.environ\b")
+
+        for file_path, rel_path in python_files:
+            # Convert to forward slashes for consistency
+            rel_path_str = str(rel_path).replace(os.sep, "/")
+
+            # Skip allowed files
+            if any(rel_path_str.startswith(f) for f in allowed_files):
+                continue
+
+            with open(file_path, encoding="utf-8") as f:
+                content = f.read()
+
+            matches = os_environ_pattern.findall(content)
+            if matches:
+                # Count occurrences
+                line_numbers = []
+                for line_num, line in enumerate(content.split("\n"), 1):
+                    if os_environ_pattern.search(line):
+                        line_numbers.append(line_num)
+
+                files_with_violations.append(
+                    f"{rel_path_str}: os.environ used on line(s) {', '.join(map(str, line_numbers))}"
+                )
+
+        if files_with_violations:
+            self.fail(
+                "The following files use os.environ but are not in the allowed list:\n"
+                + "\n".join(f"  - {f}" for f in files_with_violations)
+                + "\n\nFiles allowed to use os.environ:\n"
+                + "\n".join(f"  - {f}" for f in sorted(allowed_files))
+            )