@@ -1542,3 +1542,267 @@ def test_reflog_expire(git_repo: GitSync) -> None:
15421542 # Expire with dry_run should succeed
15431543 result = git_repo .cmd .reflog .expire (dry_run = True )
15441544 assert result == "" or "error" not in result .lower ()
1545+
1546+
1547+ # GitSubmodule tests
1548+ # ==================
1549+
1550+
1551+ @pytest .fixture
1552+ def submodule_repo (
1553+ tmp_path : pathlib .Path ,
1554+ git_commit_envvars : dict [str , str ],
1555+ set_gitconfig : pathlib .Path ,
1556+ ) -> git .Git :
1557+ """Create a git repository to use as a submodule source."""
1558+ import os
1559+
1560+ # Create a repo to serve as submodule source
1561+ source_path = tmp_path / "submodule_source"
1562+ source_path .mkdir ()
1563+ source_repo = git .Git (path = source_path )
1564+ source_repo .init ()
1565+
1566+ # Create initial commit with environment
1567+ env = os .environ .copy ()
1568+ env .update (git_commit_envvars )
1569+
1570+ (source_path / "lib.py" ).write_text ("# Library code\n " )
1571+ source_repo .run (["add" , "." ])
1572+ source_repo .run (
1573+ ["commit" , "-m" , "Initial commit" ],
1574+ env = env ,
1575+ )
1576+
1577+ return source_repo
1578+
1579+
1580+ def _setup_submodule_test (git_repo : GitSync , submodule_repo : git .Git ) -> str :
1581+ """Set up git_repo for submodule tests.
1582+
1583+ Returns the result of adding the submodule.
1584+ """
1585+ # Allow file protocol for submodule operations
1586+ git_repo .cmd .run (["config" , "protocol.file.allow" , "always" ])
1587+
1588+ # Add the submodule
1589+ return git_repo .cmd .submodules .add (
1590+ repository = str (submodule_repo .path ),
1591+ path = "vendor/lib" ,
1592+ )
1593+
1594+
1595+ def test_submodule_add (
1596+ git_repo : GitSync ,
1597+ submodule_repo : git .Git ,
1598+ ) -> None :
1599+ """Test GitSubmoduleManager.add()."""
1600+ # Allow file protocol for submodule operations
1601+ git_repo .cmd .run (["config" , "protocol.file.allow" , "always" ])
1602+
1603+ # Add the submodule
1604+ result = git_repo .cmd .submodules .add (
1605+ repository = str (submodule_repo .path ),
1606+ path = "vendor/lib" ,
1607+ )
1608+ # Should succeed (output varies by git version)
1609+ assert "fatal" not in result .lower () or result == ""
1610+
1611+
1612+ def test_submodule_ls (
1613+ git_repo : GitSync ,
1614+ submodule_repo : git .Git ,
1615+ ) -> None :
1616+ """Test GitSubmoduleManager.ls()."""
1617+ # Setup
1618+ _setup_submodule_test (git_repo , submodule_repo )
1619+
1620+ # List submodules
1621+ submodules = git_repo .cmd .submodules .ls ()
1622+ assert isinstance (submodules , list )
1623+ assert len (submodules ) >= 1
1624+
1625+ # Check the submodule has expected attributes
1626+ submodule = submodules [0 ]
1627+ assert submodule .path == "vendor/lib"
1628+ assert submodule .name is not None
1629+
1630+
1631+ def test_submodule_get (
1632+ git_repo : GitSync ,
1633+ submodule_repo : git .Git ,
1634+ ) -> None :
1635+ """Test GitSubmoduleManager.get()."""
1636+ # Setup
1637+ _setup_submodule_test (git_repo , submodule_repo )
1638+
1639+ # Get the submodule by path
1640+ submodule = git_repo .cmd .submodules .get (path = "vendor/lib" )
1641+ assert submodule is not None
1642+ assert submodule .path == "vendor/lib"
1643+
1644+
1645+ def test_submodule_get_not_found (
1646+ git_repo : GitSync ,
1647+ ) -> None :
1648+ """Test GitSubmoduleManager.get() raises when not found."""
1649+ # Try to get a non-existent submodule
1650+ with pytest .raises (ObjectDoesNotExist ):
1651+ git_repo .cmd .submodules .get (path = "nonexistent" )
1652+
1653+
1654+ def test_submodule_filter (
1655+ git_repo : GitSync ,
1656+ submodule_repo : git .Git ,
1657+ ) -> None :
1658+ """Test GitSubmoduleManager.filter()."""
1659+ # Setup
1660+ _setup_submodule_test (git_repo , submodule_repo )
1661+
1662+ # Filter by path
1663+ submodules = git_repo .cmd .submodules .filter (path = "vendor/lib" )
1664+ assert len (submodules ) == 1
1665+ assert submodules [0 ].path == "vendor/lib"
1666+
1667+ # Filter with no match
1668+ submodules = git_repo .cmd .submodules .filter (path = "nonexistent" )
1669+ assert len (submodules ) == 0
1670+
1671+
1672+ def test_submodule_init (
1673+ git_repo : GitSync ,
1674+ submodule_repo : git .Git ,
1675+ ) -> None :
1676+ """Test GitSubmoduleManager.init()."""
1677+ # Setup
1678+ _setup_submodule_test (git_repo , submodule_repo )
1679+
1680+ # Initialize all submodules
1681+ result = git_repo .cmd .submodules .init ()
1682+ assert result == "" or "fatal" not in result .lower ()
1683+
1684+
1685+ def test_submodule_update (
1686+ git_repo : GitSync ,
1687+ submodule_repo : git .Git ,
1688+ ) -> None :
1689+ """Test GitSubmoduleManager.update()."""
1690+ # Setup
1691+ _setup_submodule_test (git_repo , submodule_repo )
1692+
1693+ # Update all submodules
1694+ result = git_repo .cmd .submodules .update (init = True )
1695+ assert "fatal" not in result .lower () or result == ""
1696+
1697+
1698+ def test_submodule_sync (
1699+ git_repo : GitSync ,
1700+ submodule_repo : git .Git ,
1701+ ) -> None :
1702+ """Test GitSubmoduleManager.sync()."""
1703+ # Setup
1704+ _setup_submodule_test (git_repo , submodule_repo )
1705+
1706+ # Sync submodule URLs
1707+ result = git_repo .cmd .submodules .sync ()
1708+ assert result == "" or "fatal" not in result .lower ()
1709+
1710+
1711+ def test_submodule_summary (
1712+ git_repo : GitSync ,
1713+ submodule_repo : git .Git ,
1714+ ) -> None :
1715+ """Test GitSubmoduleManager.summary()."""
1716+ # Setup
1717+ _setup_submodule_test (git_repo , submodule_repo )
1718+
1719+ # Get summary
1720+ result = git_repo .cmd .submodules .summary ()
1721+ # Summary output may vary
1722+ assert isinstance (result , str )
1723+
1724+
1725+ def test_submodule_entry_status (
1726+ git_repo : GitSync ,
1727+ submodule_repo : git .Git ,
1728+ ) -> None :
1729+ """Test GitSubmoduleEntryCmd.status()."""
1730+ # Setup
1731+ _setup_submodule_test (git_repo , submodule_repo )
1732+
1733+ # Get the submodule and check status
1734+ submodule = git_repo .cmd .submodules .get (path = "vendor/lib" )
1735+ assert submodule .cmd is not None
1736+
1737+ result = submodule .cmd .status ()
1738+ assert isinstance (result , str )
1739+
1740+
1741+ def test_submodule_entry_init (
1742+ git_repo : GitSync ,
1743+ submodule_repo : git .Git ,
1744+ ) -> None :
1745+ """Test GitSubmoduleEntryCmd.init()."""
1746+ # Setup
1747+ _setup_submodule_test (git_repo , submodule_repo )
1748+
1749+ # Get the submodule and init it
1750+ submodule = git_repo .cmd .submodules .get (path = "vendor/lib" )
1751+ assert submodule .cmd is not None
1752+
1753+ result = submodule .cmd .init ()
1754+ assert result == "" or "fatal" not in result .lower ()
1755+
1756+
1757+ def test_submodule_entry_update (
1758+ git_repo : GitSync ,
1759+ submodule_repo : git .Git ,
1760+ ) -> None :
1761+ """Test GitSubmoduleEntryCmd.update()."""
1762+ # Setup
1763+ _setup_submodule_test (git_repo , submodule_repo )
1764+
1765+ # Get the submodule and init it
1766+ submodule = git_repo .cmd .submodules .get (path = "vendor/lib" )
1767+ assert submodule .cmd is not None
1768+
1769+ # Init and update
1770+ submodule .cmd .init ()
1771+ result = submodule .cmd .update ()
1772+ assert "fatal" not in result .lower () or result == ""
1773+
1774+
1775+ def test_submodule_foreach (
1776+ git_repo : GitSync ,
1777+ submodule_repo : git .Git ,
1778+ ) -> None :
1779+ """Test GitSubmoduleManager.foreach()."""
1780+ # Setup
1781+ _setup_submodule_test (git_repo , submodule_repo )
1782+
1783+ # Run foreach with a simple command
1784+ result = git_repo .cmd .submodules .foreach (command = "pwd" )
1785+ assert isinstance (result , str )
1786+
1787+
1788+ def test_submodule_dataclass_properties (
1789+ git_repo : GitSync ,
1790+ submodule_repo : git .Git ,
1791+ ) -> None :
1792+ """Test GitSubmodule dataclass properties."""
1793+ # Setup
1794+ _setup_submodule_test (git_repo , submodule_repo )
1795+
1796+ # Get the submodule
1797+ submodule = git_repo .cmd .submodules .get (path = "vendor/lib" )
1798+
1799+ # Check dataclass attributes
1800+ assert submodule .path == "vendor/lib"
1801+ assert submodule .name is not None
1802+ assert submodule .url is not None
1803+ assert submodule .sha is not None or submodule .status_prefix == "-"
1804+ assert submodule .cmd is not None
1805+
1806+ # Test initialized property
1807+ # After add, submodule should be initialized (prefix not '-')
1808+ assert submodule .initialized is True or submodule .status_prefix == "-"
0 commit comments