feat(submodule): add deinit method to Submodule (#2014)#2129
feat(submodule): add deinit method to Submodule (#2014)#2129mvanhorn wants to merge 2 commits intogitpython-developers:mainfrom
Conversation
…#2014) Mirrors the pattern of `Submodule.add`, `Submodule.update`, and `Submodule.remove` by exposing `git submodule deinit` as a first-class method, so callers no longer need the `repo.git.submodule('deinit', ...)` workaround noted in the issue. The method delegates to `git submodule deinit [--force] -- <path>` via `self.repo.git.submodule`, decorated with `@unbare_repo` to match the other mutating Submodule methods. It unregisters the submodule from `.git/config` and clears the working-tree directory while leaving `.gitmodules` and `.git/modules/<name>` intact, so a later `update()` can re-initialize. Closes gitpython-developers#2014.
There was a problem hiding this comment.
Pull request overview
Adds a first-class Submodule.deinit(force=False) API to GitPython’s submodule object model, so callers can deinitialize a submodule without manually invoking repo.git.submodule('deinit', ...).
Changes:
- Introduces
Submodule.deinit(force: bool = False)as a thin wrapper aroundgit submodule deinit [--force] -- <path>. - Marks
deinitas unsupported for bare repositories via@unbare_repo. - Updates typing imports to support the new method’s local argument list construction.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @unbare_repo | ||
| def deinit(self, force: bool = False) -> "Submodule": | ||
| """Run ``git submodule deinit`` on this submodule. | ||
|
|
||
| This is a thin wrapper around ``git submodule deinit <path>``, paralleling | ||
| :meth:`add`, :meth:`update`, and :meth:`remove`. It unregisters the | ||
| submodule (removes its entry from ``.git/config`` and empties the | ||
| working-tree directory) without deleting the submodule from | ||
| ``.gitmodules`` or its checked-out repository under ``.git/modules/``. | ||
| A subsequent :meth:`update` will re-initialize the submodule from the | ||
| retained contents. | ||
|
|
||
| :param force: | ||
| If ``True``, pass ``--force`` to ``git submodule deinit``. This | ||
| allows deinitialization even when the submodule's working tree has | ||
| local modifications that would otherwise block the command. | ||
|
|
||
| :return: | ||
| self | ||
|
|
||
| :note: | ||
| Doesn't work in bare repositories. | ||
| """ | ||
| args: List[str] = [] | ||
| if force: | ||
| args.append("--force") | ||
| args.extend(["--", self.path]) | ||
| self.repo.git.submodule("deinit", *args) | ||
| return self |
There was a problem hiding this comment.
The new public API Submodule.deinit() isn't covered by tests. Please add a targeted unit test that asserts the underlying git invocation (e.g., by mocking submodule.repo.git.submodule) for both force=False and force=True, including verifying the -- separator and the provided path argument.
Adds a targeted unit test that mocks Git.submodule and verifies deinit()
calls git submodule with ("deinit", "--", path) and deinit(force=True)
with ("deinit", "--force", "--", path), per Copilot review feedback.
|
Added
Verified locally with |
Closes #2014.
Adds a `Submodule.deinit(force=False)` method that wraps `git submodule deinit [--force] -- `, paralleling the existing `add` / `update` / `remove` methods. Callers no longer need the `repo.git.submodule('deinit', ...)` workaround mentioned in the issue.
Behavior
Why a thin wrapper
The full semantics of `git submodule deinit` (recursive children, working-tree checks, .git/modules retention) are already handled correctly by the git porcelain. Re-implementing that logic in Python for parity with the existing `remove` method would duplicate significant behavior. A thin wrapper gives callers a typed entry point without duplicating git's state machine.
Verification
🤖 AI-assisted.