Index: include/svn_fs.h =================================================================== --- include/svn_fs.h (revision 3866) +++ include/svn_fs.h (working copy) @@ -861,6 +861,10 @@ typedef struct svn_fs_dirent_t { /* The node revision ID it names. */ svn_fs_id_t *id; + /* The first revision that this node revision appeared + at this path. */ + svn_revnum_t created_rev; + } svn_fs_dirent_t; Index: libsvn_fs/tree.c =================================================================== --- libsvn_fs/tree.c (revision 3866) +++ libsvn_fs/tree.c (working copy) @@ -213,7 +213,6 @@ already_exists (svn_fs_root_t *root, con abort (); } - static svn_error_t * not_txn (svn_fs_root_t *root) { @@ -222,6 +221,32 @@ not_txn (svn_fs_root_t *root) "root object must be a transaction root"); } + +/* Populating the `changes' table. */ + +/* Add a change to the changes table in FS, keyed on transaction id + TXN_ID, and indicated that a change of kind CHANGE_KIND occured on + PATH (whose node revision id is--or was, in the case of a + deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs + occured. Do all this as part of TRAIL. */ +static svn_error_t * +add_change (svn_fs_t *fs, + const char *txn_id, + const char *path, + const svn_fs_id_t *noderev_id, + svn_fs_path_change_kind_t change_kind, + int text_mod, + int prop_mod, + trail_t *trail) +{ + svn_fs__change_t change; + change.path = svn_fs__canonicalize_abspath (path, trail->pool); + change.noderev_id = noderev_id; + change.kind = change_kind; + change.text_mod = text_mod; + change.prop_mod = prop_mod; + return svn_fs__changes_add (fs, txn_id, &change, trail); +} /* Simple root operations. */ @@ -340,7 +365,16 @@ typedef enum copy_id_inherit_t also needs to change the parent directory. */ typedef struct parent_path_t { - + /* The last non-lazy branchpoint that was encountered for this node. + or NULL if the lazy determination logic in open_path was skipped. */ + const svn_fs_id_t *last_branch_id; + + /* This is set to true if this node hasn't been modified + since being copied onto this path. This is set to false + if this node hasn't been copied, or has been modified since + being copied. */ + svn_boolean_t in_lazy_land; + /* A node along the path. This could be the final node, one of its parents, or the root. Every parent path ends with an element for the root directory. */ @@ -350,28 +384,137 @@ typedef struct parent_path_t root directory, which (obviously) has no name in its parent. */ char *entry; + /* The path that this entry was last committed at for a lazy + branch point. */ + const char *last_path; + /* The parent of NODE, or zero if NODE is the root directory. */ struct parent_path_t *parent; } parent_path_t; - /* Allocate a new parent_path_t node from POOL, referring to NODE, ENTRY, PARENT, and COPY_ID. */ static parent_path_t * make_parent_path (dag_node_t *node, char *entry, + const svn_fs_id_t *last_branch_id, + svn_boolean_t in_lazy_land, + const char *last_path, parent_path_t *parent, apr_pool_t *pool) { parent_path_t *parent_path = apr_pcalloc (pool, sizeof (*parent_path)); + parent_path->last_branch_id = last_branch_id; + parent_path->in_lazy_land = in_lazy_land; parent_path->node = node; parent_path->entry = entry; parent_path->parent = parent; + parent_path->last_path = last_path; return parent_path; } +static svn_error_t * +get_id_path (const char **path, + svn_fs_t *fs, + const svn_fs_id_t *id, + trail_t *trail) +{ + apr_hash_t *changes; + apr_hash_index_t *hi; + + /* Initialize returned value. */ + *path = NULL; + + /* Fetch all the changes that occured in the transaction that child + appeared in. Find the change whose node revision id is ID, and + return the path associated with it. If no such change exists, + return the default value. */ + SVN_ERR (svn_fs__changes_fetch (&changes, fs, + svn_fs__id_txn_id (id), trail)); + for (hi = apr_hash_first (trail->pool, changes); hi; hi = apr_hash_next (hi)) + { + svn_fs_path_change_t *change; + void *val; + const void *key; + const char *change_path; + + apr_hash_this (hi, &key, NULL, &val); + change_path = key; + change = val; + if (svn_fs_compare_ids (change->node_rev_id, id) == 0) + { + *path = change_path; + break; + } + } + + return SVN_NO_ERROR; +} + + +/* Sets lazy_p to TRUE if the child_id was not committed at + child_path. Sets branch_p to TRUE if the child_id is not + lazy and child_id is a branch point. Set + last_committed_path if child_id is a lazy branch point. +*/ +static svn_error_t * +is_child_lazy_copied (svn_boolean_t *lazy_p, + svn_boolean_t *branch_p, + char **last_committed_path, + svn_fs_t *fs, + const svn_fs_id_t *parent_id, + const svn_fs_id_t *child_id, + const char *child_path, + trail_t *trail) +{ + char *child_committed_path; + svn_fs__copy_t *copy; + + /* If the current CopyID and the child CopyID are different + then the child is either a branch point, or a lazy copied + node revision. */ + if (strcmp(svn_fs__id_copy_id(parent_id), + svn_fs__id_copy_id(child_id)) != 0) + { + /* The only way to determine laziness is to compare the + current path against the committed path. */ + SVN_ERR(get_id_path(&child_committed_path, fs, + child_id, trail)); + + if (child_committed_path + && strcmp(child_path, child_committed_path) == 0) + { + /* It's not lazy copied. This means it is a + non-lazy branch point. */ + /* Assert branch point-ness here. */ + *branch_p = TRUE; + *lazy_p = FALSE; + } + else + { + /* This is a lazy node revision on this path. */ + SVN_ERR (svn_fs__get_copy (©, fs, svn_fs__id_copy_id(child_id), + trail)); + if (strcmp(svn_fs__id_node_id(copy->dst_noderev_id), + svn_fs__id_node_id(child_id)) == 0 + && strcmp(svn_fs__id_copy_id(copy->dst_noderev_id), + svn_fs__id_copy_id(child_id)) == 0 + && child_committed_path + && last_committed_path) + { + *last_committed_path = apr_pstrdup(trail->pool, child_committed_path); + } + *lazy_p = TRUE; + } + } + else + { + *lazy_p = FALSE; + } + return SVN_NO_ERROR; +} /* Return a null-terminated copy of the first component of PATH, allocated in POOL. If path is empty, or consists entirely of @@ -453,16 +596,22 @@ open_path (parent_path_t **parent_path_p svn_fs_t *fs = root->fs; apr_pool_t *pool = trail->pool; const svn_fs_id_t *id; + const svn_fs_id_t *child_id; + const svn_fs_id_t *last_branch_id; + svn_boolean_t in_lazy_land = FALSE; dag_node_t *here; /* The directory we're currently looking at. */ parent_path_t *parent_path; /* The path from HERE up to the root. */ const char *rest; /* The portion of PATH we haven't traversed yet. */ const char *canon_path = svn_fs__canonicalize_abspath (path, trail->pool); + char *last_path = NULL; /* Make a parent_path item for the root node, using its own current copy id. */ SVN_ERR (root_node (&here, root, trail)); + id = svn_fs__dag_get_id (here); - parent_path = make_parent_path (here, 0, 0, pool); + last_branch_id = id; + parent_path = make_parent_path (here, NULL, last_branch_id, FALSE, NULL, NULL, pool); rest = canon_path + 1; /* skip the leading '/', it saves in iteration */ /* Whenever we are at the top of this loop: @@ -504,8 +653,9 @@ open_path (parent_path_t **parent_path_p if ((flags & open_path_last_optional) && (! next || *next == '\0')) { - parent_path = make_parent_path (NULL, entry, parent_path, - pool); + parent_path = make_parent_path (NULL, entry, + NULL, in_lazy_land, + NULL, parent_path, pool); break; } else @@ -519,8 +669,41 @@ open_path (parent_path_t **parent_path_p /* Other errors we return normally. */ SVN_ERR (err); + /* We now need to determine if child is a lazy entity, or was + actually committed at this path. */ + child_id = svn_fs__dag_get_id(child); + if (!in_lazy_land) + { + int length = strlen(canon_path) + 1; + char *child_path = apr_palloc(trail->pool, length); + svn_boolean_t is_branch = FALSE; + char *end = strchr(rest, '/'); + + if (end == NULL) + { + memcpy(child_path, canon_path, length); + } + else + { + length = end - canon_path; + memcpy(child_path, canon_path, length); + child_path[length] = '\0'; + } + + SVN_ERR(is_child_lazy_copied(&in_lazy_land, &is_branch, &last_path, + fs, id, child_id, + child_path, trail)); + if (!in_lazy_land && is_branch) + { + /* It must be a branch point. */ + last_branch_id = child_id; + } + } + /* Now, make a parent_path item for CHILD. */ - parent_path = make_parent_path (child, entry, parent_path, pool); + parent_path = make_parent_path (child, entry, last_branch_id, + in_lazy_land, last_path, parent_path, + pool); } /* Are we finished traversing the path? */ @@ -533,6 +716,8 @@ open_path (parent_path_t **parent_path_p rest = next; here = child; + id = child_id; + last_path = NULL; } *parent_path_p = parent_path; @@ -552,43 +737,6 @@ parent_path_path (parent_path_t *parent_ : path_so_far; } -static svn_error_t * -get_id_path (const char **path, - svn_fs_t *fs, - const svn_fs_id_t *id, - trail_t *trail) -{ - apr_hash_t *changes; - apr_hash_index_t *hi; - - /* Initialize returned value. */ - *path = NULL; - - /* Fetch all the changes that occured in the transaction that child - appeared in. Find the change whose node revision id is ID, and - return the path associated with it. If no such change exists, - return the default value. */ - SVN_ERR (svn_fs__changes_fetch (&changes, fs, - svn_fs__id_txn_id (id), trail)); - for (hi = apr_hash_first (trail->pool, changes); hi; hi = apr_hash_next (hi)) - { - svn_fs_path_change_t *change; - void *val; - const void *key; - const char *change_path; - - apr_hash_this (hi, &key, NULL, &val); - change_path = key; - change = val; - if (svn_fs_compare_ids (change->node_rev_id, id) == 0) - { - *path = change_path; - break; - } - } - - return SVN_NO_ERROR; -} /* Choose a copy ID inheritance method *INHERIT_P to be used in the @@ -627,7 +775,7 @@ choose_copy_id (copy_id_inherit_t *inher /* Special case: if the child's copy ID is '0', use the parent's copy ID. */ if (strcmp (child_copy_id, "0") == 0) - return SVN_NO_ERROR; + return SVN_NO_ERROR; /* Compare the copy IDs of the child and its parent. If they are the same, then the child is already on the same branch as the @@ -644,26 +792,27 @@ choose_copy_id (copy_id_inherit_t *inher or if it is a branch point that we are accessing via its original copy destination path. */ SVN_ERR (svn_fs__get_copy (©, fs, child_copy_id, trail)); - if (svn_fs_compare_ids (copy->dst_noderev_id, child_id) == -1) + if (!(strcmp(svn_fs__id_node_id(copy->dst_noderev_id), + svn_fs__id_node_id(child_id)) == 0 + && strcmp(svn_fs__id_copy_id(copy->dst_noderev_id), + svn_fs__id_copy_id(child_id)) == 0)) return SVN_NO_ERROR; - /* Fetch all the changes that occured in the transaction that child - appeared in. Find the change whose node revision ID is the - child, so we can determine if we are looking at the child via its - original path or as a subtree item of a copied tree. */ - SVN_ERR (get_id_path (&id_path, fs, child_id, trail)); - child_path = parent_path_path (child, trail->pool); - if (id_path && child_path && (strcmp (child_path, id_path) == 0)) + /* The parent_path of child already knows whether or not this node + was committed on the current path or not. */ + if (!child->in_lazy_land) { - *inherit_p = copy_id_inherit_self; - return SVN_NO_ERROR; + /* We now know that this branch was created on this path, and any + change to this node should keep the current CopyID. */ + *inherit_p = copy_id_inherit_self; + return SVN_NO_ERROR; } - /* We are pretty sure that the child node is an unedited nested + /* We are sure that the child node is an unedited nested branched node. When it needs to be made mutable, it should claim a new copy ID. */ *inherit_p = copy_id_inherit_new; - *copy_src_path = id_path; + *copy_src_path = child->last_path; return SVN_NO_ERROR; } @@ -740,6 +889,15 @@ make_path_mutable (svn_fs_root_t *root, svn_fs__id_txn_id (node_id), new_node_id, trail)); SVN_ERR (svn_fs__add_txn_copy (fs, txn_id, copy_id, trail)); + SVN_ERR (add_change (fs, txn_id, + parent_path_path(parent_path, trail->pool), new_node_id, + svn_fs_path_change_modify, 0, 0, trail)); + } + else + { + SVN_ERR (add_change (fs, txn_id, parent_path_path(parent_path, trail->pool), + svn_fs__dag_get_id(clone), + svn_fs_path_change_modify, 0, 0, trail)); } } else @@ -773,34 +931,6 @@ get_dag (dag_node_t **dag_node_p, -/* Populating the `changes' table. */ - -/* Add a change to the changes table in FS, keyed on transaction id - TXN_ID, and indicated that a change of kind CHANGE_KIND occured on - PATH (whose node revision id is--or was, in the case of a - deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs - occured. Do all this as part of TRAIL. */ -static svn_error_t * -add_change (svn_fs_t *fs, - const char *txn_id, - const char *path, - const svn_fs_id_t *noderev_id, - svn_fs_path_change_kind_t change_kind, - int text_mod, - int prop_mod, - trail_t *trail) -{ - svn_fs__change_t change; - change.path = svn_fs__canonicalize_abspath (path, trail->pool); - change.noderev_id = noderev_id; - change.kind = change_kind; - change.text_mod = text_mod; - change.prop_mod = prop_mod; - return svn_fs__changes_add (fs, txn_id, &change, trail); -} - - - /* Generic node operations. */ @@ -854,10 +984,30 @@ static svn_error_t * txn_body_node_created_rev (void *baton, trail_t *trail) { struct node_created_rev_args *args = baton; - dag_node_t *node; + parent_path_t *parent_path; + svn_fs__copy_t *copy; + svn_fs__transaction_t *txn; - SVN_ERR (get_dag (&node, args->root, args->path, trail)); - SVN_ERR (svn_fs__dag_get_revision (&(args->revision), node, trail)); + SVN_ERR (open_path (&parent_path, args->root, args->path, 0, trail)); + if (parent_path->in_lazy_land) + { + SVN_ERR (svn_fs__get_copy (©, args->root->fs, + svn_fs__id_copy_id( + parent_path->last_branch_id), + trail)); + SVN_ERR (svn_fs__get_txn (&txn, args->root->fs, + svn_fs__id_txn_id( + copy->dst_noderev_id), + trail)); + } + else + { + SVN_ERR (svn_fs__get_txn (&txn, args->root->fs, + svn_fs__id_txn_id( + svn_fs__dag_get_id(parent_path->node)), + trail)); + } + args->revision = txn->revision; return SVN_NO_ERROR; } @@ -2462,11 +2612,102 @@ txn_body_dir_entries (void *baton, struct dir_entries_args *args = baton; parent_path_t *parent_path; apr_hash_t *entries; + apr_hash_index_t *hi; + svn_revnum_t last_revision; + svn_fs__copy_t *copy; + svn_fs__transaction_t *txn; + const char *parent_copy_id; + const svn_fs_id_t *parent_id; + svn_boolean_t lazy = FALSE; + svn_string_t *createdrev = NULL; + svn_fs_t *fs; + + fs = args->root->fs; SVN_ERR (open_path (&parent_path, args->root, args->path, 0, trail)); /* Get the entries for PARENT_PATH. */ SVN_ERR (svn_fs__dag_dir_entries (&entries, parent_path->node, trail)); + if (entries == NULL) + { + *args->table_p = apr_hash_make (trail->pool); + return SVN_NO_ERROR; + } + + parent_id = svn_fs__dag_get_id(parent_path->node); + parent_copy_id = svn_fs__id_copy_id(parent_id); + + /* open_path has logic to see through the laziness of the DAG implemented + by dag.c. We need to something similar for directory entries. */ + + /* If the containing directory is completly lazy, then every entry of ours + will also be lazy. */ + if (parent_path->in_lazy_land) + { + SVN_ERR (svn_fs__get_copy (©, fs, + svn_fs__id_copy_id( + parent_path->last_branch_id), + trail)); + SVN_ERR (svn_fs__get_txn (&txn, fs, + svn_fs__id_txn_id(copy->dst_noderev_id), + trail)); + last_revision = txn->revision; + } + + /* Add created on path at revision data to each directory entry. */ + for (hi = apr_hash_first (trail->pool, entries); hi; hi = apr_hash_next (hi)) + { + const void *key; + apr_ssize_t klen; + void *val; + svn_fs_dirent_t *dirent; + + /* KEY will be the entry name in parent, VAL the dirent. */ + apr_hash_this (hi, &key, &klen, &val); + dirent = (svn_fs_dirent_t *)val; + + /* If we already know we're in lazy land, then the value will never + change, so use the value we already know. */ + if (parent_path->in_lazy_land) + { + dirent->created_rev = last_revision; + } + else + { + const char *child_path = + svn_fs__canonicalize_abspath(svn_path_join( + args->path, dirent->name, + trail->pool), + trail->pool); + svn_boolean_t is_branch = FALSE; + + SVN_ERR (is_child_lazy_copied(&lazy, &is_branch, NULL, fs, parent_id, + dirent->id, child_path, trail)); + if (lazy) + { + /* Lookup the revision of the copy operation that exposed + this node revision at this path. */ + SVN_ERR (svn_fs__get_copy (©, fs, + svn_fs__id_copy_id( + parent_path->last_branch_id), + trail)); + SVN_ERR (svn_fs__get_txn (&txn, fs, + svn_fs__id_txn_id( + copy->dst_noderev_id), + trail)); + dirent->created_rev = txn->revision; + } + else + { + /* Now we have to lookup the created revision property ourselves. */ + SVN_ERR (svn_fs__get_txn (&txn, fs, + svn_fs__id_txn_id(dirent->id), trail)); + + dirent->created_rev = txn->revision; + } + } + apr_hash_set (entries, key, klen, (void *) dirent); + } /* Potentially initialize the return value to an empty hash. */ *args->table_p = entries ? entries : apr_hash_make (trail->pool); @@ -3459,8 +3700,8 @@ revisions_changed_callback (void *baton, struct revisions_changed_args { apr_hash_t *revs; - svn_fs_t *fs; - const svn_fs_id_t *id; + svn_fs_root_t *root; + const char *path; int cross_copy_history; }; @@ -3470,7 +3711,9 @@ txn_body_revisions_changed (void *baton, { struct revisions_changed_args *args = baton; struct revisions_changed_baton b; - dag_node_t *node; + parent_path_t *parent_path; + svn_fs__copy_t *copy; + svn_fs__transaction_t *txn; svn_revnum_t *rev = apr_palloc (apr_hash_pool_get (args->revs), sizeof (*rev)); @@ -3485,16 +3728,29 @@ txn_body_revisions_changed (void *baton, about to start working on a new node. */ b.successor_id = NULL; - /* Get the NODE for ARGS->id. */ - SVN_ERR (svn_fs__dag_get_node (&node, args->fs, args->id, trail)); - + SVN_ERR (open_path (&parent_path, args->root, args->path, 0, trail)); + + /* Stop immediately with the revision that created us at this path + if we've been lazy copied but not yet modified and we're not + crossing copy history. */ + if (parent_path->in_lazy_land && !b.cross_copy_history) + { + SVN_ERR (svn_fs__get_copy (©, args->root->fs, svn_fs__id_copy_id(parent_path->last_branch_id), trail)); + SVN_ERR (svn_fs__get_txn (&txn, args->root->fs, + svn_fs__id_txn_id(copy->dst_noderev_id), + trail)); + *rev = txn->revision; + apr_hash_set(b.revs, (void *)rev, sizeof (rev), (void *)1); + return SVN_NO_ERROR; + } + /* Add NODE's created rev to the array in the baton. */ - SVN_ERR (svn_fs__dag_get_revision (rev, node, trail)); + SVN_ERR (svn_fs__dag_get_revision (rev, parent_path->node, trail)); if (SVN_IS_VALID_REVNUM (*rev)) apr_hash_set (b.revs, (void *)rev, sizeof (rev), (void *)1); /* Walk NODE's predecessors, harvesting revisions changed. */ - return svn_fs__dag_walk_predecessors (node, revisions_changed_callback, + return svn_fs__dag_walk_predecessors (parent_path->node, revisions_changed_callback, &b, trail); } @@ -3518,16 +3774,14 @@ svn_fs_revisions_changed (apr_array_head /* Populate the common baton members. */ args.revs = all_revs; - args.fs = fs; + args.root = root; args.cross_copy_history = cross_copy_history; /* Get the node revision id for each PATH under ROOT, and find out in which revisions that node revision id was changed. */ for (i = 0; i < paths->nelts; i++) { - SVN_ERR (svn_fs_node_id (&(args.id), root, - APR_ARRAY_IDX (paths, i, const char *), - subpool)); + args.path = APR_ARRAY_IDX (paths, i, const char *); SVN_ERR (svn_fs__retry_txn (fs, txn_body_revisions_changed, &args, subpool)); svn_pool_clear (subpool); Index: tests/libsvn_fs/fs-test.c =================================================================== --- tests/libsvn_fs/fs-test.c (revision 3866) +++ tests/libsvn_fs/fs-test.c (working copy) @@ -5585,27 +5585,581 @@ branch_test (const char **msg, SVN_ERR (svn_fs_close_txn (txn)); svn_pool_clear (spool); -#if 0 { - const svn_fs_id_t *G2_id, *G2_rho_id, *G2_rho2_id; + const svn_fs_id_t *D_G2_id, *D_G2_rho_id, *D_G2_rho2_id, *D_G_id, + *D_G_rho_id, *D_G_rho2_id; + const svn_fs_id_t *D2_id, *D2_G_id, *D2_G2_id, *D2_G_rho_id, + *D2_G_rho2_id, *D2_G2_rho_id, *D2_G2_rho2_id, *D_id; + svn_string_t *s1, *s2; /* Now, A/D/G and A/D/G2 should have the same NodeId, but A/D/G2 should have earned a new CopyId. Also, A/D/G/rho and A/D/G/rho2 should be the same nodes as A/D/G2/rho and A/D/G2/rho2, respectively. */ SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); - SVN_ERR (svn_fs_node_id (&G2_id, rev_root, "A/D/G2", spool)); - SVN_ERR (svn_fs_node_id (&G2_rho_id, rev_root, "A/D/G2/rho", spool)); - SVN_ERR (svn_fs_node_id (&G2_rho2_id, rev_root, "A/D/G2/rho2", spool)); + + SVN_ERR (svn_fs_node_id (&D_G2_id, rev_root, "A/D/G2", spool)); + SVN_ERR (svn_fs_node_id (&D_G_id, rev_root, "A/D/G", spool)); + s1 = svn_fs_unparse_id (D_G2_id, spool); + s2 = svn_fs_unparse_id (D_G_id, spool); + + SVN_ERR (svn_fs_node_id (&D_G2_rho_id, rev_root, "A/D/G2/rho", spool)); + SVN_ERR (svn_fs_node_id (&D_G2_rho2_id, rev_root, "A/D/G2/rho2", spool)); + SVN_ERR (svn_fs_node_id (&D_G_rho_id, rev_root, "A/D/G/rho", spool)); + SVN_ERR (svn_fs_node_id (&D_G_rho2_id, rev_root, "A/D/G/rho2", spool)); + + s1 = svn_fs_unparse_id (D_G2_rho_id, spool); + s2 = svn_fs_unparse_id (D_G_rho_id, spool); + + if (strcmp(s2->data, "f.0.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho id: expected f.0.5 got: %s", s2); + if (strcmp(s1->data, "f.2.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho id: expected d.2.5 got: %s", s1); + + s1 = svn_fs_unparse_id (D_G2_rho2_id, spool); + s2 = svn_fs_unparse_id (D_G_rho2_id, spool); + + if (strcmp(s2->data, "f.1.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho2 id: expected d.1.5 got: %s", s2); + if (strcmp(s1->data, "f.4.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho2 id: expected f.4.5 got: %s", s1); + + SVN_ERR (svn_fs_node_id (&D_id, rev_root, "A/D", spool)); + SVN_ERR (svn_fs_node_id (&D2_id, rev_root, "A/D2", spool)); + s1 = svn_fs_unparse_id (D_id, spool); + s2 = svn_fs_unparse_id (D2_id, spool); + + if (strcmp(s1->data, "b.0.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D id: expected b.0.5 got: %s", s1); + if (strcmp(s2->data, "b.3.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2 id: expected b.3.5 got: %s", s2); + + SVN_ERR (svn_fs_node_id (&D2_G_id, rev_root, "A/D2/G", spool)); + SVN_ERR (svn_fs_node_id (&D2_G2_id, rev_root, "A/D2/G2", spool)); + s1 = svn_fs_unparse_id (D2_G_id, spool); + s2 = svn_fs_unparse_id (D2_G2_id, spool); + + if (strcmp(s1->data, "d.3.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G id: expected d.3.5 got: %s", s1); + if (strcmp(s2->data, "d.6.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2 id: expected d.6.5 got: %s", s2); + + SVN_ERR (svn_fs_node_id (&D2_G_rho_id, rev_root, "A/D2/G/rho", spool)); + SVN_ERR (svn_fs_node_id (&D2_G2_rho_id, rev_root, "A/D2/G2/rho", spool)); + + s1 = svn_fs_unparse_id (D2_G_rho_id, spool); + s2 = svn_fs_unparse_id (D2_G2_rho_id, spool); + + if (strcmp(s1->data, "f.3.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho id: expected f.3.5 got: %s", s1); + if (strcmp(s2->data, "f.6.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho id: expected f.6.5 got: %s", s2); + + SVN_ERR (svn_fs_node_id (&D2_G_rho2_id, rev_root, "A/D2/G/rho2", spool)); + SVN_ERR (svn_fs_node_id (&D2_G2_rho2_id, rev_root, "A/D2/G2/rho2", spool)); + + s1 = svn_fs_unparse_id (D2_G_rho2_id, spool); + s2 = svn_fs_unparse_id (D2_G2_rho2_id, spool); + + if (strcmp(s1->data, "f.5.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho2 id: expected f.5.5 got: %s", s1); + if (strcmp(s2->data, "f.7.5") != 0) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 id: expected f.7.5 got: %s", s2); + + svn_pool_clear (spool); } + + svn_pool_destroy (spool); + svn_fs_close_fs (fs); + return SVN_NO_ERROR; +} + +static svn_error_t * +lazy_copies_created_rev (const char **msg, + svn_boolean_t msg_only, + apr_pool_t *pool) +{ + apr_pool_t *spool = svn_pool_create (pool); + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *rev_root; + svn_revnum_t youngest_rev = 0; + svn_revnum_t rev = 0; + + *msg = "seeing through lazy copies (node_created_rev)"; + + if (msg_only) + return SVN_NO_ERROR; + + /* Create a filesystem and repository. */ + SVN_ERR (svn_test__create_fs (&fs, "test-repo-see-through-lazy-copies", pool)); + + /*** Revision 1: Create the greek tree in revision. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_test__create_greek_tree (txn_root, spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 2: Copy A/D/G/rho to A/D/G/rho2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G/rho", txn_root, "A/D/G/rho2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 3: Copy A/D/G to A/D/G2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G", txn_root, "A/D/G2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 4: Copy A/D to A/D2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D", txn_root, "A/D2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); svn_pool_clear (spool); -#endif /* 0 */ + /*** Now verify that all rho's, and rho2's have the correct created-rev value. ***/ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D/G/rho", spool)); + if (rev != 1) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho created rev mistmatch expeceted 1, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D/G/rho2", spool)); + if (rev != 2) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho2 created rev mismatch expected 2, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D/G2/rho", spool)); + if (rev != 3) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho created rev mismatch expected 3, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D/G2/rho2", spool)); + if (rev != 3) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho2 created rev mismatch expected 3, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G/rho", spool)); + if (rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G/rho2", spool)); + if (rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho2 created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G2/rho", spool)); + if (rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G2/rho2", spool)); + if (rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 created rev mismatch expected 4, got: %d", rev); + + svn_pool_clear (spool); + + /* Revision 5: Modify some lazy copies, and re-verify the created-revs. */ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho", + "Edited text.", spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho2", + "Edited text.", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /* Verify the created-revs on our modified paths. */ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G2/rho", spool)); + if (rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho created rev mismatch expected 5, got: %d", rev); + + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G2/rho2", spool)); + if (rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 created rev mismatch expected 5, got: %d", rev); + /* Also verify the created-revs on our bubble up directories. */ + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2/G2", spool)); + if (rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2 created rev mismatch expected 5, got: %d", rev); + SVN_ERR (svn_fs_node_created_rev(&rev, rev_root, "A/D2", spool)); + if (rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2 created rev mismatch expected 5, got: %d", rev); + svn_pool_destroy (spool); svn_fs_close_fs (fs); return SVN_NO_ERROR; } +static svn_error_t * +lazy_copies_dir_entries (const char **msg, + svn_boolean_t msg_only, + apr_pool_t *pool) +{ + apr_pool_t *spool = svn_pool_create (pool); + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *rev_root; + svn_revnum_t youngest_rev = 0; + svn_revnum_t rev = 0; + apr_hash_t *entries; + svn_fs_dirent_t *entry; + + *msg = "seeing through lazy copies (dir_entries)"; + + if (msg_only) + return SVN_NO_ERROR; + + /* Create a filesystem and repository. */ + SVN_ERR (svn_test__create_fs (&fs, "test-repo-lazy-copies-dir-entries", pool)); + + /*** Revision 1: Create the greek tree in revision. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_test__create_greek_tree (txn_root, spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 2: Copy A/D/G/rho to A/D/G/rho2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G/rho", txn_root, "A/D/G/rho2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 3: Copy A/D/G to A/D/G2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G", txn_root, "A/D/G2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 4: Copy A/D to A/D2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D", txn_root, "A/D2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Now verify that all rho's, and rho2's have the correct created-rev value. ***/ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D/G", spool)); + entry = apr_hash_get (entries, "rho", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 1) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho created rev mistmatch expeceted 1, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D/G", spool)); + entry = apr_hash_get (entries, "rho2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 2) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G/rho2 created rev mistmatch expeceted 2, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D/G2", spool)); + entry = apr_hash_get (entries, "rho", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 3) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho created rev mismatch expected 3, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D/G2", spool)); + entry = apr_hash_get (entries, "rho2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 3) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho2 created rev mismatch expected 3, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G", spool)); + entry = apr_hash_get (entries, "rho", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G", spool)); + entry = apr_hash_get (entries, "rho2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho2 created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G2", spool)); + entry = apr_hash_get (entries, "rho", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho created rev mismatch expected 4, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G2", spool)); + entry = apr_hash_get (entries, "rho2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 4) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 created rev mismatch expected 4, got: %d", rev); + + svn_pool_clear (spool); + + /* Revision 5: Modify some lazy copies, and re-verify the created-revs. */ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho", + "Edited text.", spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho2", + "Edited text.", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /* Verify the created-revs on our modified paths. */ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G2", spool)); + entry = apr_hash_get (entries, "rho", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho created rev mismatch expected 5, got: %d", rev); + + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2/G2", spool)); + entry = apr_hash_get (entries, "rho2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 created rev mismatch expected 5, got: %d", rev); + + /* Also verify the created-revs on our bubble up directories. */ + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A/D2", spool)); + entry = apr_hash_get (entries, "G2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2 created rev mismatch expected 5, got: %d", rev); + SVN_ERR (svn_fs_dir_entries(&entries, rev_root, "A", spool)); + entry = apr_hash_get (entries, "D2", APR_HASH_KEY_STRING); + if (entry == NULL + || entry->created_rev != 5) + return svn_error_createf(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2 created rev mismatch expected 5, got: %d", rev); + + svn_pool_destroy (spool); + svn_fs_close_fs (fs); + return SVN_NO_ERROR; +} + +static svn_error_t * +lazy_copies_rev_changed (const char **msg, + svn_boolean_t msg_only, + apr_pool_t *pool) +{ + apr_pool_t *spool = svn_pool_create (pool); + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *rev_root; + svn_revnum_t youngest_rev = 0; + svn_revnum_t rev = 0; + apr_array_header_t *revs = NULL; + apr_array_header_t *path = NULL; + + *msg = "seeing through lazy copies (revisions_changed)"; + + if (msg_only) + return SVN_NO_ERROR; + + /* Create a filesystem and repository. */ + SVN_ERR (svn_test__create_fs (&fs, "test-repo-lazy-copies-rev-changed", pool)); + + /*** Revision 1: Create the greek tree in revision. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_test__create_greek_tree (txn_root, spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 2: Copy A/D/G/rho to A/D/G/rho2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G/rho", txn_root, "A/D/G/rho2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 3: Copy A/D/G to A/D/G2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D/G", txn_root, "A/D/G2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Revision 4: Copy A/D to A/D2. ***/ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_copy (rev_root, "A/D", txn_root, "A/D2", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /*** Now verify that for all lazy paths svn_fs_revisions_changed doesn't cross + the copy barrier. ***/ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D/G2/rho"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 3 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D/G2/rho2"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 3 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D/G2/rho2 revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G/rho"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G2/rho"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G/rho2"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G/rho2 revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G2/rho2"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho2 revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G2"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 4 + || revs->nelts != 1) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2 revisions changed failure."); + + svn_pool_clear (spool); + + /* Revision 5: Modify some lazy copies, and re-verify the created-revs. */ + SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, spool)); + SVN_ERR (svn_fs_txn_root (&txn_root, txn, spool)); + SVN_ERR (svn_fs_revision_root (&rev_root, fs, youngest_rev, spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho", + "Edited text.", spool)); + SVN_ERR (svn_test__set_file_contents (txn_root, "A/D2/G2/rho2", + "Edited text.", spool)); + SVN_ERR (svn_fs_commit_txn (NULL, &youngest_rev, txn)); + SVN_ERR (svn_fs_close_txn (txn)); + svn_pool_clear (spool); + + /* Verify the changed revisiosn on our modified paths. */ + SVN_ERR (svn_fs_revision_root(&rev_root, fs, youngest_rev, spool)); + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G2/rho"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 5 + || revs->nelts != 2) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho revisions changed failure."); + + path = apr_array_make(spool, 1, sizeof(const char *)); + (*(const char **)apr_array_push(path)) = "A/D2/G2/rho2"; + SVN_ERR (svn_fs_revisions_changed (&revs, rev_root, path, 0, spool)); + rev = ((svn_revnum_t *)revs->elts)[0]; + if (rev != 5 + || revs->nelts != 2) + return svn_error_create(SVN_ERR_TEST_FAILED, 0, NULL, + "/A/D2/G2/rho revisions changed failure."); + + svn_pool_destroy (spool); + svn_fs_close_fs (fs); + return SVN_NO_ERROR; +} + + + /* ------------------------------------------------------------------------ */ @@ -5649,5 +6203,8 @@ struct svn_test_descriptor_t test_funcs[ SVN_TEST_PASS (revisions_changed), SVN_TEST_PASS (canonicalize_abspath), SVN_TEST_PASS (branch_test), + SVN_TEST_PASS (lazy_copies_created_rev), + SVN_TEST_PASS (lazy_copies_dir_entries), + SVN_TEST_PASS (lazy_copies_rev_changed), SVN_TEST_NULL };