diff BUGFIX single subtree for nested changes in move
diff --git a/src/diff.c b/src/diff.c
index fd5a176..556ac58 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -161,6 +161,127 @@
     return rc;
 }
 
+/**
+ * @brief Find metadata/an attribute of a node.
+ *
+ * @param[in] node Node to search.
+ * @param[in] name Metadata/attribute name.
+ * @param[out] meta Metadata found, NULL if not found.
+ * @param[out] attr Attribute found, NULL if not found.
+ */
+static void
+lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
+{
+    struct lyd_meta *m;
+    struct lyd_attr *a;
+
+    if (meta) {
+        *meta = NULL;
+    }
+    if (attr) {
+        *attr = NULL;
+    }
+
+    if (node->schema) {
+        assert(meta);
+
+        LY_LIST_FOR(node->meta, m) {
+            if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
+                *meta = m;
+                break;
+            }
+        }
+    } else {
+        assert(attr);
+
+        LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
+            /* name */
+            if (strcmp(a->name.name, name)) {
+                continue;
+            }
+
+            /* module */
+            switch (a->format) {
+            case LY_VALUE_JSON:
+                if (strcmp(a->name.module_name, "yang")) {
+                    continue;
+                }
+                break;
+            case LY_VALUE_XML:
+                if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
+                    continue;
+                }
+                break;
+            default:
+                LOGINT(LYD_CTX(node));
+                return;
+            }
+
+            *attr = a;
+            break;
+        }
+    }
+}
+
+/**
+ * @brief Learn operation of a diff node.
+ *
+ * @param[in] diff_node Diff node.
+ * @param[out] op Operation.
+ * @return LY_ERR value.
+ */
+static LY_ERR
+lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
+{
+    struct lyd_meta *meta = NULL;
+    struct lyd_attr *attr = NULL;
+    const struct lyd_node *diff_parent;
+    const char *str;
+    char *path;
+
+    for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
+        lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
+        if (!meta && !attr) {
+            continue;
+        }
+
+        str = meta ? lyd_get_meta_value(meta) : attr->value;
+        if ((str[0] == 'r') && (diff_parent != diff_node)) {
+            /* we do not care about this operation if it's in our parent */
+            continue;
+        }
+        *op = lyd_diff_str2op(str);
+        return LY_SUCCESS;
+    }
+
+    /* operation not found */
+    path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
+    LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
+    free(path);
+    return LY_EINT;
+}
+
+/**
+ * @brief Remove metadata/an attribute from a node.
+ *
+ * @param[in] node Node to update.
+ * @param[in] name Metadata/attribute name.
+ */
+static void
+lyd_diff_del_meta(struct lyd_node *node, const char *name)
+{
+    struct lyd_meta *meta;
+    struct lyd_attr *attr;
+
+    lyd_diff_find_meta(node, name, &meta, &attr);
+
+    if (meta) {
+        lyd_free_meta_single(meta);
+    } else if (attr) {
+        lyd_free_attr_single(LYD_CTX(node), attr);
+    }
+}
+
 LY_ERR
 lyd_diff_add(const struct lyd_node *node, enum lyd_diff_op op, const char *orig_default, const char *orig_value,
         const char *key, const char *value, const char *position, const char *orig_key, const char *orig_position,
@@ -168,6 +289,7 @@
 {
     struct lyd_node *dup, *siblings, *match = NULL, *diff_parent = NULL, *elem;
     const struct lyd_node *parent = NULL;
+    enum lyd_diff_op cur_op;
 
     assert(diff);
 
@@ -189,14 +311,16 @@
 
     /* find the first existing parent */
     siblings = *diff;
-    while (1) {
+    do {
         /* find next node parent */
         parent = node;
         while (parent->parent && (!diff_parent || (parent->parent->schema != diff_parent->schema))) {
             parent = lyd_parent(parent);
         }
-        if (parent == node) {
-            /* no more parents to find */
+
+        if (lysc_is_dup_inst_list(parent->schema)) {
+            /* assume it never exists, we are not able to distinguish whether it does or not */
+            match = NULL;
             break;
         }
 
@@ -210,41 +334,51 @@
 
         /* move down in the diff */
         siblings = lyd_child_no_keys(match);
-    }
+    } while (parent != node);
 
-    /* duplicate the subtree (and connect to the diff if possible) */
-    if (diff_parent) {
-        LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
-                LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
+    if (match && (parent == node)) {
+        /* special case when there is already an operation on our descendant */
+        assert(!lyd_diff_get_op(diff_parent, &cur_op) && (cur_op == LYD_DIFF_OP_NONE));
+        (void)cur_op;
+
+        /* will be replaced by the new operation */
+        lyd_diff_del_meta(diff_parent, "operation");
+        dup = diff_parent;
     } else {
-        LY_CHECK_RET(lyd_dup_single(node, NULL,
-                LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
-    }
-
-    /* find the first duplicated parent */
-    if (!diff_parent) {
-        diff_parent = lyd_parent(dup);
-        while (diff_parent && diff_parent->parent) {
-            diff_parent = lyd_parent(diff_parent);
+        /* duplicate the subtree (and connect to the diff if possible) */
+        if (diff_parent) {
+            LY_CHECK_RET(lyd_dup_single_to_ctx(node, LYD_CTX(diff_parent), (struct lyd_node_inner *)diff_parent,
+                    LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
+        } else {
+            LY_CHECK_RET(lyd_dup_single(node, NULL,
+                    LYD_DUP_RECURSIVE | LYD_DUP_NO_META | LYD_DUP_WITH_PARENTS | LYD_DUP_WITH_FLAGS, &dup));
         }
-    } else {
-        diff_parent = dup;
-        while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
-            diff_parent = lyd_parent(diff_parent);
+
+        /* find the first duplicated parent */
+        if (!diff_parent) {
+            diff_parent = lyd_parent(dup);
+            while (diff_parent && diff_parent->parent) {
+                diff_parent = lyd_parent(diff_parent);
+            }
+        } else {
+            diff_parent = dup;
+            while (diff_parent->parent && (diff_parent->parent->schema == parent->schema)) {
+                diff_parent = lyd_parent(diff_parent);
+            }
         }
-    }
 
-    /* no parent existed, must be manually connected */
-    if (!diff_parent) {
-        /* there actually was no parent to duplicate */
-        lyd_insert_sibling(*diff, dup, diff);
-    } else if (!diff_parent->parent) {
-        lyd_insert_sibling(*diff, diff_parent, diff);
-    }
+        /* no parent existed, must be manually connected */
+        if (!diff_parent) {
+            /* there actually was no parent to duplicate */
+            lyd_insert_sibling(*diff, dup, diff);
+        } else if (!diff_parent->parent) {
+            lyd_insert_sibling(*diff, diff_parent, diff);
+        }
 
-    /* add parent operation, if any */
-    if (diff_parent && (diff_parent != dup)) {
-        LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+        /* add parent operation, if any */
+        if (diff_parent && (diff_parent != dup)) {
+            LY_CHECK_RET(lyd_new_meta(NULL, diff_parent, NULL, "yang:operation", "none", 0, NULL));
+        }
     }
 
     /* add subtree operation */
@@ -929,98 +1063,6 @@
 }
 
 /**
- * @brief Find metadata/an attribute of a node.
- *
- * @param[in] node Node to search.
- * @param[in] name Metadata/attribute name.
- * @param[out] meta Metadata found, NULL if not found.
- * @param[out] attr Attribute found, NULL if not found.
- */
-static void
-lyd_diff_find_meta(const struct lyd_node *node, const char *name, struct lyd_meta **meta, struct lyd_attr **attr)
-{
-    struct lyd_meta *m;
-    struct lyd_attr *a;
-
-    *meta = NULL;
-    *attr = NULL;
-
-    if (node->schema) {
-        LY_LIST_FOR(node->meta, m) {
-            if (!strcmp(m->name, name) && !strcmp(m->annotation->module->name, "yang")) {
-                *meta = m;
-                break;
-            }
-        }
-    } else {
-        LY_LIST_FOR(((struct lyd_node_opaq *)node)->attr, a) {
-            /* name */
-            if (strcmp(a->name.name, name)) {
-                continue;
-            }
-
-            /* module */
-            switch (a->format) {
-            case LY_VALUE_JSON:
-                if (strcmp(a->name.module_name, "yang")) {
-                    continue;
-                }
-                break;
-            case LY_VALUE_XML:
-                if (strcmp(a->name.module_ns, "urn:ietf:params:xml:ns:yang:1")) {
-                    continue;
-                }
-                break;
-            default:
-                LOGINT(LYD_CTX(node));
-                return;
-            }
-
-            *attr = a;
-            break;
-        }
-    }
-}
-
-/**
- * @brief Learn operation of a diff node.
- *
- * @param[in] diff_node Diff node.
- * @param[out] op Operation.
- * @return LY_ERR value.
- */
-static LY_ERR
-lyd_diff_get_op(const struct lyd_node *diff_node, enum lyd_diff_op *op)
-{
-    struct lyd_meta *meta = NULL;
-    struct lyd_attr *attr = NULL;
-    const struct lyd_node *diff_parent;
-    const char *str;
-    char *path;
-
-    for (diff_parent = diff_node; diff_parent; diff_parent = lyd_parent(diff_parent)) {
-        lyd_diff_find_meta(diff_parent, "operation", &meta, &attr);
-        if (!meta && !attr) {
-            continue;
-        }
-
-        str = meta ? lyd_get_meta_value(meta) : attr->value;
-        if ((str[0] == 'r') && (diff_parent != diff_node)) {
-            /* we do not care about this operation if it's in our parent */
-            continue;
-        }
-        *op = lyd_diff_str2op(str);
-        return LY_SUCCESS;
-    }
-
-    /* operation not found */
-    path = lyd_path(diff_node, LYD_PATH_STD, NULL, 0);
-    LOGERR(LYD_CTX(diff_node), LY_EINVAL, "Node \"%s\" without an operation.", path);
-    free(path);
-    return LY_EINT;
-}
-
-/**
  * @brief Insert a diff node into a data tree.
  *
  * @param[in,out] first_node First sibling of the data tree.
@@ -1358,27 +1400,6 @@
 }
 
 /**
- * @brief Remove metadata/an attribute from a node.
- *
- * @param[in] node Node to update.
- * @param[in] name Metadata/attribute name.
- */
-static void
-lyd_diff_del_meta(struct lyd_node *node, const char *name)
-{
-    struct lyd_meta *meta;
-    struct lyd_attr *attr;
-
-    lyd_diff_find_meta(node, name, &meta, &attr);
-
-    if (meta) {
-        lyd_free_meta_single(meta);
-    } else if (attr) {
-        lyd_free_attr_single(LYD_CTX(node), attr);
-    }
-}
-
-/**
  * @brief Set a specific operation of a node. Delete the previous operation, if any.
  * Does not change the default flag.
  *
diff --git a/tests/utests/data/test_diff.c b/tests/utests/data/test_diff.c
index 818a782..748c064 100644
--- a/tests/utests/data/test_diff.c
+++ b/tests/utests/data/test_diff.c
@@ -1062,6 +1062,103 @@
 }
 
 static void
+test_userord_list3(void **state)
+{
+    (void) state;
+    const char *xml1 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "    <l2>1</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>4</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *xml2 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "    <l2>3</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "    <l2>1</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>44</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *xml3 =
+            "<df xmlns=\"urn:libyang:tests:defaults\">\n"
+            "  <ul>\n"
+            "    <l1>a</l1>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>c</l1>\n"
+            "    <l2>3</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>d</l1>\n"
+            "    <l2>44</l2>\n"
+            "  </ul>\n"
+            "  <ul>\n"
+            "    <l1>b</l1>\n"
+            "    <l2>2</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+
+    const char *out_diff_1 =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>c</l1>\n"
+            "    <l2 yang:operation=\"create\">3</l2>\n"
+            "  </ul>\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>d</l1>\n"
+            "    <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *out_diff_2 =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='c']\">\n"
+            "    <l1>a</l1>\n"
+            "    <l2 yang:operation=\"delete\">1</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+    const char *out_merge =
+            "<df xmlns=\"urn:libyang:tests:defaults\" xmlns:yang=\"urn:ietf:params:xml:ns:yang:1\" yang:operation=\"none\">\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>c</l1>\n"
+            "    <l2 yang:operation=\"create\">3</l2>\n"
+            "  </ul>\n"
+            "  <ul yang:operation=\"replace\" yang:key=\"[l1='a']\" yang:orig-key=\"[l1='b']\">\n"
+            "    <l1>d</l1>\n"
+            "    <l2 yang:operation=\"replace\" yang:orig-default=\"false\" yang:orig-value=\"4\">44</l2>\n"
+            "  </ul>\n"
+            "  <ul yang:key=\"\" yang:orig-key=\"[l1='c']\" yang:operation=\"replace\">\n"
+            "    <l1>a</l1>\n"
+            "    <l2 yang:operation=\"delete\">1</l2>\n"
+            "  </ul>\n"
+            "</df>\n";
+
+    TEST_DIFF_3(xml1, xml2, xml3, out_diff_1, out_diff_2, out_merge);
+}
+
+static void
 test_keyless_list(void **state)
 {
     (void) state;
@@ -1335,6 +1432,7 @@
         UTEST(test_userord_mix, setup),
         UTEST(test_userord_list, setup),
         UTEST(test_userord_list2, setup),
+        UTEST(test_userord_list3, setup),
         UTEST(test_keyless_list, setup),
         UTEST(test_state_llist, setup),
         UTEST(test_wd, setup),