Repository: jazzband/pip-tools

Issue:
`pip-compile requirements.txt` compiles dependencies to the same input file
It looks like pip-tools has a bug, when `reqs.txt` is passed as an input file then `pip-compile reqs.txt ` would resolve dependencies to the same input file. I remember we had a guard against this scenario, but apparently, we lost it somewhere in between new features.

_Originally posted by @atugushev in https://github.com/jazzband/pip-tools/issues/1760#issuecomment-1345420673_

#### Environment Versions

1. OS Type macOS
1. Python version: `3.11`
1. pip version: `22.3`
1. pip-tools version: `6.11.0`

#### Steps to replicate

1. `echo six > requirements.txt`
2. `pip-compile requirements.txt`
3. `ls requirements.*` 

#### Expected result

I would expect an error that `requirements.txt` is using as input and output file.

#### Actual result

```
$ ls requirements.*
requirements.txt
```




Discussion:
In #1650 (rejected), in the case where the input file was the same as the derived output file, I added another `.txt` extension. The change is in [this commit](https://github.com/jazzband/pip-tools/pull/1650/commits/996f21c0f97cd69e8a5fd2d099c6d83fae1a6802), which also addresses the current dangerous behavior when the input file has no extension and a parent folder has a `.`.

```console
$ echo six > requirements.txt
$ pip-compile requirements.txt
$ ls requirements.*
```
```
requirements.txt  requirements.txt.txt
```
@AndydeCleyre 

thanks for the links. Although it looks weird to have `requirements.txt.txt` as a lock file, have you considered to raise an error instead when you've been working on this?

UPDATE: there was a guard long time ago: https://github.com/jazzband/pip-tools/blob/dbf6622341d9b67e326954ddeccff6334e5bec49/piptools/scripts/compile.py#L136
It didn't seem useful to me to raise an exception. I often use pip-tools locally on projects that don't use it, so I like the ability to easily compile from a regular requirements.txt without bothering to specify an output name.
Now that I read the source issue for this, and see how pip-tools was attempted to be applied, I am not necessarily opposed to requiring an output file be specified explicitly in this case, to protect users. 

EDIT: Not necessarily for it either. 🤷🏼‍♂️ 

Resolving patch:
diff --git a/piptools/scripts/compile.py b/piptools/scripts/compile.py
--- a/piptools/scripts/compile.py
+++ b/piptools/scripts/compile.py
@@ -385,6 +385,11 @@ def cli(
         if isinstance(output_file, LazyFile):  # pragma: no cover
             ctx.call_on_close(safecall(output_file.close_intelligently))
 
+    if output_file.name != "-" and output_file.name in src_files:
+        raise click.BadArgumentUsage(
+            f"input and output filenames must not be matched: {output_file.name}"
+        )
+
     if resolver_name == "legacy":
         log.warning(
             "WARNING: the legacy dependency resolver is deprecated and will be removed"


Test patch:
diff --git a/tests/test_cli_compile.py b/tests/test_cli_compile.py
--- a/tests/test_cli_compile.py
+++ b/tests/test_cli_compile.py
@@ -2776,3 +2776,31 @@ def test_print_deprecation_warning_if_using_legacy_resolver(runner, current_reso
         assert expected_warning in out.stderr
     else:
         assert expected_warning not in out.stderr
+
+
+@pytest.mark.parametrize(
+    "input_filenames",
+    (
+        pytest.param(("requirements.txt",), id="one file"),
+        pytest.param(("requirements.txt", "dev-requirements.in"), id="multiple files"),
+    ),
+)
+def test_raise_error_when_input_and_output_filenames_are_matched(
+    runner, tmp_path, input_filenames
+):
+    req_in_paths = []
+    for input_filename in input_filenames:
+        req_in = tmp_path / input_filename
+        req_in.touch()
+        req_in_paths.append(req_in.as_posix())
+
+    req_out = tmp_path / "requirements.txt"
+    req_out_path = req_out.as_posix()
+
+    out = runner.invoke(cli, req_in_paths + ["--output-file", req_out_path])
+    assert out.exit_code == 2
+
+    expected_error = (
+        f"Error: input and output filenames must not be matched: {req_out_path}"
+    )
+    assert expected_error in out.stderr.splitlines()