GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||
GeezerVison is showing github.com — Reject submodule move destinations through intermediate symlinks by Byron · Pull Request #2232 · gitpython-developers/GitPython Conversation
|
| File | Summary and findings |
|---|---|
test/test_submodule.py |
Adds symlink-safety tests. Moderate findings: missing .gitmodules preflight in an initialized update() path (2 votes), and inconsistent dangling-final-symlink expectation (2 votes). |
git/objects/submodule/base.py |
Implements path validation. Critical finding: forced removal may continue after a symlink-related ValueError instead of rejecting before mutation (2 votes). |
doc/source/changes.rst |
Adds the 3.1.63 changelog entry. Critical finding: VERSION remains 3.1.62, breaking release validation and publication consistency (1 vote). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unresolved findings can bypass validation or cause incorrect repository state changes.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
git/objects/submodule/base.py:1237
module=True and the checkout path is symlinked, module_exists() below catches the ValueError from self.abspath and returns False. Removal then skips the checkout deletion but continues deleting the index and configuration, so remove(force=True) partially unregisters the submodule instead of rejecting it. Validate the checkout path before calling module_exists(). self._validated_name(self.name)
if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:1103
osp.exists() test returns false for them. On POSIX, _renames() then calls os.rename, which replaces the dangling link and lets the move update repository metadata, contradicting the new test's required OSError and no-side-effects behavior. Reject a dangling final link before the destination-removal branch. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
test/test_submodule.py:168
allow_final_symlink=True is used for destination validation, a dangling final symlink is not seen by osp.exists() or osp.isfile(), so move() reaches _renames() and os.rename() replaces the dangling link on POSIX. This branch therefore does not raise OSError and mutates repository state, causing the new test to fail. If dangling final links must be rejected, check lexists before removal; otherwise adjust the expectation to preserve the existing behavior. with pytest.raises(OSError if kind == "dangling" else ValueError):
submodule.move("destination")
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
84d33f3 to
286af15
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unresolved moderate findings affect update, forced removal, and dangling symlink handling.
git/objects/submodule/base.py:258
_config_parser. update() starts with self.module() and remote fetching and, for an already initialized checkout at the requested revision, can return without opening .gitmodules, so the gitmodules/update case added in this PR does not raise for the symlink. Add the .gitmodules preflight at the start of update(), before fetching. fp_module = cls._checked_abspath(repo.working_tree_dir, cls.k_modules_file)
git/objects/submodule/base.py:1239
.gitmodules; when module=True, the module_exists() call below invokes self.module(), whose abspath now raises for a symlinked checkout, but module_exists() catches every exception and returns False. remove(force=True) therefore skips checkout deletion and continues deleting the index/configuration, so the checkout/remove case added in this PR is not rejected and can partially apply. Validate the checkout path before calling module_exists(). if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:1105
allow_final_symlink=True does not handle the new dangling-link case: osp.exists() is false for a dangling symlink, so the removal block is skipped and _renames() calls os.rename(), which replaces that link on POSIX. The kind == "dangling" assertion at test/test_submodule.py:167-168 will therefore fail and the destination link is changed; reject a lexists-but-not-exists destination before renaming. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
test/test_submodule.py:167
allow_final_symlink=True lets it through, osp.isfile and osp.exists are false, and _renames uses os.rename, which replaces the dangling link. That contradicts this test and the stated preservation of final-component behavior; treat dangling like empty (or add an explicit lexists rejection if rejection is intended). with pytest.raises(OSError if kind == "dangling" else ValueError):
test/test_submodule.py:234
update branch does not consult .gitmodules when the checkout is already initialized: it calls self.module(), fetches, and can return at the same commit without reaching _config_parser. A symlinked .gitmodules therefore is not rejected and this pytest.raises case fails; add the shared .gitmodules preflight in update() before fetching. if operation == "update":
sm.update()
Sorry, something went wrong.
There was an error while loading. Please reload this page.
A quick rubber-stamp, admittedly. V4 will probably review all tests and make it more proper, if there can be such a thing in python anyway. <!-- agent --> Both Windows failures came from os.renames() pruning a directory symlink above the source after the rename succeeded. Unlike POSIX, Windows rmdir() can remove a directory symlink even when its target is nonempty. Moving a checkout through a worktree alias therefore deleted the alias and broke configuration updates and rollback. Renaming metadata through a linked .git/modules directory deleted that link and broke config.lock creation. Route checkout moves, rollback, and metadata renames through one helper. It creates destination parents and renames the source, then prunes empty source parents only until it reaches a symlink or a directory it cannot remove. This keeps ordinary empty-directory cleanup while preserving parent links and their targets, including targets that become empty. Leaf symlinks continue to move as links rather than moving their targets. Exercise Windows directory-symlink removal semantics on POSIX in the existing compatibility tests, and use native behavior on Windows. Both reported failures reproduced locally before the fix. Strengthen assertions that worktree and metadata parent aliases survive, their targets remain directories, and leaf metadata symlinks move without moving their targets. A further Windows run exposed a separate sharing violation during the checkout move. Submodule.add() read HEAD through its temporary Repo but left that Repo's persistent cat-file processes open. Those processes can hold the checkout as their current directory and prevent its rename. Close the owned Repo with a context manager when reading HEAD, including on read failure, instead of waiting for garbage collection. Add a regression that observes the real cat-file processes started for the new checkout and requires them to have exited before add() returns. It failed before the fix and now passes, along with the immediate move. The remaining metadata failures also reproduce with Python 3.7's Windows path semantics: ntpath.realpath is an alias of abspath and does not resolve symlinks. Relative core.worktree values were calculated from the metadata alias instead of the repository directory Git actually opens. This broke add/reconnect HEAD reads and made moves and renames point at nonexistent worktrees. The SHA/dubious-ownership message was a secondary read failure. Use pathlib.Path.resolve(), which resolves Windows symlinks on Python 3.7, for both endpoints of gitfile/config rewrites and for metadata removal. Run metadata and worktree-alias tests with native and simulated Windows 3.7 realpath behavior. The simulation reproduced all eight reported failures plus a leaf-symlink removal failure before this change. Reference: https://github.com/python/cpython/blob/3.7/Lib/ntpath.py and https://github.com/python/cpython/blob/3.7/Lib/pathlib.py. Validation: 183 passed, 3 skipped, and 1 expected failure across the submodule and diff suites plus the commit-message hook success test on macOS. Ruff lint and formatting, mypy for the changed module, and git diff --check passed. Native Windows validation remains for CI. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <[email protected]>
286af15 to
2bfd829
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Three moderate review findings remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
git/objects/submodule/base.py:1241
.gitmodules but not the checkout. When the checkout is a symlink, module_exists() calls self.module(), catches the ValueError from self.abspath, and returns False; remove() then continues to delete the index and configuration below instead of rejecting the operation. Validate the checkout path before calling module_exists() whenever module is enabled. if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
test/test_submodule.py:234
gitmodules/update parameter does not reach a rejecting path for an initialized submodule whose HEAD already matches the index: update() can use self.module(), fetch, and finish without invoking _config_parser. Consequently this pytest.raises block fails for that combination. Add an explicit .gitmodules preflight at the start of update() before fetching so the regression case is implemented rather than only asserted. with pytest.raises(ValueError, match="contains a symbolic link"):
if operation == "update":
sm.update()
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
ef28587 to
d985a65
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unresolved review findings remain in path validation, metadata cleanup, and regression coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
git/objects/submodule/base.py:1241
.gitmodules. The module_exists() call below catches the ValueError raised by self.abspath and returns False, so remove(force=True) skips physical deletion and still deletes the index/config entries. Validate the checkout path whenever module is true before recursive/removal work. if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:1107
allow_final_symlink=True skips the final component, a dangling destination is false for both osp.isfile() and osp.exists() and falls through to _renames, which replaces the dangling link on POSIX. The new kind == "dangling" branch therefore will not raise as asserted; use osp.lexists() for the destination existence check if dangling leaf links are meant to remain rejected. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
test/test_submodule.py:235
gitmodules + update combination is not rejected when the module is already initialized: update() calls self.module() and fetches the child repository, but does not read .gitmodules, so _config_parser's symlink check is never reached. This parameterized case will fail; add an explicit .gitmodules preflight to update(), or remove this expectation if update() is intentionally allowed to use a symlinked file. with pytest.raises(ValueError, match="contains a symbolic link"):
if operation == "update":
sm.update()
test/test_submodule.py:169
allow_final_symlink=True lets a dangling leaf through, osp.exists() is false, and _renames() calls os.rename, which replaces the dangling link and completes the move. The assertion therefore fails and the repository state changes; either reject dangling leaf links before the rename (for example with lexists) or update this case to match the intended final-link compatibility behavior. with pytest.raises(OSError if kind == "dangling" else ValueError):
submodule.move("destination")
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
rubber stamp <!-- agent --> The Python 3.7 Windows path simulation wrapped the entire os.path module in Mock. On Windows, all six simulated removal cases hit sharing violations that the default error handling converted into skipped tests. Use a SimpleNamespace copy of the path module and replace only realpath with abspath. Patch only the submodule module's osp binding so pathlib keeps its own resolver and other path operations remain ordinary calls. This retains Python 3.7 compatibility and allows the removal cases to run successfully without Windows permission-error suppression. Before removing a submodule, resolve and verify its metadata directory, then assert that removal deletes it as well as the checkout. Checking only the checkout could miss metadata left behind through a directory symlink. Validation: all 57 focused submodule compatibility and process-cleanup tests passed on Windows with Python 3.10 and HIDE_WINDOWS_KNOWN_ERRORS=0. Ruff 0.16.5 lint and formatting checks and git diff --check passed. Native Python 3.7 was unavailable; its realpath behavior is simulated. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <[email protected]>
d985a65 to
415c211
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Two moderate findings remain regarding dangling destination symlinks and an ineffective gitmodules update test case.
git/objects/submodule/base.py:1107
osp.isfile() above and the osp.exists() check below follow links, so a dangling symlink falls through to _renames(), whose os.rename() replaces that link on POSIX. The new kind == "dangling" test therefore fails instead of raising; detect the destination with osp.lexists() (while retaining the existing empty-leaf replacement behavior). module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
test/test_submodule.py:233
gitmodules + update case does not reach the new guard: for this already initialized checkout, update() proceeds through self.module() and fetch_remotes() without reading .gitmodules, so _config_parser is never called and no ValueError is raised. This parameterized case will fail; either preflight .gitmodules before fetching or remove/adjust this expectation if matching Git's less restrictive behavior is intentional. with pytest.raises(ValueError, match="contains a symbolic link"):
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Three unresolved moderate findings affect symlink validation and expected test behavior.
git/objects/submodule/base.py:1107
allow_final_symlink=True still treats a dangling destination as absent: osp.exists() below is false, so _renames() replaces the dangling link and the move succeeds on POSIX. The new test_move_leaf_symlink_compatibility case expects an OSError; use osp.lexists() for the destination check (or explicitly reject dangling final links) before moving. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
git/objects/submodule/base.py:1247
.gitmodules. For a symlinked checkout, module_exists() catches the ValueError raised by self.abspath and returns False, so remove(force=True) skips the checkout deletion and continues deleting the index/configuration. That contradicts the new checkout-symlink case at test/test_submodule.py:241; validate the checkout path before calling module_exists(). if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:259
update() loads the module, fetches, and can finish without invoking _config_parser, so a symlinked working-tree .gitmodules is accepted while the new matrix at test/test_submodule.py:233 expects ValueError. Add the same .gitmodules preflight to update() before module access, inside its existing error-handling scope. fp_module = cls._checked_abspath(repo.working_tree_dir, cls.k_modules_file)
Sorry, something went wrong.
There was an error while loading. Please reload this page.
<!-- agent --> Ubuntu CI reported FileNotFoundError for maintenance.lock while running test_timeout_funcs. The test performs normal pull/fetch calls before its forced timeouts, and Git can launch detached automatic maintenance from those operations. Maintenance can then race with the fixture's recursive removal of the temporary repository, removing a lock file after cleanup has enumerated it. Disable maintenance.auto in this test's temporary repository and set gc.auto to zero for older Git versions that use automatic garbage collection. This removes background housekeeping unrelated to the timeout assertions without weakening repository cleanup or changing library behavior. Use mock.patch.object for the global forced termination status so an assertion failure cannot leak the override into subsequent tests. Validation: Git Trace2 recorded three detached maintenance launches in the original test and none with the fix. The timeout test then passed 20 consecutive runs locally on macOS. Ruff lint and formatting checks and git diff --check passed. The original Ubuntu cleanup exception was not reproduced locally; tracing verified removal of the suspected race source. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <[email protected]>
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unresolved implementation and regression-test issues affect symlink safety and side-effect-free behavior.
Previously missed (1) — in code that hasn't changed since the last review.
test/test_submodule.py:322
git.objects.submodule.base.osp does not affect pathlib.Path.resolve(), which uses pathlib's own os.path.realpath. _write_git_file_and_module_config() now calls Path.resolve() directly at base.py:487-488, so both fixture parameters exercise the same resolver and the windows37-realpath case does not model the Python 3.7 Windows behavior it claims to cover. Patch the resolver used by pathlib or make the resolution helper injectable.git/objects/submodule/base.py:1107
allow_final_symlink=True lets a dangling destination skip the component check, but the later osp.exists() is also false for a dangling link. On POSIX, _renames() then calls os.rename(source, destination), which replaces that dangling symlink, so the new kind == "dangling" test succeeds instead of raising and the move mutates repository state. Treat the destination as present with lexists() before inspecting or replacing it. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
git/objects/submodule/base.py:1247
module=True, module_exists() below catches the ValueError raised by self.module()/self.abspath and returns False. Forced removal then skips checkout deletion but still deletes the index and configuration, so the added checkout/remove case cannot pass and the rejected operation is not side-effect free. Validate the checkout path before calling module_exists(), or remove this case if that compatibility is intentional. self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
test/test_submodule.py:235
update case cannot pass for link_kind == "gitmodules": update() calls self.module() and fetch_remotes() directly and never enters _config_parser, so replacing .gitmodules with a symlink is not observed and sm.update() completes without the expected ValueError. Either add an explicit .gitmodules preflight to update(), or remove this operation from the rejection matrix if Git-compatible initialized updates are intentionally allowed. sm.update()
test/test_submodule.py:241
checkout/remove case does not take this rejection path. With .gitmodules intact, remove() calls module_exists(), which catches the ValueError raised by self.abspath for the symlinked checkout and returns False; removal then continues into index and configuration deletion. The new pytest.raises at test/test_submodule.py:241 therefore fails and the operation is not side-effect-free. If Git-compatible removal is intentional, remove "remove" from this matrix; otherwise preflight the checkout before module_exists(). sm.remove(force=True)
test/test_submodule.py:308
git.Repo(alias) resolves a Path input before storing working_tree_dir (git/util.py:592-601), so this test operates on the real parent directory rather than through the alias symlink. It therefore does not exercise the claimed symlink-above-worktree case or the new root-handling path; preserve the symlink in the repository's working-tree path before running the operations. with git.Repo(alias) as parent:
added = Submodule.add(parent, "new", "new", sm.url)
test/test_submodule.py:220
sm.rename("nested/module") has already moved the checkout from root / "module" to root / "nested/module". For link_kind == "checkout", this path.rename(target) therefore raises FileNotFoundError before the operation under test runs; point the checkout case at the renamed path. path = root / (".gitmodules" if link_kind == "gitmodules" else "module")
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Moderate unresolved issues affect update, removal, and dangling-symlink handling.
git/objects/submodule/base.py:259
_config_parser is not reached by update() when the checkout is already initialized: update() calls self.module(), fetches remotes, and can finish without reading .gitmodules. Consequently the new gitmodules/update case in test_submodule_rejects_checkout_and_gitmodules_symlinks returns successfully instead of raising before side effects. Add an explicit .gitmodules preflight to update(), or remove that expectation if this operation is intentionally allowed. fp_module = cls._checked_abspath(repo.working_tree_dir, cls.k_modules_file)
git/objects/submodule/base.py:1259
module_exists() below calls self.module() and swallows the ValueError from self.abspath when the checkout itself is symlinked. With module=True, removal therefore skips deleting the checkout and proceeds to index/configuration deletion; for a normal .gitmodules this succeeds, and for a symlinked .gitmodules it fails only after the index and parent config have changed. Validate the checkout path before module_exists() so removal rejects without partial state changes. if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:1119
allow_final_symlink=True skips validation of the destination leaf, but the later osp.exists(module_checkout_abspath) is false for a dangling leaf symlink. _renames() then invokes os.rename and replaces that link on POSIX, so the new kind == "dangling" test succeeds and changes repository state instead of raising. Use osp.lexists() for the destination existence check and reject the dangling case before removing or moving anything. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
test/test_submodule.py:169
dangling case does not raise: allow_final_symlink=True lets the destination through, osp.exists() is false for a dangling link, and _renames() calls os.rename, which replaces that link. This test therefore fails and conflicts with the stated preservation of final-component behavior; either assert the successful replacement for this case or add an explicit lexists rejection in move if rejection is intended. with pytest.raises(OSError if kind == "dangling" else ValueError):
submodule.move("destination")
test/test_submodule.py:235
update() follows self.module(), fetches remotes, and can reset the checkout without invoking _config_parser, so a symlinked .gitmodules is not rejected by this branch. The gitmodules + update parameter therefore completes instead of raising and makes this new regression test fail; narrow the case to operations that read configuration, or add an explicit update preflight only if rejecting this Git-compatible behavior is intended. with pytest.raises(ValueError, match="contains a symbolic link"):
if operation == "update":
sm.update()
Sorry, something went wrong.
There was an error while loading. Please reload this page.
rubber stamp <!-- agent --> Removing a submodule retains its metadata alias but deletes the target. Adding the same submodule again then passes a dangling directory symlink to git clone --separate-git-dir. Git for Windows fails while copying its template files through that alias. Both native-realpath and simulated Windows 3.7 remove-leaf cases reproduced this failure locally. When the metadata destination is a leaf symlink, pass its target to Git and leave the alias intact. Resolve relative targets against the link's parent, create missing target parents through the existing clone setup, and let Git create the repository directory itself. Precreating that directory is insufficient because Git rejects an existing separate git repository destination. Read the link explicitly because Python 3.7 on Windows cannot resolve a dangling link with Path.resolve(). Normalize the Windows namespace prefix returned by newer os.readlink implementations, including UNC targets, and use forward slashes before passing the path through Git's URL logic. The shared clone helper covers add() and initialization through update(). Add regression coverage for direct cloning through absolute and relative dangling metadata links with missing target parents. Verify that the link and its stored target are retained, metadata is created at the target, and the resulting checkout works under both realpath modes. Validation: 61 focused tests passed on Windows/Python 3.10 with HIDE_WINDOWS_KNOWN_ERRORS=0, including both reported failures. Ruff 0.16.5 lint and formatting checks and git diff --check passed. Native Python 3.7 and UNC network shares were not available for execution. A broader run also exposed sharing violations in sibling-reinitialization tests before cloning; a representative case also failed with the unchanged HEAD clone helper loaded in memory. That separate removal issue is not changed here. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <[email protected]>
rubber stamp, just get this through CI OMG <!-- agent --> The dangling-metadata regression passed pathlib.Path objects to os.readlink() when capturing and checking the symlink target. Windows Python 3.7 requires a string argument, so all four parameter combinations failed with TypeError before exercising the clone fix. Convert the path to str at both calls. The production clone helper already passes a string and needs no change. Keep the target-preservation assertions and the absolute/relative and realpath-mode coverage intact. Validation: reproduced all four TypeErrors on Windows/Python 3.10 with an in-memory readlink wrapper enforcing the Python 3.7 string requirement. After the conversions, all four cases passed with the same wrapper and Windows permission-error suppression disabled. Ruff 0.16.5 lint and formatting checks and git diff --check passed. Native Python 3.7 was not available locally. Assisted-by: GPT 6.0 Co-authored-by: GPT 6.0 <[email protected]>
939d60e to
2aca177
Compare
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Unresolved moderate findings affect dangling symlink handling, forced removal, and update validation.
git/objects/submodule/base.py:1119
allow_final_symlink=True lets a dangling destination reach the osp.exists() check below, which returns false for dangling links; _renames() then calls os.rename and replaces the link on POSIX. The new kind == "dangling" case therefore does not raise and the move mutates the index/configuration. Use osp.lexists(module_checkout_abspath) for the destination-presence check so dangling links are rejected before the move. module_checkout_abspath = self._checkout_abspath(module_checkout_path, allow_final_symlink=True)
git/objects/submodule/base.py:1259
module_exists() below catches every exception from self.module(), including the ValueError raised by self.abspath for a symlinked checkout. Consequently remove(force=True) skips the module branch and continues to delete the index/configuration instead of rejecting the operation; the new (checkout, remove) regression case fails and can leave partial state. Validate the checkout path before calling module_exists() when module is true. if configuration:
self._checked_abspath(self.repo.working_tree_dir, self.k_modules_file)
git/objects/submodule/base.py:259
update(): it first opens the module and fetches, and when the module is already at self.binsha it never reads .gitmodules. Consequently the new gitmodules + update parameterization does not raise and the test suite fails; either add an update preflight if rejection is intended, or remove that case if Git-compatible behavior is intended. fp_module = cls._checked_abspath(repo.working_tree_dir, cls.k_modules_file)
test/test_submodule.py:235
update() calls self.module() and fetches/resets the child repository without opening the working-tree .gitmodules; therefore the (link_kind="gitmodules", operation="update") combination returns successfully instead of entering this pytest.raises. Either add an explicit preflight if rejecting this case is required, or exclude this pair; as written the new test fails. with pytest.raises(ValueError, match="contains a symbolic link"):
if operation == "update":
sm.update()
test/test_submodule.py:169
allow_final_symlink=True permits it, both osp.isfile() and the later osp.exists() check return false, and _renames() replaces the dangling link with os.rename(). The added kind == "dangling" case therefore succeeds and mutates repository state instead of entering this exception block; update the expectation to preserve the existing replacement behavior, or add an explicit dangling-link rejection. with pytest.raises(OSError if kind == "dangling" else ValueError):
submodule.move("destination")
Sorry, something went wrong.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.