Repository: pdm-project/pdm

Issue:
`pdm export --self` is incompatible with hashes (no `--without-hashes`)
- [x] I have searched the issue tracker and believe that this is not a duplicate.

## Steps to reproduce
```sh
pdm add requests
pdm export --self -o requirements.txt
```

The command creates this file

```
# This file is @generated by PDM.
# Please do not edit it manually.

certifi==2024.2.2 \
    --hash=sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f \
    --hash=sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1
charset-normalizer==3.3.2 \
    --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \
    --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \
    --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \
    --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \
    --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \
    --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \
    --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \
    --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \
    --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \
    --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \
    --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \
    --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \
    --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \
    --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \
    --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \
    --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \
    --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5
idna==3.7 \
    --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \
    --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0
requests==2.32.2 \
    --hash=sha256:dd951ff5ecf3e3b3aa26b40703ba77495dab41da839ae72ef3c8e5d8e2433289 \
    --hash=sha256:fc06670dd0ed212426dfeb94fc1b983d917c4f9847c863f313c9dfaaffb7c23c
urllib3==2.2.1 \
    --hash=sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d \
    --hash=sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19
.  # this package
```

## Actual behavior

Feeding this file into `pip install -r` will create an error, likely due to pypa/pip#4995

```
Processing /project
ERROR: Can't verify hashes for these file:// requirements because they point to directories:
    file:///project (from -r requirements.txt (line 34))
```

## Expected behavior

<!--A clear and concise description of what you expected to happen.-->
I believe a warning should be displayed when `--self` is used without `--no-hashes`, as it will create a file which does not work with pip. The same should be done for any VCS or file:// dependencies.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pdm-project/pdm/pull/2909?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) Report
Attention: Patch coverage is `66.66667%` with `2 lines` in your changes are missing coverage. Please review.
> Project coverage is 85.26%. Comparing base [(`c5967da`)](https://app.codecov.io/gh/pdm-project/pdm/commit/c5967da8cc455f59a747899e40e81c6cc80dd649?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) to head [(`17ddd13`)](https://app.codecov.io/gh/pdm-project/pdm/commit/17ddd13857f14d73ee03010034614efe417fda74?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).
> Report is 1 commits behind head on main.

| [Files](https://app.codecov.io/gh/pdm-project/pdm/pull/2909?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Patch % | Lines |
|---|---|---|
| [src/pdm/formats/requirements.py](https://app.codecov.io/gh/pdm-project/pdm/pull/2909?src=pr&el=tree&filepath=src%2Fpdm%2Fformats%2Frequirements.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#diff-c3JjL3BkbS9mb3JtYXRzL3JlcXVpcmVtZW50cy5weQ==) | 50.00% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/pdm-project/pdm/pull/2909?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #2909      +/-   ##
==========================================
- Coverage   85.33%   85.26%   -0.07%     
==========================================
  Files         106      106              
  Lines       10605    10610       +5     
  Branches     2308     2311       +3     
==========================================
- Hits         9050     9047       -3     
- Misses       1089     1094       +5     
- Partials      466      469       +3     
```

| [Flag](https://app.codecov.io/gh/pdm-project/pdm/pull/2909/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | Coverage Δ | |
|---|---|---|
| [unittests](https://app.codecov.io/gh/pdm-project/pdm/pull/2909/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project) | `85.05% <66.66%> (-0.07%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pdm-project/pdm/pull/2909?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pdm-project).


Patch:
diff --git a/news/2908.bugfix.md b/news/2908.bugfix.md
new file mode 100644
index 00000000..5f1da2d6
--- /dev/null
+++ b/news/2908.bugfix.md
@@ -0,0 +1,1 @@
+Fix a bug that VCS dependencies and `--self` don't work in the exported requirements.txt with hashes.
diff --git a/src/pdm/cli/commands/add.py b/src/pdm/cli/commands/add.py
index df4d2ce1..76db5728 100644
--- a/src/pdm/cli/commands/add.py
+++ b/src/pdm/cli/commands/add.py
@@ -155,8 +155,9 @@ class Command(BaseCommand):
         reqs = [
             r for g, deps in all_dependencies.items() if lock_groups is None or g in lock_groups for r in deps.values()
         ]
-        # pre-write the dependencies to the pyproject.toml to make them recognized by the resolver
-        project.add_dependencies(requirements, group, selection.dev or False, write=False)
+        # pre-write the named dependencies to the pyproject.toml to make them recognized by the resolver
+        named_req = {k: r for k, r in requirements.items() if r.is_named}
+        project.add_dependencies(named_req, group, selection.dev or False, write=False)
         with hooks.skipping("post_lock"):
             resolved = do_lock(
                 project,
diff --git a/src/pdm/formats/requirements.py b/src/pdm/formats/requirements.py
index 0e40c279..f4ff02b9 100644
--- a/src/pdm/formats/requirements.py
+++ b/src/pdm/formats/requirements.py
@@ -189,6 +189,8 @@ def export(
     for candidate in sorted(candidates, key=lambda x: x.identify()):  # type: ignore[attr-defined]
         if isinstance(candidate, Candidate):
             req = candidate.req.as_pinned_version(candidate.version)
+            if options.hashes and not candidate.hashes:
+                raise PdmUsageError(f"Hash is not available for '{req}', please export with `--no-hashes` option.")
         else:
             assert isinstance(candidate, Requirement)
             req = candidate
@@ -203,6 +205,8 @@ def export(
         lines.append("\n")
     if (options.self or options.editable_self) and not project.is_distribution:
         raise PdmUsageError("Cannot export the project itself in a non-library project.")
+    if options.hashes and (options.self or options.editable_self):
+        raise PdmUsageError("Hash is not available for `--self/--editable-self`. Please export with `--no-hashes`.")
     if options.self:
         lines.append(".  # this package\n")
     elif options.editable_self:


Test patch:
diff --git a/tests/cli/test_others.py b/tests/cli/test_others.py
index 90541ce0..7a5123dc 100644
--- a/tests/cli/test_others.py
+++ b/tests/cli/test_others.py
@@ -176,10 +176,16 @@ def test_export_to_requirements_txt(pdm, fixture_project):
     assert result.output.strip() == requirements_txt.read_text().strip()
 
     result = pdm(["export", "--self"], obj=project)
+    assert result.exit_code == 1
+
+    result = pdm(["export", "--editable-self"], obj=project)
+    assert result.exit_code == 1
+
+    result = pdm(["export", "--no-hashes", "--self"], obj=project)
     assert result.exit_code == 0
     assert ".  # this package\n" in result.output.strip()
 
-    result = pdm(["export", "--editable-self"], obj=project)
+    result = pdm(["export", "--no-hashes", "--editable-self"], obj=project)
     assert result.exit_code == 0
     assert "-e .  # this package\n" in result.output.strip()
 
@@ -227,8 +233,19 @@ def test_show_update_hint(pdm, project, monkeypatch):
 @pytest.mark.usefixtures("repository")
 def test_export_with_platform_markers(pdm, project):
     pdm(["add", "--no-sync", 'urllib3; sys_platform == "fake"'], obj=project, strict=True)
-    result = pdm(["export"], obj=project, strict=True)
+    result = pdm(["export", "--no-hashes"], obj=project, strict=True)
     assert 'urllib3==1.22; sys_platform == "fake"' in result.output.splitlines()
 
-    result = pdm(["export", "--no-markers"], obj=project, strict=True)
+    result = pdm(["export", "--no-markers", "--no-hashes"], obj=project, strict=True)
     assert not any("urllib3" in line for line in result.output.splitlines())
+
+
+@pytest.mark.usefixtures("repository", "vcs")
+def test_export_with_vcs_deps(pdm, project):
+    pdm(["add", "--no-sync", "git+https://github.com/test-root/demo.git"], obj=project, strict=True)
+    result = pdm(["export"], obj=project)
+    assert result.exit_code != 0
+
+    result = pdm(["export", "--no-hashes"], obj=project)
+    assert result.exit_code == 0
+    assert "demo @ git+https://github.com/test-root/demo.git@1234567890abcdef" in result.output.splitlines()
diff --git a/tests/test_formats.py b/tests/test_formats.py
index 52831118..874b2ab9 100644
--- a/tests/test_formats.py
+++ b/tests/test_formats.py
@@ -175,12 +175,12 @@ def test_import_requirements_with_group(project):
 
 
 def test_export_requirements_with_self(project):
-    result = requirements.export(project, [], ns(self=True))
+    result = requirements.export(project, [], ns(self=True, hashes=False))
     assert result.strip().splitlines()[-1] == ".  # this package"
 
 
 def test_export_requirements_with_editable_self(project):
-    result = requirements.export(project, [], ns(editable_self=True))
+    result = requirements.export(project, [], ns(editable_self=True, hashes=False))
     assert result.strip().splitlines()[-1] == "-e .  # this package"