2
0
Jelmer Vernooij 1 сар өмнө
parent
commit
5fc7980b9b

+ 3 - 3
Cargo.lock

@@ -16,7 +16,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
 [[package]]
 name = "diff-tree-py"
-version = "0.22.7"
+version = "0.22.9"
 dependencies = [
  "pyo3",
 ]
@@ -56,7 +56,7 @@ dependencies = [
 
 [[package]]
 name = "objects-py"
-version = "0.22.7"
+version = "0.22.9"
 dependencies = [
  "memchr",
  "pyo3",
@@ -70,7 +70,7 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
 
 [[package]]
 name = "pack-py"
-version = "0.22.7"
+version = "0.22.9"
 dependencies = [
  "memchr",
  "pyo3",

+ 1 - 1
tests/__init__.py

@@ -208,7 +208,7 @@ def nocompat_test_suite():
     result = unittest.TestSuite()
     result.addTests(self_test_suite())
     result.addTests(tutorial_test_suite())
-    from dulwich.contrib import test_suite as contrib_test_suite
+    from .contrib import test_suite as contrib_test_suite
 
     result.addTests(contrib_test_suite())
     return result

+ 115 - 1
tests/test_graph.py

@@ -20,7 +20,13 @@
 
 """Tests for dulwich.graph."""
 
-from dulwich.graph import WorkList, _find_lcas, can_fast_forward
+from dulwich.graph import (
+    WorkList,
+    _find_lcas,
+    can_fast_forward,
+    find_merge_base,
+    find_octopus_base,
+)
 from dulwich.repo import MemoryRepo
 from dulwich.tests.utils import make_commit
 
@@ -168,6 +174,84 @@ class FindMergeBaseTests(TestCase):
         self.assertEqual(set(lcas), {"2"})
 
 
+class FindMergeBaseFunctionTests(TestCase):
+    def test_find_merge_base_empty(self) -> None:
+        r = MemoryRepo()
+        # Empty list of commits
+        self.assertEqual([], find_merge_base(r, []))
+
+    def test_find_merge_base_single(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        r.object_store.add_objects([(base, None)])
+        # Single commit returns itself
+        self.assertEqual([base.id], find_merge_base(r, [base.id]))
+
+    def test_find_merge_base_identical(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        r.object_store.add_objects([(base, None)])
+        # When the same commit is in both positions
+        self.assertEqual([base.id], find_merge_base(r, [base.id, base.id]))
+
+    def test_find_merge_base_linear(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        c1 = make_commit(parents=[base.id])
+        c2 = make_commit(parents=[c1.id])
+        r.object_store.add_objects([(base, None), (c1, None), (c2, None)])
+        # Base of c1 and c2 is c1
+        self.assertEqual([c1.id], find_merge_base(r, [c1.id, c2.id]))
+        # Base of c2 and c1 is c1
+        self.assertEqual([c1.id], find_merge_base(r, [c2.id, c1.id]))
+
+    def test_find_merge_base_diverged(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        c1 = make_commit(parents=[base.id])
+        c2a = make_commit(parents=[c1.id], message=b"2a")
+        c2b = make_commit(parents=[c1.id], message=b"2b")
+        r.object_store.add_objects([(base, None), (c1, None), (c2a, None), (c2b, None)])
+        # Merge base of two diverged commits is their common parent
+        self.assertEqual([c1.id], find_merge_base(r, [c2a.id, c2b.id]))
+
+
+class FindOctopusBaseTests(TestCase):
+    def test_find_octopus_base_empty(self) -> None:
+        r = MemoryRepo()
+        # Empty list of commits
+        self.assertEqual([], find_octopus_base(r, []))
+
+    def test_find_octopus_base_single(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        r.object_store.add_objects([(base, None)])
+        # Single commit returns itself
+        self.assertEqual([base.id], find_octopus_base(r, [base.id]))
+
+    def test_find_octopus_base_two_commits(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        c1 = make_commit(parents=[base.id])
+        c2 = make_commit(parents=[c1.id])
+        r.object_store.add_objects([(base, None), (c1, None), (c2, None)])
+        # With two commits it should call find_merge_base
+        self.assertEqual([c1.id], find_octopus_base(r, [c1.id, c2.id]))
+
+    def test_find_octopus_base_multiple(self) -> None:
+        r = MemoryRepo()
+        base = make_commit()
+        c1 = make_commit(parents=[base.id])
+        c2a = make_commit(parents=[c1.id], message=b"2a")
+        c2b = make_commit(parents=[c1.id], message=b"2b")
+        c2c = make_commit(parents=[c1.id], message=b"2c")
+        r.object_store.add_objects(
+            [(base, None), (c1, None), (c2a, None), (c2b, None), (c2c, None)]
+        )
+        # Common ancestor of all three branches
+        self.assertEqual([c1.id], find_octopus_base(r, [c2a.id, c2b.id, c2c.id]))
+
+
 class CanFastForwardTests(TestCase):
     def test_ff(self) -> None:
         r = MemoryRepo()
@@ -207,3 +291,33 @@ class WorkListTest(TestCase):
         wlst.add((150, "Test Value 4"))
         self.assertEqual(wlst.get(), (150, "Test Value 4"))
         self.assertEqual(wlst.get(), (50, "Test Value 2"))
+
+    def test_WorkList_iter(self) -> None:
+        # Test the iter method of WorkList
+        wlst = WorkList()
+        wlst.add((100, "Value 1"))
+        wlst.add((200, "Value 2"))
+        wlst.add((50, "Value 3"))
+
+        # Collect all items from iter
+        items = list(wlst.iter())
+
+        # Items should be in their original order, not sorted
+        self.assertEqual(len(items), 3)
+
+        # Check the values are present with correct timestamps
+        timestamps = [dt for dt, _ in items]
+        values = [val for _, val in items]
+
+        self.assertIn(100, timestamps)
+        self.assertIn(200, timestamps)
+        self.assertIn(50, timestamps)
+        self.assertIn("Value 1", values)
+        self.assertIn("Value 2", values)
+        self.assertIn("Value 3", values)
+
+    def test_WorkList_empty_get(self) -> None:
+        # Test getting from an empty WorkList
+        wlst = WorkList()
+        with self.assertRaises(IndexError):
+            wlst.get()

+ 416 - 0
tests/test_index.py

@@ -39,7 +39,10 @@ from dulwich.index import (
     cleanup_mode,
     commit_tree,
     get_unstaged_changes,
+    index_entry_from_directory,
+    index_entry_from_path,
     index_entry_from_stat,
+    iter_fresh_entries,
     read_index,
     read_index_dict,
     validate_path_element_default,
@@ -786,6 +789,27 @@ class TestTreeFSPathConversion(TestCase):
             os.fsencode(os.path.join("/prefix/path", "délwíçh", "foo")),
         )
 
+    def test_tree_to_fs_path_windows_separator(self) -> None:
+        tree_path = b"path/with/slash"
+        original_sep = os.sep.encode("ascii")
+        try:
+            # Temporarily modify os_sep_bytes to test Windows path conversion
+            # This simulates Windows behavior on all platforms for testing
+            import dulwich.index
+
+            dulwich.index.os_sep_bytes = b"\\"
+
+            fs_path = _tree_to_fs_path(b"/prefix/path", tree_path)
+
+            # The function should join the prefix path with the converted tree path
+            # The expected behavior is that the path separators in the tree_path are
+            # converted to the platform-specific separator (which we've set to backslash)
+            expected_path = os.path.join(b"/prefix/path", b"path\\with\\slash")
+            self.assertEqual(fs_path, expected_path)
+        finally:
+            # Restore original value
+            dulwich.index.os_sep_bytes = original_sep
+
     def test_fs_to_tree_path_str(self) -> None:
         fs_path = os.path.join(os.path.join("délwíçh", "foo"))
         tree_path = _fs_to_tree_path(fs_path)
@@ -795,3 +819,395 @@ class TestTreeFSPathConversion(TestCase):
         fs_path = os.path.join(os.fsencode(os.path.join("délwíçh", "foo")))
         tree_path = _fs_to_tree_path(fs_path)
         self.assertEqual(tree_path, "délwíçh/foo".encode())
+
+    def test_fs_to_tree_path_windows_separator(self) -> None:
+        # Test conversion of Windows paths to tree paths
+        fs_path = b"path\\with\\backslash"
+        original_sep = os.sep.encode("ascii")
+        try:
+            # Temporarily modify os_sep_bytes to test Windows path conversion
+            import dulwich.index
+
+            dulwich.index.os_sep_bytes = b"\\"
+
+            tree_path = _fs_to_tree_path(fs_path)
+            self.assertEqual(tree_path, b"path/with/backslash")
+        finally:
+            # Restore original value
+            dulwich.index.os_sep_bytes = original_sep
+
+
+class TestIndexEntryFromPath(TestCase):
+    def setUp(self):
+        self.tempdir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, self.tempdir)
+
+    def test_index_entry_from_path_file(self) -> None:
+        """Test creating index entry from a regular file."""
+        # Create a test file
+        test_file = os.path.join(self.tempdir, "testfile")
+        with open(test_file, "wb") as f:
+            f.write(b"test content")
+
+        # Get the index entry
+        entry = index_entry_from_path(os.fsencode(test_file))
+
+        # Verify the entry was created with the right mode
+        self.assertIsNotNone(entry)
+        self.assertEqual(cleanup_mode(os.stat(test_file).st_mode), entry.mode)
+
+    @skipIf(not can_symlink(), "Requires symlink support")
+    def test_index_entry_from_path_symlink(self) -> None:
+        """Test creating index entry from a symlink."""
+        # Create a target file
+        target_file = os.path.join(self.tempdir, "target")
+        with open(target_file, "wb") as f:
+            f.write(b"target content")
+
+        # Create a symlink
+        link_file = os.path.join(self.tempdir, "symlink")
+        os.symlink(target_file, link_file)
+
+        # Get the index entry
+        entry = index_entry_from_path(os.fsencode(link_file))
+
+        # Verify the entry was created with the right mode
+        self.assertIsNotNone(entry)
+        self.assertEqual(cleanup_mode(os.lstat(link_file).st_mode), entry.mode)
+
+    def test_index_entry_from_path_directory(self) -> None:
+        """Test creating index entry from a directory (should return None)."""
+        # Create a directory
+        test_dir = os.path.join(self.tempdir, "testdir")
+        os.mkdir(test_dir)
+
+        # Get the index entry for a directory
+        entry = index_entry_from_path(os.fsencode(test_dir))
+
+        # Should return None for regular directories
+        self.assertIsNone(entry)
+
+    def test_index_entry_from_directory_regular(self) -> None:
+        """Test index_entry_from_directory with a regular directory."""
+        # Create a directory
+        test_dir = os.path.join(self.tempdir, "testdir")
+        os.mkdir(test_dir)
+
+        # Get stat for the directory
+        st = os.lstat(test_dir)
+
+        # Get the index entry for a regular directory
+        entry = index_entry_from_directory(st, os.fsencode(test_dir))
+
+        # Should return None for regular directories
+        self.assertIsNone(entry)
+
+    def test_index_entry_from_directory_git_submodule(self) -> None:
+        """Test index_entry_from_directory with a Git submodule."""
+        # Create a git repository that will be a submodule
+        sub_repo_dir = os.path.join(self.tempdir, "subrepo")
+        os.mkdir(sub_repo_dir)
+
+        # Create the .git directory to make it look like a git repo
+        git_dir = os.path.join(sub_repo_dir, ".git")
+        os.mkdir(git_dir)
+
+        # Create HEAD file with a fake commit SHA
+        head_sha = b"1234567890" * 4  # 40-char fake SHA
+        with open(os.path.join(git_dir, "HEAD"), "wb") as f:
+            f.write(head_sha)
+
+        # Get stat for the submodule directory
+        st = os.lstat(sub_repo_dir)
+
+        # Get the index entry for a git submodule directory
+        entry = index_entry_from_directory(st, os.fsencode(sub_repo_dir))
+
+        # Since we don't have a proper git setup, this might still return None
+        # This test just ensures the code path is executed
+        if entry is not None:
+            # If an entry is returned, it should have the gitlink mode
+            self.assertEqual(entry.mode, S_IFGITLINK)
+
+    def test_index_entry_from_path_with_object_store(self) -> None:
+        """Test creating index entry with object store."""
+        # Create a test file
+        test_file = os.path.join(self.tempdir, "testfile")
+        with open(test_file, "wb") as f:
+            f.write(b"test content")
+
+        # Create a memory object store
+        object_store = MemoryObjectStore()
+
+        # Get the index entry and add to object store
+        entry = index_entry_from_path(os.fsencode(test_file), object_store)
+
+        # Verify we can access the blob from the object store
+        self.assertIsNotNone(entry)
+        blob = object_store[entry.sha]
+        self.assertEqual(b"test content", blob.data)
+
+    def test_iter_fresh_entries(self) -> None:
+        """Test iterating over fresh entries."""
+        # Create some test files
+        file1 = os.path.join(self.tempdir, "file1")
+        with open(file1, "wb") as f:
+            f.write(b"file1 content")
+
+        file2 = os.path.join(self.tempdir, "file2")
+        with open(file2, "wb") as f:
+            f.write(b"file2 content")
+
+        # Create a memory object store
+        object_store = MemoryObjectStore()
+
+        # Get fresh entries
+        paths = [b"file1", b"file2", b"nonexistent"]
+        entries = dict(
+            iter_fresh_entries(paths, os.fsencode(self.tempdir), object_store)
+        )
+
+        # Verify both files got entries but nonexistent file is None
+        self.assertIn(b"file1", entries)
+        self.assertIn(b"file2", entries)
+        self.assertIn(b"nonexistent", entries)
+        self.assertIsNotNone(entries[b"file1"])
+        self.assertIsNotNone(entries[b"file2"])
+        self.assertIsNone(entries[b"nonexistent"])
+
+        # Check that blobs were added to object store
+        blob1 = object_store[entries[b"file1"].sha]
+        self.assertEqual(b"file1 content", blob1.data)
+
+        blob2 = object_store[entries[b"file2"].sha]
+        self.assertEqual(b"file2 content", blob2.data)
+
+    def test_read_submodule_head(self) -> None:
+        """Test reading the HEAD of a submodule."""
+        from dulwich.index import read_submodule_head
+        from dulwich.repo import Repo
+
+        # Create a test repo that will be our "submodule"
+        sub_repo_dir = os.path.join(self.tempdir, "subrepo")
+        os.mkdir(sub_repo_dir)
+        submodule_repo = Repo.init(sub_repo_dir)
+
+        # Create a file and commit it to establish a HEAD
+        test_file = os.path.join(sub_repo_dir, "testfile")
+        with open(test_file, "wb") as f:
+            f.write(b"test content")
+
+        submodule_repo.stage(["testfile"])
+        commit_id = submodule_repo.do_commit(b"Test commit for submodule")
+
+        # Test reading the HEAD
+        head_sha = read_submodule_head(sub_repo_dir)
+        self.assertEqual(commit_id, head_sha)
+
+        # Test with bytes path
+        head_sha_bytes = read_submodule_head(os.fsencode(sub_repo_dir))
+        self.assertEqual(commit_id, head_sha_bytes)
+
+        # Test with non-existent path
+        non_repo_dir = os.path.join(self.tempdir, "nonrepo")
+        os.mkdir(non_repo_dir)
+        self.assertIsNone(read_submodule_head(non_repo_dir))
+
+        # Test with path that doesn't have a .git directory
+        not_git_dir = os.path.join(self.tempdir, "notgit")
+        os.mkdir(not_git_dir)
+        self.assertIsNone(read_submodule_head(not_git_dir))
+
+    def test_has_directory_changed(self) -> None:
+        """Test checking if a directory has changed."""
+        from dulwich.index import IndexEntry, _has_directory_changed
+        from dulwich.repo import Repo
+
+        # Setup mock IndexEntry
+        mock_entry = IndexEntry(
+            (1230680220, 0),
+            (1230680220, 0),
+            2050,
+            3761020,
+            33188,
+            1000,
+            1000,
+            0,
+            b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",
+            0,
+            0,
+        )
+
+        # Test with a regular directory (not a submodule)
+        reg_dir = os.path.join(self.tempdir, "regular_dir")
+        os.mkdir(reg_dir)
+
+        # Should return True for regular directory
+        self.assertTrue(_has_directory_changed(os.fsencode(reg_dir), mock_entry))
+
+        # Create a git repository to test submodule scenarios
+        sub_repo_dir = os.path.join(self.tempdir, "subrepo")
+        os.mkdir(sub_repo_dir)
+        submodule_repo = Repo.init(sub_repo_dir)
+
+        # Create a file and commit it to establish a HEAD
+        test_file = os.path.join(sub_repo_dir, "testfile")
+        with open(test_file, "wb") as f:
+            f.write(b"test content")
+
+        submodule_repo.stage(["testfile"])
+        commit_id = submodule_repo.do_commit(b"Test commit for submodule")
+
+        # Create an entry with the correct commit SHA
+        correct_entry = IndexEntry(
+            (1230680220, 0),
+            (1230680220, 0),
+            2050,
+            3761020,
+            33188,
+            1000,
+            1000,
+            0,
+            commit_id,
+            0,
+            0,
+        )
+
+        # Create an entry with an incorrect commit SHA
+        incorrect_entry = IndexEntry(
+            (1230680220, 0),
+            (1230680220, 0),
+            2050,
+            3761020,
+            33188,
+            1000,
+            1000,
+            0,
+            b"0000000000000000000000000000000000000000",
+            0,
+            0,
+        )
+
+        # Should return False for submodule with correct SHA
+        self.assertFalse(
+            _has_directory_changed(os.fsencode(sub_repo_dir), correct_entry)
+        )
+
+        # Should return True for submodule with incorrect SHA
+        self.assertTrue(
+            _has_directory_changed(os.fsencode(sub_repo_dir), incorrect_entry)
+        )
+
+    def test_get_unstaged_changes(self) -> None:
+        """Test detecting unstaged changes in a working tree."""
+        from dulwich.index import (
+            ConflictedIndexEntry,
+            Index,
+            IndexEntry,
+            get_unstaged_changes,
+        )
+
+        # Create a test repo
+        repo_dir = tempfile.mkdtemp()
+        self.addCleanup(shutil.rmtree, repo_dir)
+
+        # Create test index
+        index = Index(os.path.join(repo_dir, "index"))
+
+        # Create an actual hash of our test content
+        from dulwich.objects import Blob
+
+        test_blob = Blob()
+        test_blob.data = b"initial content"
+
+        # Create some test files with known contents
+        file1_path = os.path.join(repo_dir, "file1")
+        with open(file1_path, "wb") as f:
+            f.write(b"initial content")
+
+        file2_path = os.path.join(repo_dir, "file2")
+        with open(file2_path, "wb") as f:
+            f.write(b"initial content")
+
+        # Add them to index
+        entry1 = IndexEntry(
+            (1230680220, 0),
+            (1230680220, 0),
+            2050,
+            3761020,
+            33188,
+            1000,
+            1000,
+            0,
+            b"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391",  # Not matching actual content
+            0,
+            0,
+        )
+
+        entry2 = IndexEntry(
+            (1230680220, 0),
+            (1230680220, 0),
+            2050,
+            3761020,
+            33188,
+            1000,
+            1000,
+            0,
+            test_blob.id,  # Will be content's real hash
+            0,
+            0,
+        )
+
+        # Add a file that has a conflict
+        entry_conflict = ConflictedIndexEntry(b"conflict", {0: None, 1: None, 2: None})
+
+        index._byname = {
+            b"file1": entry1,
+            b"file2": entry2,
+            b"file3": IndexEntry(
+                (1230680220, 0),
+                (1230680220, 0),
+                2050,
+                3761020,
+                33188,
+                1000,
+                1000,
+                0,
+                b"0000000000000000000000000000000000000000",
+                0,
+                0,
+            ),
+            b"conflict": entry_conflict,
+        }
+
+        # Get unstaged changes
+        changes = list(get_unstaged_changes(index, repo_dir))
+
+        # File1 should be unstaged (content doesn't match hash)
+        # File3 doesn't exist (deleted)
+        # Conflict is always unstaged
+        self.assertEqual(sorted(changes), [b"conflict", b"file1", b"file3"])
+
+        # Create directory where there should be a file
+        os.mkdir(os.path.join(repo_dir, "file4"))
+        index._byname[b"file4"] = entry1
+
+        # Get unstaged changes again
+        changes = list(get_unstaged_changes(index, repo_dir))
+
+        # Now file4 should also be unstaged because it's a directory instead of a file
+        self.assertEqual(sorted(changes), [b"conflict", b"file1", b"file3", b"file4"])
+
+        # Create a custom blob filter function
+        def filter_blob_callback(blob, path):
+            # Modify blob to make it look changed
+            blob.data = b"modified " + blob.data
+            return blob
+
+        # Get unstaged changes with blob filter
+        changes = list(get_unstaged_changes(index, repo_dir, filter_blob_callback))
+
+        # Now both file1 and file2 should be unstaged due to the filter
+        self.assertEqual(
+            sorted(changes), [b"conflict", b"file1", b"file2", b"file3", b"file4"]
+        )

+ 97 - 4
tests/test_stash.py

@@ -21,8 +21,13 @@
 
 """Tests for stashes."""
 
-from dulwich.repo import MemoryRepo
-from dulwich.stash import Stash
+import os
+import shutil
+import tempfile
+
+from dulwich.objects import Blob, Tree
+from dulwich.repo import Repo
+from dulwich.stash import DEFAULT_STASH_REF, Stash
 
 from . import TestCase
 
@@ -30,7 +35,95 @@ from . import TestCase
 class StashTests(TestCase):
     """Tests for stash."""
 
+    def setUp(self):
+        self.test_dir = tempfile.mkdtemp()
+        self.repo_dir = os.path.join(self.test_dir, "repo")
+        os.makedirs(self.repo_dir)
+        self.repo = Repo.init(self.repo_dir)
+
+        # Create initial commit so we can have a HEAD
+        blob = Blob()
+        blob.data = b"initial data"
+        self.repo.object_store.add_object(blob)
+
+        tree = Tree()
+        tree.add(b"initial.txt", 0o100644, blob.id)
+        tree_id = self.repo.object_store.add_object(tree)
+
+        self.commit_id = self.repo.do_commit(b"Initial commit", tree=tree_id)
+
+    def tearDown(self):
+        shutil.rmtree(self.test_dir)
+
     def test_obtain(self) -> None:
-        repo = MemoryRepo()
-        stash = Stash.from_repo(repo)
+        stash = Stash.from_repo(self.repo)
         self.assertIsInstance(stash, Stash)
+
+    def test_empty_stash(self) -> None:
+        stash = Stash.from_repo(self.repo)
+        # Make sure logs directory exists for reflog
+        os.makedirs(os.path.join(self.repo.commondir(), "logs"), exist_ok=True)
+
+        self.assertEqual(0, len(stash))
+        self.assertEqual([], list(stash.stashes()))
+
+    def test_push_stash(self) -> None:
+        stash = Stash.from_repo(self.repo)
+
+        # Make sure logs directory exists for reflog
+        os.makedirs(os.path.join(self.repo.commondir(), "logs"), exist_ok=True)
+
+        # Create a file and add it to the index
+        file_path = os.path.join(self.repo_dir, "testfile.txt")
+        with open(file_path, "wb") as f:
+            f.write(b"test data")
+        self.repo.stage(["testfile.txt"])
+
+        # Push to stash
+        commit_id = stash.push(message=b"Test stash message")
+        self.assertIsNotNone(commit_id)
+
+        # Verify stash was created
+        self.assertEqual(1, len(stash))
+
+        # Verify stash entry
+        entry = stash[0]
+        self.assertEqual(commit_id, entry.new_sha)
+        self.assertTrue(b"Test stash message" in entry.message)
+
+    def test_drop_stash(self) -> None:
+        stash = Stash.from_repo(self.repo)
+
+        # Make sure logs directory exists for reflog
+        logs_dir = os.path.join(self.repo.commondir(), "logs")
+        os.makedirs(logs_dir, exist_ok=True)
+
+        # Create a couple of files and stash them
+        file1_path = os.path.join(self.repo_dir, "testfile1.txt")
+        with open(file1_path, "wb") as f:
+            f.write(b"test data 1")
+        self.repo.stage(["testfile1.txt"])
+        commit_id1 = stash.push(message=b"Test stash 1")
+
+        file2_path = os.path.join(self.repo_dir, "testfile2.txt")
+        with open(file2_path, "wb") as f:
+            f.write(b"test data 2")
+        self.repo.stage(["testfile2.txt"])
+        stash.push(message=b"Test stash 2")
+
+        self.assertEqual(2, len(stash))
+
+        # Drop the newest stash
+        stash.drop(0)
+        self.assertEqual(1, len(stash))
+        self.assertEqual(commit_id1, stash[0].new_sha)
+
+        # Drop the remaining stash
+        stash.drop(0)
+        self.assertEqual(0, len(stash))
+        self.assertNotIn(DEFAULT_STASH_REF, self.repo.refs)
+
+    def test_custom_ref(self) -> None:
+        custom_ref = b"refs/custom_stash"
+        stash = Stash(self.repo, ref=custom_ref)
+        self.assertEqual(custom_ref, stash._ref)