Repository: iterative/dvc

Issue:
`dvc exp run --dry --allow-missing` modifies dvc files
Reproduction steps:

```bash
cd "$(mktemp -d)"
git init && dvc init
echo -e "outs:\n- path: data" > data.dvc
dvc stage add -n test -d data ls
cat data.dvc
echo "data" >> .gitignore
git add .
git commit -m "init"
dvc exp run --dry --allow-missing -vv
cat data.dvc
```




Discussion:
 @skshetry could you clarify why this is p1 /which scenarios are impacted?
 
 It looks like this is coming from the internal `commit` operation done as part of `dvc exp run`:
 
 ```
cd "$(mktemp -d)"
git init && dvc init
echo -e "outs:\n- path: data" > data.dvc
dvc stage add -n test -d data ls
cat data.dvc
echo "data" >> .gitignore
git add .
git commit -m "init"
python -c "from dvc.repo import Repo; Repo().commit(allow_missing=True, force=True)"
cat data.dvc
```
It is breaking this scenario: [Verify Pipeline Status](https://dvc.org/doc/user-guide/pipelines/running-pipelines#verify-pipeline-status), specifically, this

> However, if data is missing, --dry will fail because DVC does not know whether that data simply needs to be pulled or is missing for some other reason. To check which stages to run and ignore any missing data, use [dvc exp run --dry --allow-missing](https://dvc.org/doc/command-reference/exp/run#--dry).


So, `--dry --allow-missing` is supposed to be used in scenario when you have not pulled, but it instead modifies the file with empty hash value.

Even without `--allow-missing`, `--dry` should not commit or change anything. 
Discord: https://discord.com/channels/485586884165107732/563406153334128681/1138124181268074516
> `--dry` should not commit or change anything.

Afaik, this was never the case since the introduction of `dvc exp run`, the logic for `commit_deps` happens when creating the experiment, before `reproduce`
`commit` could be smarter, and skip missing stuff. 
I see virtual directory being a problem in a similar manner. We really need better ways to handle partial checkout.
Should `exp run` even support `--dry`? If anything it seems like if `--dry` is used, it should just directly call `repo.reproduce(dry=True)` instead of trying to do any of the actual experiment setup
@pmrowla What if `--dry` would automatically imply `--temp`? Seems like that should do the trick. Not ideal, but maybe quick and simple enough for now? The ideal solution is something that could build the workspace virtually (related to run-cache discussions too), but that's way out of scope here for now.,
Need to confirm that:

1. Doesn't change params or anything else in workspace
2. Doesn't create git ref

Resolving patch:
diff --git a/dvc/commands/experiments/run.py b/dvc/commands/experiments/run.py
--- a/dvc/commands/experiments/run.py
+++ b/dvc/commands/experiments/run.py
@@ -26,7 +26,7 @@ def run(self):
 
 
 def add_parser(experiments_subparsers, parent_parser):
-    EXPERIMENTS_RUN_HELP = "Run or resume an experiment."
+    EXPERIMENTS_RUN_HELP = "Run an experiment."
     experiments_run_parser = experiments_subparsers.add_parser(
         "run",
         parents=[parent_parser],
diff --git a/dvc/repo/experiments/run.py b/dvc/repo/experiments/run.py
--- a/dvc/repo/experiments/run.py
+++ b/dvc/repo/experiments/run.py
@@ -30,6 +30,9 @@ def run(  # noqa: C901, PLR0912
     Returns a dict mapping new experiment SHAs to the results
     of `repro` for that experiment.
     """
+    if kwargs.get("dry"):
+        tmp_dir = True
+
     if run_all:
         return repo.experiments.reproduce_celery(jobs=jobs)
 


Test patch:
diff --git a/tests/func/experiments/test_experiments.py b/tests/func/experiments/test_experiments.py
--- a/tests/func/experiments/test_experiments.py
+++ b/tests/func/experiments/test_experiments.py
@@ -676,10 +676,12 @@ def test_experiment_unchanged(tmp_dir, scm, dvc, exp_stage):
     assert len(dvc.experiments.ls()[scm.get_rev()]) == 2
 
 
-def test_experiment_run_dry(tmp_dir, scm, dvc, exp_stage):
+def test_experiment_run_dry(tmp_dir, scm, dvc, exp_stage, mocker):
+    repro = mocker.spy(dvc.experiments, "reproduce_one")
     dvc.experiments.run(exp_stage.addressing, dry=True)
 
     assert len(dvc.experiments.ls()["master"]) == 0
+    assert repro.call_args.kwargs["tmp_dir"] is True
 
 
 def test_clean(tmp_dir, scm, dvc, mocker):