diff --git a/CHANGELOG.md b/CHANGELOG.md
index 75317f22362c35df9ebc2399599226f506b1e945..6eb0dabcad7a7510de90899d1a651ed70f791767 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Added ###
 
+- New function in apiutils that copies an Entity.
+
 ### Changed ###
 
 ### Deprecated ###
diff --git a/src/caosdb/apiutils.py b/src/caosdb/apiutils.py
index dc9209b58c8163da552f29e7a4435a0c640b1ecf..08f31daad56c0ab471322197cadc1a1378267f35 100644
--- a/src/caosdb/apiutils.py
+++ b/src/caosdb/apiutils.py
@@ -37,7 +37,9 @@ from caosdb.common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER,
                                     REFERENCE, TEXT, is_reference)
 from caosdb.common.models import (Container, Entity, File, Property, Query,
                                   Record, RecordType, execute_query,
-                                  get_config)
+                                  get_config, SPECIAL_ATTRIBUTES)
+
+import logging
 
 
 def new_record(record_type, name=None, description=None,
@@ -565,10 +567,6 @@ def getCommitIn(folder):
         return t.readline().strip()
 
 
-COMPARED = ["name", "role", "datatype", "description", "importance",
-            "id", "path", "checksum", "size"]
-
-
 def compare_entities(old_entity: Entity, new_entity: Entity):
     """
     Compare two entites.
@@ -592,7 +590,7 @@ def compare_entities(old_entity: Entity, new_entity: Entity):
     if old_entity is new_entity:
         return (olddiff, newdiff)
 
-    for attr in COMPARED:
+    for attr in SPECIAL_ATTRIBUTES:
         try:
             oldattr = old_entity.__getattribute__(attr)
             old_entity_attr_exists = True
@@ -681,10 +679,77 @@ def compare_entities(old_entity: Entity, new_entity: Entity):
     return (olddiff, newdiff)
 
 
+def merge_entities(entity_a: Entity, entity_b: Entity):
+    """
+    Merge entity_b into entity_a such that they have the same parents and properties.
+
+    datatype, unit, value, name and description will only be changed in entity_a if they
+    are None for entity_a and set for entity_b. If there is a corresponding value
+    for entity_a different from None a RuntimeError will be raised informing of an
+    unresolvable merge conflict.
+
+    The merge operation is done in place.
+
+    Returns entity_a.
+
+    WARNING: This function is currently experimental and insufficiently tested. Use with care.
+    """
+
+    logging.warning(
+        "This function is currently experimental and insufficiently tested. Use with care.")
+
+    # Compare both entities:
+    diff_r1, diff_r2 = compare_entities(entity_a, entity_b)
+
+    # Go through the comparison and try to apply changes to entity_a:
+    for key in diff_r2["parents"]:
+        entity_a.add_parent(entity_b.get_parent(key))
+
+    for key in diff_r2["properties"]:
+        if key in diff_r1["properties"]:
+            if ("importance" in diff_r1["properties"][key] and
+                    "importance" in diff_r2["properties"][key]):
+                if (diff_r1["properties"][key]["importance"] !=
+                        diff_r2["properties"][key]["importance"]):
+                    raise NotImplementedError()
+            elif ("importance" in diff_r1["properties"][key] or
+                  "importance" in diff_r2["properties"][key]):
+                raise NotImplementedError()
+
+            for attribute in ("datatype", "unit", "value"):
+                if diff_r1["properties"][key][attribute] is None:
+                    setattr(entity_a.get_property(key), attribute,
+                            diff_r2["properties"][key][attribute])
+                else:
+                    raise RuntimeError("Merge conflict.")
+        else:
+            # TODO: This is a temporary FIX for
+            #       https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/issues/105
+            entity_a.add_property(id=entity_b.get_property(key).id,
+                                  name=entity_b.get_property(key).name,
+                                  datatype=entity_b.get_property(key).datatype,
+                                  value=entity_b.get_property(key).value,
+                                  unit=entity_b.get_property(key).unit,
+                                  importance=entity_b.get_importance(key))
+            # entity_a.add_property(
+            #     entity_b.get_property(key),
+            #     importance=entity_b.get_importance(key))
+
+    for special_attribute in ("name", "description"):
+        sa_a = getattr(entity_a, special_attribute)
+        sa_b = getattr(entity_b, special_attribute)
+        if sa_a != sa_b:
+            if sa_a is None:
+                setattr(entity_a, special_attribute, sa_b)
+            else:
+                raise RuntimeError("Merge conflict.")
+    return entity_a
+
+
 def describe_diff(olddiff, newdiff, name=None, as_update=True):
     description = ""
 
-    for attr in list(set(list(olddiff.keys())+list(newdiff.keys()))):
+    for attr in list(set(list(olddiff.keys()) + list(newdiff.keys()))):
         if attr == "parents" or attr == "properties":
             continue
         description += "{} differs:\n".format(attr)
diff --git a/src/caosdb/common/models.py b/src/caosdb/common/models.py
index 181750aae6fd3e1aeab2c61b59f53d8b8111d5bd..6475bc99ec825e102d5eac1b38d506247c11ebcb 100644
--- a/src/caosdb/common/models.py
+++ b/src/caosdb/common/models.py
@@ -79,6 +79,10 @@ ALL = "ALL"
 NONE = "NONE"
 
 
+SPECIAL_ATTRIBUTES = ["name", "role", "datatype", "description",
+                      "id", "path", "checksum", "size"]
+
+
 class Entity(object):
 
     """Entity is a generic CaosDB object.
@@ -121,6 +125,48 @@ class Entity(object):
         self.id = id
         self.state = None
 
+
+    def copy(self):
+        """
+        Return a copy of entity.
+
+        If deep == True return a deep copy, recursively copying all sub entities.
+
+        Standard properties are copied using add_property.
+        Special attributes, as defined by the global variable SPECIAL_ATTRIBUTES and additionaly
+        the "value" are copied using setattr.
+        """
+        if self.role == "File":
+            new = File()
+        elif self.role == "Property":
+            new = Property()
+        elif self.role == "RecordType":
+            new = RecordType()
+        elif self.role == "Record":
+            new = Record()
+        elif self.role == "Entity":
+            new = Entity()
+        else:
+            raise RuntimeError("Unkonwn role.")
+
+        # Copy special attributes:
+        # TODO: this might rise an exception when copying
+        #       special file attributes like checksum and size.
+        for attribute in SPECIAL_ATTRIBUTES + ["value"]:
+            val = getattr(self, attribute)
+            if val is not None:
+                setattr(new, attribute, val)
+
+        # Copy parents:
+        for p in self.parents:
+            new.add_parent(p)
+
+        # Copy properties:
+        for p in self.properties:
+            new.add_property(p, importance=self.get_importance(p))
+
+        return new
+
     @property
     def version(self):
         if self._version is not None or self._wrapped_entity is None:
diff --git a/unittests/test_apiutils.py b/unittests/test_apiutils.py
index 0294646f6c526230a8e9fb722d56aa23a8f9285c..13603f4caae8b1212ffa041f37d9be4b462223ff 100644
--- a/unittests/test_apiutils.py
+++ b/unittests/test_apiutils.py
@@ -31,10 +31,14 @@ import tempfile
 import caosdb as db
 import caosdb.apiutils
 from caosdb.apiutils import (apply_to_ids, compare_entities, create_id_query,
-                             resolve_reference)
+                             resolve_reference, merge_entities)
+
+from caosdb.common.models import SPECIAL_ATTRIBUTES
 
 from .test_property import testrecord
 
+import pytest
+
 
 def test_convert_object():
     r2 = db.apiutils.convert_to_python_object(testrecord)
@@ -230,3 +234,92 @@ def test_compare_special_properties():
             assert diff_r2[key] == 2
         assert len(diff_r1["properties"]) == 0
         assert len(diff_r2["properties"]) == 0
+
+
+def test_copy_entities():
+    r = db.Record(name="A")
+    r.add_parent(name="B")
+    r.add_property(name="C", value=4, importance="OBLIGATORY")
+    r.add_property(name="D", value=[3, 4, 7], importance="OBLIGATORY")
+    r.description = "A fancy test record"
+
+    c = r.copy()
+
+    assert c is not r
+    assert c.name == "A"
+    assert c.role == r.role
+    assert c.parents[0].name == "B"
+    # parent and property objects are not shared among copy and original:
+    assert c.parents[0] is not r.parents[0]
+
+    for i in [0, 1]:
+        assert c.properties[i] is not r.properties[i]
+        for special in SPECIAL_ATTRIBUTES:
+            assert getattr(c.properties[i], special) == getattr(r.properties[i], special)
+        assert c.get_importance(c.properties[i]) == r.get_importance(r.properties[i])
+
+
+def test_merge_entities():
+    r = db.Record(name="A")
+    r.add_parent(name="B")
+    r.add_property(name="C", value=4, importance="OBLIGATORY")
+    r.add_property(name="D", value=[3, 4, 7], importance="OBLIGATORY")
+    r.description = "A fancy test record"
+
+    r2 = db.Record()
+    r2.add_property(name="F", value="text")
+    merge_entities(r2, r)
+    assert r2.get_parents()[0].name == "B"
+    assert r2.get_property("C").name == "C"
+    assert r2.get_property("C").value == 4
+    assert r2.get_property("D").name == "D"
+    assert r2.get_property("D").value == [3, 4, 7]
+
+    assert r2.get_property("F").name == "F"
+    assert r2.get_property("F").value == "text"
+
+
+def test_merge_bug_109():
+    rt = db.RecordType(name="TestBug")
+    p = db.Property(name="test_bug_property", datatype=db.LIST(db.INTEGER))
+
+    r_b = db.Record(name="TestRecord")
+    r_b.add_parent(rt)
+    r_b.add_property(p, value=[18, 19])
+
+    r_a = db.Record(name="TestRecord")
+    r_a.add_parent(rt)
+
+    merge_entities(r_a, r_b)
+
+    assert r_b.get_property("test_bug_property").value == [18, 19]
+    assert r_a.get_property("test_bug_property").value == [18, 19]
+
+    assert "<Value>18</Value>\n    <Value>19</Value>" in str(r_b)
+    assert "<Value>18</Value>\n    <Value>19</Value>\n    <Value>18</Value>\n    <Value>19</Value>" not in str(r_b)
+
+    assert "<Value>18</Value>\n    <Value>19</Value>" in str(r_a)
+    assert "<Value>18</Value>\n    <Value>19</Value>\n    <Value>18</Value>\n    <Value>19</Value>" not in str(r_a)
+
+
+@pytest.mark.xfail
+def test_bug_109():
+    rt = db.RecordType(name="TestBug")
+    p = db.Property(name="test_bug_property", datatype=db.LIST(db.INTEGER))
+
+    r_b = db.Record(name="TestRecord")
+    r_b.add_parent(rt)
+    r_b.add_property(p, value=[18, 19])
+
+    r_a = db.Record(name="TestRecord")
+    r_a.add_parent(rt)
+    r_a.add_property(r_b.get_property("test_bug_property"))
+
+    assert r_b.get_property("test_bug_property").value == [18, 19]
+    assert r_a.get_property("test_bug_property").value == [18, 19]
+
+    assert "<Value>18</Value>\n    <Value>19</Value>" in str(r_b)
+    assert "<Value>18</Value>\n    <Value>19</Value>\n    <Value>18</Value>\n    <Value>19</Value>" not in str(r_b)
+
+    assert "<Value>18</Value>\n    <Value>19</Value>" in str(r_a)
+    assert "<Value>18</Value>\n    <Value>19</Value>\n    <Value>18</Value>\n    <Value>19</Value>" not in str(r_a)