Repository: pdm-project/pdm

Issue:
`--without` does not respect groups in dev-dependencies
- [x] I have searched the issue tracker and believe that this is not a duplicate.

**Make sure you run commands with `-v` flag before pasting the output.**

## Steps to reproduce

<!--Describe the minimized example of how to reproduce the bug-->

run the following command in pdm repository
```shell
pdm lock --without doc
```

## Actual behavior

<!--A clear and concise description the result of the above steps-->

lock file is generated still with `doc` group

## Expected behavior

<!--A clear and concise description of what you expected to happen.-->

lock file is generated without `doc` group

## Environment Information

```bash
# Paste the output of `pdm info && pdm info --env` below:
PDM version:
  2.13.3
Python Interpreter:
  /home/huxuan/Code/pdm/.venv/bin/python (3.10)
Project Root:
  /home/huxuan/Code/pdm
Local Packages:

{
  "implementation_name": "cpython",
  "implementation_version": "3.10.12",
  "os_name": "posix",
  "platform_machine": "x86_64",
  "platform_release": "5.15.146.1-microsoft-standard-WSL2",
  "platform_system": "Linux",
  "platform_version": "#1 SMP Thu Jan 11 04:09:03 UTC 2024",
  "python_full_version": "3.10.12",
  "platform_python_implementation": "CPython",
  "python_version": "3.10",
  "sys_platform": "linux"
}
```



Discussion:

Patch:
diff --git a/news/2799.bugfix.md b/news/2799.bugfix.md
new file mode 100644
index 0000000000..7cb7753cd9
--- /dev/null
+++ b/news/2799.bugfix.md
@@ -0,0 +1,1 @@
+Make `--without` respect groups in `dev-dependencies`
diff --git a/src/pdm/cli/filters.py b/src/pdm/cli/filters.py
index 8c8f31dc94..9cf47244e9 100644
--- a/src/pdm/cli/filters.py
+++ b/src/pdm/cli/filters.py
@@ -88,8 +88,8 @@ def _translated_groups(self) -> list[str]:
             groups_set.update(dev_groups)
         if ":all" in groups:
             groups_set.discard(":all")
-            optional_groups -= set(self.excluded_groups)
             groups_set.update(optional_groups)
+            groups_set -= set(self.excluded_groups)
 
         invalid_groups = groups_set - set(project.iter_groups())
         if invalid_groups:


Test patch:
diff --git a/tests/cli/test_install.py b/tests/cli/test_install.py
index 4686fc9796..2889cbc0ee 100644
--- a/tests/cli/test_install.py
+++ b/tests/cli/test_install.py
@@ -327,7 +327,7 @@ def test_fix_package_type_and_update(fixture_project, pdm, working_set):
 @pytest.mark.parametrize("args", exclusion_cases)
 def test_install_all_with_excluded_groups(project, working_set, pdm, args):
     project.add_dependencies({"urllib3": parse_requirement("urllib3")}, "url")
-    project.add_dependencies({"pytz": parse_requirement("pytz")}, "tz")
+    project.add_dependencies({"pytz": parse_requirement("pytz")}, "tz", True)
     project.add_dependencies({"pyopenssl": parse_requirement("pyopenssl")}, "ssl")
     pdm(["install", *args], obj=project, strict=True)
     assert "urllib3" in working_set
@@ -338,7 +338,7 @@ def test_install_all_with_excluded_groups(project, working_set, pdm, args):
 @pytest.mark.parametrize("args", exclusion_cases)
 def test_sync_all_with_excluded_groups(project, working_set, pdm, args):
     project.add_dependencies({"urllib3": parse_requirement("urllib3")}, "url")
-    project.add_dependencies({"pytz": parse_requirement("pytz")}, "tz")
+    project.add_dependencies({"pytz": parse_requirement("pytz")}, "tz", True)
     project.add_dependencies({"pyopenssl": parse_requirement("pyopenssl")}, "ssl")
     pdm(["lock", "-G:all"], obj=project, strict=True)
     pdm(["sync", *args], obj=project, strict=True)
diff --git a/tests/cli/test_lock.py b/tests/cli/test_lock.py
index 0ffd91e7e7..b722388959 100644
--- a/tests/cli/test_lock.py
+++ b/tests/cli/test_lock.py
@@ -274,3 +274,24 @@ def test_lock_exclude_newer(project, pdm):
 
     pdm(["lock"], obj=project, strict=True, cleanup=False)
     assert project.locked_repository.all_candidates["zipp"].version == "3.7.0"
+
+
+exclusion_cases = [
+    pytest.param(("-G", ":all", "--without", "tz,ssl"), id="-G :all --without tz,ssl"),
+    pytest.param(("-G", ":all", "--without", "tz", "--without", "ssl"), id="-G :all --without tz --without ssl"),
+    pytest.param(("--with", ":all", "--without", "tz,ssl"), id="--with all --without tz,ssl"),
+    pytest.param(("--with", ":all", "--without", "tz", "--without", "ssl"), id="--with all --without tz --without ssl"),
+    pytest.param(("--without", "tz", "--without", "ssl"), id="--without tz --without ssl"),
+    pytest.param(("--without", "tz,ssl"), id="--without tz,ssl"),
+]
+
+
+@pytest.mark.parametrize("args", exclusion_cases)
+def test_lock_all_with_excluded_groups(project, pdm, args):
+    project.add_dependencies({"urllib3": parse_requirement("urllib3")}, "url")
+    project.add_dependencies({"pytz": parse_requirement("pytz")}, "tz", True)
+    project.add_dependencies({"pyopenssl": parse_requirement("pyopenssl")}, "ssl")
+    pdm(["lock", *args], obj=project, strict=True)
+    assert "urllib3" in project.locked_repository.all_candidates
+    assert "pytz" not in project.locked_repository.all_candidates
+    assert "pyopenssl" not in project.locked_repository.all_candidates
diff --git a/tests/test_utils.py b/tests/test_utils.py
index b82ffac078..a5099e6cc8 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -513,7 +513,7 @@ def test_dependency_group_selection(project, args, golden):
     [
         ({"groups": [":all"], "excluded_groups": ["web"]}, ["default", "auth", "test", "doc"]),
         ({"groups": [":all"], "excluded_groups": ["web", "auth"]}, ["default", "test", "doc"]),
-        ({"groups": [":all"], "excluded_groups": ["default", "test"]}, ["default", "web", "auth", "test", "doc"]),
+        ({"groups": [":all"], "excluded_groups": ["default", "test"]}, ["default", "web", "auth", "doc"]),
     ],
 )
 def test_exclude_optional_groups_from_all(project, args, golden):