1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
# test_submodule.py -- tests for submodule.py
# 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 submodule handling."""
import os
import shutil
import tempfile
from dulwich.objects import (
S_ISGITLINK,
Tree,
)
from dulwich.repo import Repo
from dulwich.submodule import ensure_submodule_placeholder, iter_cached_submodules
from . import TestCase
class SubmoduleTests(TestCase):
"""Tests for submodule functions."""
def setUp(self):
super().setUp()
self.test_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.test_dir)
super().tearDown()
def test_S_ISGITLINK(self) -> None:
"""Test the S_ISGITLINK function for checking gitlink mode."""
# 0o160000 is the mode used for submodules
self.assertTrue(S_ISGITLINK(0o160000))
# Test some other modes to ensure they're not detected as gitlinks
self.assertFalse(S_ISGITLINK(0o100644)) # regular file
self.assertFalse(S_ISGITLINK(0o100755)) # executable file
self.assertFalse(S_ISGITLINK(0o040000)) # directory
def test_iter_cached_submodules(self) -> None:
"""Test the function to detect and iterate through submodules."""
# Create a repository and add some content
repo_dir = os.path.join(self.test_dir, "repo")
os.makedirs(repo_dir)
repo = Repo.init(repo_dir)
# Create a file to add to our tree
file_path = os.path.join(repo_dir, "file.txt")
with open(file_path, "wb") as f:
f.write(b"test file content")
# Stage and commit the file to create some basic content
repo.get_worktree().stage(["file.txt"])
repo.get_worktree().commit(
message=b"Initial commit",
)
# Manually create the raw string for a tree with our file and a submodule
# Format for tree entries: [mode] [name]\0[sha]
# Get the blob SHA for our file using the index
index = repo.open_index()
file_entry = index[b"file.txt"]
file_sha = file_entry.sha
# Convert to binary representation needed for raw tree data
binary_file_sha = bytes.fromhex(file_sha.decode("ascii"))
# Generate a valid SHA for the submodule
submodule_sha = b"1" * repo.object_format.hex_length
binary_submodule_sha = bytes.fromhex(submodule_sha.decode("ascii"))
# Create raw tree data
raw_tree_data = (
# Regular file entry
b"100644 file.txt\0"
+ binary_file_sha
+
# Submodule entry with gitlink mode
b"160000 submodule\0"
+ binary_submodule_sha
)
# Create a tree from raw data
from dulwich.objects import ShaFile
tree = ShaFile.from_raw_string(Tree.type_num, raw_tree_data)
# Add the tree to the repository
repo.object_store.add_object(tree)
# Now we can test the iter_cached_submodules function
submodules = list(iter_cached_submodules(repo.object_store, tree.id))
# We should find one submodule
self.assertEqual(1, len(submodules))
# Verify the submodule details
path, sha = submodules[0]
self.assertEqual(b"submodule", path)
self.assertEqual(submodule_sha, sha)
def test_ensure_submodule_placeholder(self) -> None:
"""Test creating submodule placeholder directories."""
# Create a repository
repo_path = os.path.join(self.test_dir, "testrepo")
repo = Repo.init(repo_path, mkdir=True)
# Test creating a simple submodule placeholder
ensure_submodule_placeholder(repo, b"libs/mylib")
# Check that the directory was created
submodule_path = os.path.join(repo_path, "libs", "mylib")
self.assertTrue(os.path.isdir(submodule_path))
# Check that the .git file was created
git_file_path = os.path.join(submodule_path, ".git")
self.assertTrue(os.path.isfile(git_file_path))
# Check the content of the .git file
with open(git_file_path, "rb") as f:
content = f.read()
self.assertEqual(b"gitdir: ../../.git/modules/libs/mylib\n", content)
# Test with string path
ensure_submodule_placeholder(repo, "libs/another")
another_path = os.path.join(repo_path, "libs", "another")
self.assertTrue(os.path.isdir(another_path))
# Test idempotency - calling again should not fail
ensure_submodule_placeholder(repo, b"libs/mylib")
|