Repository: iterative/dvc

Issue:
cloud versioning: Using `dvc commit` to track updates doesn't work as expected 
## Setup

I am ttracking a dictionary with cloud versioning enabled:

```bash
$ dvc remote modify myremote version_aware true
$ dvc remote modify myremote worktree true
$ mkdir data
$ echo foo > data/foo
$ echo bar > data/bar
$ dvc add data
$ dvc push
$ cat data.dvc
outs:                                                                                                             
- md5: 168fd6761b9c3f2c081085b51fd7bf15.dir
  size: 8
  nfiles: 2
  path: data
  files:
  - size: 4
    etag: 08deb086f8d8ccfc021001
    md5: c157a79031e1c40f85931829bc5fc552
    relpath: bar
  - size: 4
    etag: 08a48b8df8d8ccfc021001
    md5: d3b07384d113edec49eaa6238ad5ff00
    relpath: foo
```

And performing an update:

## Working as expected with `dvc add`

If I run `dvc add data`, it works as expected: The .dvc file preserves "cloud-versioning" format and `dvc push` only uploads and creates a new version for the modified file:

```console
$ echo bar2 > data
$ dvc add data
$ cat data.dvc
outs:
- md5: 43f06a6517d71d0267bed23cfb1a53fb.dir
  size: 9
  nfiles: 2
  path: data
  files:
  - size: 5
    md5: 042967ff088f7f436015fbebdcad1f28
    relpath: bar
  - size: 4
    etag: 08a48b8df8d8ccfc021001
    md5: d3b07384d113edec49eaa6238ad5ff00
    relpath: foo
$ dvc push
1 file pushed                                                                                                     
$ cat data.dvc
outs:
- md5: 43f06a6517d71d0267bed23cfb1a53fb.dir
  size: 9
  nfiles: 2
  path: data
  files:
  - size: 5
    md5: 042967ff088f7f436015fbebdcad1f28
    relpath: bar
    etag: 08eca3ebfddaccfc021001
  - size: 4
    etag: 08f0f286fcdaccfc021001
    md5: d3b07384d113edec49eaa6238ad5ff00
    relpath: foo
```

## Unexpected behavior with `dvc commit`

However, If I instead run `dvc commit` to track the updates, first the `.dvc` file loses the "cloud versioning" format and then all files are uploaded and redundant versions are created on the remote:

```console
$ bar3 > data/bar
$ dvc commit
outputs ['data'] of stage: 'data.dvc' changed. Are you sure you want to commit it? [y/n] y 
$ cat data.dvc 
outs:                                                                                                             
- md5: a41477be72fa9617d877592a0b22542a.dir                                                                       
  size: 9
  nfiles: 2
  path: data
$ dvc push
2 files pushed 
$ cat data.dvc
outs:                                                                                                             
- md5: a41477be72fa9617d877592a0b22542a.dir
  size: 9
  nfiles: 2
  path: data
  files:
  - size: 5
    etag: 0898e6edffdaccfc021001
    md5: f19345bd4a711b82a13e55c61cad54e3
    relpath: bar
  - size: 4
    etag: 08bfe9e5ffdaccfc021001
    md5: d3b07384d113edec49eaa6238ad5ff00
    relpath: foo
```



Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) Report
Base: **93.40**% // Head: **93.37**% // Decreases project coverage by **`-0.03%`** :warning:
> Coverage data is based on head [(`ad83346`)](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) compared to base [(`ea2e5dc`)](https://codecov.io/gh/iterative/dvc/commit/ea2e5dc3e599eebbeddc3b2e8d5aa82cdbdd5cb4?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative).
> Patch coverage: 95.00% of modified lines in pull request are covered.

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


```diff
@@            Coverage Diff             @@
##             main    #8873      +/-   ##
==========================================
- Coverage   93.40%   93.37%   -0.03%     
==========================================
  Files         453      453              
  Lines       36146    36154       +8     
  Branches     5239     5238       -1     
==========================================
- Hits        33763    33760       -3     
- Misses       1849     1856       +7     
- Partials      534      538       +4     
```


| [Impacted Files](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative) | Coverage Δ | |
|---|---|---|
| [dvc/repo/worktree.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vd29ya3RyZWUucHk=) | `10.25% <0.00%> (ø)` | |
| [dvc/repo/add.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vYWRkLnB5) | `100.00% <100.00%> (ø)` | |
| [dvc/stage/\_\_init\_\_.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3N0YWdlL19faW5pdF9fLnB5) | `94.69% <100.00%> (-0.02%)` | :arrow_down: |
| [tests/func/test\_commit.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-dGVzdHMvZnVuYy90ZXN0X2NvbW1pdC5weQ==) | `100.00% <100.00%> (ø)` | |
| [tests/unit/test\_analytics.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-dGVzdHMvdW5pdC90ZXN0X2FuYWx5dGljcy5weQ==) | `92.85% <0.00%> (-4.29%)` | :arrow_down: |
| [dvc/analytics.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL2FuYWx5dGljcy5weQ==) | `88.05% <0.00%> (-2.99%)` | :arrow_down: |
| [dvc/testing/fixtures.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3Rlc3RpbmcvZml4dHVyZXMucHk=) | `94.21% <0.00%> (-1.66%)` | :arrow_down: |
| [dvc/repo/experiments/queue/celery.py](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative#diff-ZHZjL3JlcG8vZXhwZXJpbWVudHMvcXVldWUvY2VsZXJ5LnB5) | `84.07% <0.00%> (-1.49%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/iterative/dvc/pull/8873?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=iterative).

pmrowla: linking my comment from the old PR for future reference

> We can probably just remove the flag then. This issue is more a side effect of our current `add/commit/move` behavior, where we always create a new stage from scratch, and then load the previous version if it exists later and try to merge the two, (as opposed to loading the stage if it exists first and then modifying it)

_Originally posted by @pmrowla in https://github.com/iterative/dvc/issues/8829#issuecomment-1401972162_
            

Patch:
diff --git a/dvc/repo/add.py b/dvc/repo/add.py
index 761aa624b..d662c4197 100644
--- a/dvc/repo/add.py
+++ b/dvc/repo/add.py
@@ -194,7 +194,7 @@ def add(  # noqa: C901
                 stage.transfer(source, to_remote=to_remote, odb=odb, **kwargs)
             else:
                 try:
-                    stage.save(merge_versioned=True)
+                    stage.save()
                     if not no_commit:
                         stage.commit()
                 except CacheLinkError:
diff --git a/dvc/repo/worktree.py b/dvc/repo/worktree.py
index 933604407..46470d87d 100644
--- a/dvc/repo/worktree.py
+++ b/dvc/repo/worktree.py
@@ -228,7 +228,7 @@ def update_worktree_stages(
                 )
                 continue
             _fetch_out_changes(out, local_index, remote_index, remote)
-        stage.save(merge_versioned=True)
+        stage.save()
         for out in stage.outs:
             _update_out_meta(out, remote_index)
         stage.dump(with_files=True, update_pipeline=False)
diff --git a/dvc/stage/__init__.py b/dvc/stage/__init__.py
index e3eb38fce..6e3e48391 100644
--- a/dvc/stage/__init__.py
+++ b/dvc/stage/__init__.py
@@ -486,17 +486,11 @@ class Stage(params.StageParams):
         logger.debug("Computed %s md5: '%s'", self, m)
         return m
 
-    def save(
-        self,
-        allow_missing: bool = False,
-        merge_versioned: bool = False,
-        run_cache: bool = True,
-    ):
+    def save(self, allow_missing: bool = False, run_cache: bool = True):
         self.save_deps(allow_missing=allow_missing)
 
-        self.save_outs(
-            allow_missing=allow_missing, merge_versioned=merge_versioned
-        )
+        self.save_outs(allow_missing=allow_missing)
+
         self.md5 = self.compute_md5()
 
         if run_cache:
@@ -512,29 +506,26 @@ class Stage(params.StageParams):
                 if not allow_missing:
                     raise
 
-    def save_outs(
-        self, allow_missing: bool = False, merge_versioned: bool = False
-    ):
+    def save_outs(self, allow_missing: bool = False):
         from dvc.output import OutputDoesNotExistError
 
         from .exceptions import StageFileDoesNotExistError, StageNotFound
 
-        if merge_versioned:
-            try:
-                old = self.reload()
-                old_outs = {out.def_path: out for out in old.outs}
-                merge_versioned = any(
-                    (
-                        out.files is not None
-                        or (
-                            out.meta is not None
-                            and out.meta.version_id is not None
-                        )
+        try:
+            old = self.reload()
+            old_outs = {out.def_path: out for out in old.outs}
+            merge_versioned = any(
+                (
+                    out.files is not None
+                    or (
+                        out.meta is not None
+                        and out.meta.version_id is not None
                     )
-                    for out in old_outs.values()
                 )
-            except (StageFileDoesNotExistError, StageNotFound):
-                merge_versioned = False
+                for out in old_outs.values()
+            )
+        except (StageFileDoesNotExistError, StageNotFound):
+            merge_versioned = False
 
         for out in self.outs:
             try:


Test patch:
diff --git a/tests/func/test_commit.py b/tests/func/test_commit.py
index d58bce908..89355e53f 100644
--- a/tests/func/test_commit.py
+++ b/tests/func/test_commit.py
@@ -245,3 +245,61 @@ def test_imported_entries_unchanged(tmp_dir, dvc, erepo_dir):
     stage = dvc.imp(os.fspath(erepo_dir), "file")
 
     assert stage.changed_entries() == ([], [], None)
+
+
+def test_commit_updates_to_cloud_versioning_dir(tmp_dir, dvc):
+    data_dvc = tmp_dir / "data.dvc"
+    data_dvc.dump(
+        {
+            "outs": [
+                {
+                    "path": "data",
+                    "files": [
+                        {
+                            "size": 3,
+                            "version_id": "WYRG4BglP7pD.gEoJP6a4AqOhl.FRA.h",
+                            "etag": "acbd18db4cc2f85cedef654fccc4a4d8",
+                            "md5": "acbd18db4cc2f85cedef654fccc4a4d8",
+                            "relpath": "bar",
+                        },
+                        {
+                            "size": 3,
+                            "version_id": "0vL53tFVY5vVAoJ4HG2jCS1mEcohDPE0",
+                            "etag": "acbd18db4cc2f85cedef654fccc4a4d8",
+                            "md5": "acbd18db4cc2f85cedef654fccc4a4d8",
+                            "relpath": "foo",
+                        },
+                    ],
+                }
+            ]
+        }
+    )
+
+    data = tmp_dir / "data"
+    data.mkdir()
+    (data / "foo").write_text("foo")
+    (data / "bar").write_text("bar2")
+
+    dvc.commit("data", force=True)
+
+    assert (tmp_dir / "data.dvc").parse() == {
+        "outs": [
+            {
+                "path": "data",
+                "files": [
+                    {
+                        "size": 4,
+                        "md5": "224e2539f52203eb33728acd228b4432",
+                        "relpath": "bar",
+                    },
+                    {
+                        "size": 3,
+                        "version_id": "0vL53tFVY5vVAoJ4HG2jCS1mEcohDPE0",
+                        "etag": "acbd18db4cc2f85cedef654fccc4a4d8",
+                        "md5": "acbd18db4cc2f85cedef654fccc4a4d8",
+                        "relpath": "foo",
+                    },
+                ],
+            }
+        ]
+    }