diff --git a/dvc/commands/git_hook.py b/dvc/commands/git_hook.py index 24baa36ef..948a217b7 100644 --- a/dvc/commands/git_hook.py +++ b/dvc/commands/git_hook.py @@ -74,7 +74,7 @@ class CmdMergeDriver(CmdHookBase): our = Dvcfile(dvc, self.args.our, verify=False) their = Dvcfile(dvc, self.args.their, verify=False) - our.merge(ancestor, their) + our.merge(ancestor, their, allowed=["add", "remove", "change"]) return 0 finally: diff --git a/dvc/dvcfile.py b/dvc/dvcfile.py index 9d2098245..7cd136f3e 100644 --- a/dvc/dvcfile.py +++ b/dvc/dvcfile.py @@ -172,7 +172,7 @@ class FileMixin: def dump(self, stage, **kwargs): raise NotImplementedError - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): raise NotImplementedError @@ -204,12 +204,12 @@ class SingleStageFile(FileMixin): def remove_stage(self, stage): # pylint: disable=unused-argument self.remove() - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): assert isinstance(ancestor, SingleStageFile) assert isinstance(other, SingleStageFile) stage = self.stage - stage.merge(ancestor.stage, other.stage) + stage.merge(ancestor.stage, other.stage, allowed=allowed) self.dump(stage) @@ -310,7 +310,7 @@ class PipelineFile(FileMixin): else: super().remove() - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): raise NotImplementedError @@ -411,7 +411,7 @@ class Lockfile(FileMixin): else: self.remove() - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): raise NotImplementedError diff --git a/dvc/output.py b/dvc/output.py index 84cfe674e..e4277a386 100644 --- a/dvc/output.py +++ b/dvc/output.py @@ -1127,7 +1127,7 @@ class Output: "unable to auto-merge outputs that are not directories" ) - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): from dvc_data.hashfile.tree import MergeError as TreeMergeError from dvc_data.hashfile.tree import du, merge @@ -1144,7 +1144,11 @@ class Output: try: merged = merge( - self.odb, ancestor_info, self.hash_info, other.hash_info + self.odb, + ancestor_info, + self.hash_info, + other.hash_info, + allowed=allowed, ) except TreeMergeError as exc: raise MergeError(str(exc)) from exc diff --git a/dvc/stage/__init__.py b/dvc/stage/__init__.py index c749f0340..dedf44274 100644 --- a/dvc/stage/__init__.py +++ b/dvc/stage/__init__.py @@ -708,7 +708,7 @@ class Stage(params.StageParams): "unable to auto-merge DVC files with deleted outputs" ) - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): assert other if not other.outs: @@ -728,7 +728,7 @@ class Stage(params.StageParams): self._check_can_merge(self, ancestor_out) self._check_can_merge(other, ancestor_out) - self.outs[0].merge(ancestor_out, other.outs[0]) + self.outs[0].merge(ancestor_out, other.outs[0], allowed=allowed) def dump(self, **kwargs): self.dvcfile.dump(self, **kwargs) @@ -770,5 +770,5 @@ class PipelineStage(Stage): def _changed_stage_entry(self): return f"'cmd' of {self} has changed." - def merge(self, ancestor, other): + def merge(self, ancestor, other, allowed=None): raise NotImplementedError diff --git a/tests/func/test_merge_driver.py b/tests/func/test_merge_driver.py index 113984f92..7b673328b 100644 --- a/tests/func/test_merge_driver.py +++ b/tests/func/test_merge_driver.py @@ -39,6 +39,22 @@ def _gen(tmp_dir, struct, name): (None, {"foo": "foo"}, {"bar": "bar"}, {"foo": "foo", "bar": "bar"}), (None, None, {"bar": "bar"}, {"bar": "bar"}), (None, {"foo": "foo"}, None, {"foo": "foo"}), + ( + {"foo": "foo"}, + {"foo": "bar"}, + {"foo": "foo", "baz": "baz"}, + {"foo": "bar", "baz": "baz"}, + ), + ({"foo": "foo"}, {}, {"foo": "foo", "bar": "bar"}, {"bar": "bar"}), + ( + {"common": "common", "subdir": {"foo": "foo", "bar": "bar"}}, + {"common": "common", "subdir": {"foo": "foo", "bar": "baz"}}, + {"common": "common", "subdir": {"bar": "bar", "bizz": "bizz"}}, + { + "common": "common", + "subdir": {"bar": "baz", "bizz": "bizz"}, + }, + ), ], ) def test_merge(tmp_dir, dvc, ancestor, our, their, merged): @@ -74,18 +90,15 @@ def test_merge(tmp_dir, dvc, ancestor, our, their, merged): {"foo": "foo"}, {"foo": "bar"}, {"foo": "baz"}, - ( - "unable to auto-merge directories with " - "diff that contains 'change'ed files" - ), + ("unable to auto-merge the following paths:\nfoo"), ), ( {"common": "common", "foo": "foo"}, {"common": "common", "bar": "bar"}, {"baz": "baz"}, ( - "unable to auto-merge directories with " - "diff that contains 'remove'ed files" + "unable to auto-merge the following paths:\n" + "both deleted: ('foo',)" ), ), ],