diff --git a/src/resolvelib/resolvers.py b/src/resolvelib/resolvers.py index ecc4dadb..bb4e0dfb 100644 --- a/src/resolvelib/resolvers.py +++ b/src/resolvelib/resolvers.py @@ -309,23 +309,30 @@ def _backjump(self, causes: list[RequirementInformation[RT, CT]]) -> bool: # Remove the state that triggered backtracking. del self._states[-1] - # Ensure to backtrack to a state that caused the incompatibility - incompatible_state = False + # Optimistically backtrack to a state that caused the incompatibility broken_state = self.state - while not incompatible_state: + while True: # Retrieve the last candidate pin and known incompatibilities. try: broken_state = self._states.pop() name, candidate = broken_state.mapping.popitem() except (IndexError, KeyError): raise ResolutionImpossible(causes) from None + + # If the current dependencies and the incompatible dependencies + # are overlapping then we have found a cause of the incompatibility current_dependencies = { self._p.identify(d) for d in self._p.get_dependencies(candidate) } - incompatible_state = not current_dependencies.isdisjoint( - incompatible_deps - ) + if not current_dependencies.isdisjoint(incompatible_deps): + break + + # Fallback: We should not backtrack to the point where + # broken_state.mapping is empty, so stop backtracking for + # a chance for the resolution to recover + if not broken_state.mapping: + break incompatibilities_from_broken = [ (k, list(v.incompatibilities)) diff --git a/tests/functional/python/inputs/case/issue-134.json b/tests/functional/python/inputs/case/issue-134.json new file mode 100644 index 00000000..c786e604 --- /dev/null +++ b/tests/functional/python/inputs/case/issue-134.json @@ -0,0 +1,17 @@ +{ + "index": "issue-134", + "requested": [ + "pandas<=1.4.0,>=1.3.5", + "pystac<=1.8.3,>=1.8.2", + "pystac-client<=0.3.3,>=0.3.2", + "sat-stac<=0.1.1" + ], + "resolved": { + "pandas": "1.3.5", + "pystac": "1.8.3", + "pystac-client": "0.3.3", + "sat-stac": "0.1.1", + "python-dateutil": "2.7.5", + "requests": "2.31.0" + } +} diff --git a/tests/functional/python/inputs/index/issue-134.json b/tests/functional/python/inputs/index/issue-134.json new file mode 100644 index 00000000..bbc4a8bc --- /dev/null +++ b/tests/functional/python/inputs/index/issue-134.json @@ -0,0 +1,66 @@ +{ + "pandas": { + "1.4.0": { + "dependencies": [ + "python-dateutil (>=2.8.1)" + ] + }, + "1.3.5": { + "dependencies": [ + "python-dateutil (>=2.7.3)" + ] + } + }, + "pystac": { + "1.8.3": { + "dependencies": [ + "python-dateutil (>=2.7.0)" + ] + }, + "1.8.2": { + "dependencies": [ + "python-dateutil (>=2.7.0)" + ] + } + }, + "pystac-client": { + "0.3.3": { + "dependencies": [ + "requests (>=2.25)", + "pystac (>=1.4.0)" + ] + }, + "0.3.2": { + "dependencies": [ + "requests (>=2.25)", + "pystac (~=1.2.0)" + ] + } + }, + "sat-stac": { + "0.1.1": { + "dependencies": [ + "python-dateutil (~=2.7.5)" + ] + }, + "0.1.0": { + "dependencies": [ + "requests (~=2.19.1)", + "python-dateutil (~=2.7.5)" + ] + } + }, + "python-dateutil": { + "2.9.0": { + "dependencies": [] + }, + "2.7.5": { + "dependencies": [] + } + }, + "requests": { + "2.31.0": { + "dependencies": [] + } + } +}