Repository: iterative/dvc

Issue:
`exp apply`: unable to apply an experiment when there are unsaved changes in the workspace
# Bug Report

<!--
## Issue name

Issue names must follow the pattern `command: description` where the command is the dvc command that you are trying to run. The description should describe the consequence of the bug.

Example: `repro: doesn't detect input changes`
-->

## Description

<!--
A clear and concise description of what the bug is.
-->

I am unable to apply an experiment when there are "unsaved changed" in the workspace.

### Reproduce

<!--
Step list of how to reproduce the bug
-->

Run an experiment which creates "unsaved changes" in the workspace. Try to apply a different experiment. No combination of flags will enable you to do this before running `dvc checkout exp --force`.

```
~/projects/vscode-dvc-demo main *1 !4 ❯ dvc exp apply 0419d97                                                                                                    
Building workspace index                                                                                                                                              |43.0 [00:00, 4.84kentry/s]
Comparing indexes                                                                                                                                                     |42.0 [00:00, 5.68kentry/s]
ERROR: Can't remove the following unsaved files without confirmation. Use `--force` to force.
~/projects/vscode-dvc-demo/hist.csv
~/projects/vscode-dvc-demo main *1 ❯ dvc exp apply 0419d97 --force
ERROR: unrecognized arguments: --force
usage: dvc experiments apply [-h] [-q | -v] [--no-force] experiment

Apply the changes from an experiment to your workspace.
Documentation: <https://man.dvc.org/exp/apply>

positional arguments:
  experiment     Experiment to be applied.

options:
  -h, --help     show this help message and exit
  -q, --quiet    Be quiet.
  -v, --verbose  Be verbose.
  --no-force     Fail if this command would overwrite conflicting changes.
~/projects/vscode-dvc-demo main *1 ❯ dvc exp apply 0419d97 --no-force
The --no-force option is deprecated and will be removed in a future DVC release. To revert the result of 'exp apply', run:

        git reset --hard
        git stash apply refs/exps/apply/stash

Building workspace index                                                                                                                                              |43.0 [00:00, 5.44kentry/s]
Comparing indexes                                                                                                                                                     |42.0 [00:00, 5.91kentry/s]
ERROR: Can't remove the following unsaved files without confirmation. Use `--force` to force.
~projects/vscode-dvc-demo/hist.csv
~/projects/vscode-dvc-demo main *1 ❯ dvc exp apply 0419d97 --no-force false
ERROR: unrecognized arguments: false
usage: dvc experiments apply [-h] [-q | -v] [--no-force] experiment

Apply the changes from an experiment to your workspace.
Documentation: <https://man.dvc.org/exp/apply>

positional arguments:
  experiment     Experiment to be applied.

options:
  -h, --help     show this help message and exit
  -q, --quiet    Be quiet.
  -v, --verbose  Be verbose.
  --no-force     Fail if this command would overwrite conflicting changes.
```

<!--
Example:

1. dvc init
2. Copy dataset.zip to the directory
3. dvc add dataset.zip
4. dvc run -d dataset.zip -o model ./train.sh
5. modify dataset.zip
6. dvc repro
-->

### Expected

<!--
A clear and concise description of what you expect to happen.
-->

`--force` should work as expected.

### Environment information

<!--
This is required to ensure that we can reproduce the bug.
-->

**Output of `dvc doctor`:**

```console
$ dvc doctor
DVC version: 3.23.0 (pip)
-------------------------
Platform: Python 3.10.10 on macOS-14.0-arm64-arm-64bit
Subprojects:
        dvc_data = 2.16.4
        dvc_objects = 1.0.1
        dvc_render = 0.6.1.dev22+g1c4428a
        dvc_task = 0.3.0
        scmrepo = 1.3.1
Supports:
        http (aiohttp = 3.8.4, aiohttp-retry = 2.8.3),
        https (aiohttp = 3.8.4, aiohttp-retry = 2.8.3),
        s3 (s3fs = 2023.9.1, boto3 = 1.28.17)
Config:
        Global: /Users/mattseddon/Library/Application Support/dvc
        System: /Library/Application Support/dvc
Cache types: reflink, hardlink, symlink
Cache directory: apfs on /dev/disk3s1s1
Caches: local
Remotes: s3
Workspace directory: apfs on /dev/disk3s1s1
Repo: dvc, git
Repo.site_cache_dir: /Library/Caches/dvc/repo/42de5bfa7eb2fd81cac7807679eceb92
```

**Additional Information (if any):**

Seems like this is happening because `--force` is not being passed to `dvc_checkout` further down the chain. I raised a PR (#9992) to close this issue please LMK if that was not the correct approach.


Discussion:

Resolving patch:
diff --git a/dvc/commands/experiments/apply.py b/dvc/commands/experiments/apply.py
--- a/dvc/commands/experiments/apply.py
+++ b/dvc/commands/experiments/apply.py
@@ -4,21 +4,13 @@
 from dvc.cli import completion
 from dvc.cli.command import CmdBase
 from dvc.cli.utils import append_doc_link
-from dvc.ui import ui
 
 logger = logging.getLogger(__name__)
 
 
 class CmdExperimentsApply(CmdBase):
     def run(self):
-        if not self.args.force:
-            ui.write(
-                "The --no-force option is deprecated and will be removed in a future"
-                " DVC release. To revert the result of 'exp apply', run:\n"
-                "\n\tgit reset --hard\n"
-                "\tgit stash apply refs/exps/apply/stash\n"
-            )
-        self.repo.experiments.apply(self.args.experiment)
+        self.repo.experiments.apply(self.args.experiment, force=self.args.force)
 
         return 0
 
@@ -33,10 +25,11 @@ def add_parser(experiments_subparsers, parent_parser):
         formatter_class=argparse.RawDescriptionHelpFormatter,
     )
     experiments_apply_parser.add_argument(
-        "--no-force",
-        action="store_false",
-        dest="force",
-        help="Fail if this command would overwrite conflicting changes.",
+        "-f",
+        "--force",
+        action="store_true",
+        default=False,
+        help="Do not prompt when removing working directory files.",
     )
     experiments_apply_parser.add_argument(
         "experiment", help="Experiment to be applied."


Test patch:
diff --git a/tests/unit/command/test_experiments.py b/tests/unit/command/test_experiments.py
--- a/tests/unit/command/test_experiments.py
+++ b/tests/unit/command/test_experiments.py
@@ -27,7 +27,19 @@ def test_experiments_apply(dvc, scm, mocker):
 
     assert cmd.run() == 0
 
-    m.assert_called_once_with(cmd.repo, "exp_rev")
+    m.assert_called_once_with(cmd.repo, "exp_rev", force=False)
+
+
+def test_experiments_apply_force(dvc, scm, mocker):
+    cli_args = parse_args(["experiments", "apply", "exp_rev", "--force"])
+    assert cli_args.func == CmdExperimentsApply
+
+    cmd = cli_args.func(cli_args)
+    m = mocker.patch("dvc.repo.experiments.apply.apply", return_value={})
+
+    assert cmd.run() == 0
+
+    m.assert_called_once_with(cmd.repo, "exp_rev", force=True)
 
 
 def test_experiments_diff(dvc, scm, mocker):