Repository: openvinotoolkit/datumaro

Issue:
Patch command example error
for the example **Update the “source1” source in the current project with a dataset:**
```
datum patch -p proj/ --overwrite source1 path/to/dataset2:coco
```
There is two issue
1. for now the default branch name will be source-1 
2. patch command can not correctly locate source-1 folder in the project, result in raising FileExistsError
Reproduce:
```
datum create -o proj 
datum import -p proj -f coco -r annotations/instances_default.json  /home/dataset_merge_exp/a
datum patch -p proj --overwrite source-1 /home/dataset_merge_exp/b:coco
```

if we specify output dir, it will work correctly
```
datum patch -p proj -o proj/source-1 --overwrite source-1 /home/dataset_merge_exp/b:coco
```



Discussion:
Hi, thank you for reporting the issues.

> 1. for now the default branch name will be source-1

This is the designed behavior, because the word `patch` implies in-place operation by default. Using the `-o/--output-dir` is correct way to save results in another place. Please read the [command description](https://openvinotoolkit.github.io/datumaro/docs/user-manual/command-reference/patch/). 

If this logic does not work well, could you describe your use case?

> 2. patch command can not correctly locate source-1 folder in the project, result in raising `FileExistsError`

Thanks, it looks like a Python bug on Windows, actually.

Resolving patch:
diff --git a/datumaro/components/project.py b/datumaro/components/project.py
--- a/datumaro/components/project.py
+++ b/datumaro/components/project.py
@@ -52,12 +52,20 @@ def __init__(self, path: str, tree: 'Tree', source: str,
             readonly: bool = False):
         config = tree.sources[source]
 
+        rpath = path
         if config.path:
-            path = osp.join(path, config.path)
+            rpath = osp.join(path, config.path)
 
-        self.__dict__['_dataset'] = Dataset.import_from(path,
+        dataset = Dataset.import_from(rpath,
             env=tree.env, format=config.format, **config.options)
 
+        # Using rpath won't allow to save directly with .save() when a file
+        # path is specified. Dataset doesn't know the root location and if
+        # it exists at all, but in a project, we do.
+        dataset.bind(path, format=dataset.format, options=dataset.options)
+
+        self.__dict__['_dataset'] = dataset
+
         self.__dict__['_config'] = config
         self.__dict__['_readonly'] = readonly
         self.__dict__['name'] = source


Test patch:
diff --git a/tests/requirements.py b/tests/requirements.py
--- a/tests/requirements.py
+++ b/tests/requirements.py
@@ -45,6 +45,7 @@ class Requirements:
     DATUM_BUG_470 = "Cannot to import Cityscapes dataset without images"
     DATUM_BUG_560 = "Reading MOT dataset with seqinfo produces 0-based indexing in frames"
     DATUM_BUG_583 = "Empty lines in VOC subset lists are not ignored"
+    DATUM_BUG_602 = "Patch command example error"
 
 
 class SkipMessages:
diff --git a/tests/test_project.py b/tests/test_project.py
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -1112,6 +1112,28 @@ def test_cant_redownload_unhashed(self):
         with self.assertRaises(MissingSourceHashError):
             project.working_tree.make_dataset('source1.root')
 
+    @mark_requirement(Requirements.DATUM_BUG_602)
+    @scoped
+    def test_can_save_local_source_with_relpath(self):
+        test_dir = scope_add(TestDir())
+        source_url = osp.join(test_dir, 'source')
+        source_dataset = Dataset.from_iterable([
+            DatasetItem(0, subset='a', image=np.ones((2, 3, 3)),
+                annotations=[ Bbox(1, 2, 3, 4, label=0) ]),
+            DatasetItem(1, subset='b', image=np.zeros((10, 20, 3)),
+                annotations=[ Bbox(1, 2, 3, 4, label=1) ]),
+        ], categories=['a', 'b'])
+        source_dataset.save(source_url, save_images=True)
+
+        project = scope_add(Project.init(osp.join(test_dir, 'proj')))
+        project.import_source('s1', url=source_url, format=DEFAULT_FORMAT,
+            rpath=osp.join('annotations', 'b.json'))
+
+        read_dataset = project.working_tree.make_dataset('s1')
+        self.assertEqual(read_dataset.data_path, project.source_data_dir('s1'))
+
+        read_dataset.save()
+
 class BackwardCompatibilityTests_v0_1(TestCase):
     @mark_requirement(Requirements.DATUM_GENERAL_REQ)
     @scoped