Index: include/svn_fs.h =================================================================== --- include/svn_fs.h (revision 4154) +++ include/svn_fs.h (working copy) @@ -1047,6 +1047,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 4154) +++ 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) { @@ -340,7 +339,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 +358,118 @@ 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) +{ + dag_node_t *node; + + /* Initialize returned value. */ + *path = NULL; + + SVN_ERR (svn_fs__dag_get_node (&node, fs, id, trail)); + SVN_ERR (svn_fs__dag_get_committed_path (path, node, trail)); + *path = svn_fs__canonicalize_abspath (*path, trail->pool); + 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 @@ -426,7 +524,10 @@ typedef enum open_path_flags_t { directories must exist, as usual.) If the last component doesn't exist, simply leave the `node' member of the bottom parent_path component zero. */ - open_path_last_optional = 1 + open_path_last_optional = 1, + + /* open_path will skip its lazy copy detection logic */ + open_path_skip_lazy_detection = 2, } open_path_flags_t; @@ -453,16 +554,27 @@ 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 = NULL; + 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); + if (!(flags & open_path_skip_lazy_detection)) + { + 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 +616,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 +632,46 @@ open_path (parent_path_t **parent_path_p /* Other errors we return normally. */ SVN_ERR (err); + child_id = svn_fs__dag_get_id (child); + + /* We now need to determine if child is a lazy entity, or was + actually committed at this path. */ + if (!(flags & open_path_skip_lazy_detection)) + { + + 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 +684,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 +705,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 +743,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 +760,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; } @@ -727,6 +844,8 @@ make_path_mutable (svn_fs_root_t *root, parent_path->parent->node, parent_path->entry, copy_id, txn_id, + parent_path_path (parent_path, + trail->pool), trail)); /* If we just created a brand new copy ID, we need to store a @@ -763,15 +882,15 @@ get_dag (dag_node_t **dag_node_p, { parent_path_t *parent_path; - /* Call open_path with no flags, as we want this to return an error - if the node for which we are searching doesn't exist. */ - SVN_ERR (open_path (&parent_path, root, path, 0, trail)); + /* Call open_path with the skip lazy detection flag, as we + don't care about that data atm. */ + SVN_ERR (open_path (&parent_path, root, path, + open_path_skip_lazy_detection, trail)); *dag_node_p = parent_path->node; return SVN_NO_ERROR; } - /* Populating the `changes' table. */ @@ -799,7 +918,6 @@ add_change (svn_fs_t *fs, return svn_fs__changes_add (fs, txn_id, &change, trail); } - /* Generic node operations. */ @@ -854,10 +972,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; } @@ -1102,7 +1240,8 @@ txn_body_node_proplist (void *baton, tra parent_path_t *parent_path; apr_hash_t *proplist; - SVN_ERR (open_path (&parent_path, args->root, args->path, 0, trail)); + SVN_ERR (open_path (&parent_path, args->root, args->path, + open_path_skip_lazy_detection, trail)); SVN_ERR (svn_fs__dag_get_proplist (&proplist, parent_path->node, trail)); *args->table_p = proplist ? proplist : apr_hash_make (trail->pool); return SVN_NO_ERROR; @@ -1146,6 +1285,8 @@ txn_body_change_node_prop (void *baton, apr_hash_t *proplist; const char *txn_id = svn_fs_txn_root_name (args->root, trail->pool); + /* We must calculate lazy infomration here so that make_path_mutable + can take advantage of the calculated information. */ SVN_ERR (open_path (&parent_path, args->root, args->path, 0, trail)); SVN_ERR (make_path_mutable (args->root, parent_path, args->path, trail)); SVN_ERR (svn_fs__dag_get_proplist (&proplist, parent_path->node, trail)); @@ -1214,8 +1355,10 @@ txn_body_props_changed (void *baton, tra struct things_changed_args *args = baton; parent_path_t *parent_path_1, *parent_path_2; - SVN_ERR (open_path (&parent_path_1, args->root1, args->path1, 0, trail)); - SVN_ERR (open_path (&parent_path_2, args->root2, args->path2, 0, trail)); + SVN_ERR (open_path (&parent_path_1, args->root1, args->path1, + open_path_skip_lazy_detection, trail)); + SVN_ERR (open_path (&parent_path_2, args->root2, args->path2, + open_path_skip_lazy_detection, trail)); SVN_ERR (svn_fs__things_different (args->changed_p, NULL, @@ -2126,7 +2269,7 @@ txn_body_merge (void *baton, trail_t *tr } if (svn_fs__id_eq (svn_fs__dag_get_id (ancestor_node), - svn_fs__dag_get_id (txn_root_node))) + svn_fs__dag_get_id (txn_root_node))) { /* If no changes have been made in TXN since its current base, then it can't conflict with any changes since that base. So @@ -2462,11 +2605,107 @@ 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; + /* The following code relies on correct laziness calculations, so + we can't skip that expensive operation here. */ 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); @@ -2514,6 +2753,8 @@ txn_body_make_dir (void *baton, dag_node_t *sub_dir; const char *txn_id = svn_fs_txn_root_name (root, trail->pool); + /* We can't skip lazy calculations here because make_path_mutable + cares about those calculations. */ SVN_ERR (open_path (&parent_path, root, path, open_path_last_optional, trail)); @@ -2528,6 +2769,7 @@ txn_body_make_dir (void *baton, parent_path->parent->node, parent_path->entry, txn_id, + path, trail)); /* Make a record of this modification in the changes table. */ @@ -2576,6 +2818,8 @@ txn_body_delete (void *baton, parent_path_t *parent_path; const char *txn_id = svn_fs_txn_root_name (root, trail->pool); + /* We can't skip lazy calculations here because make_path_mutable + cares about those calculations. */ SVN_ERR (open_path (&parent_path, root, path, 0, trail)); if (! svn_fs_is_txn_root (root)) @@ -2676,6 +2920,9 @@ txn_body_copy (void *baton, NULL, "copy from mutable tree not currently supported"); + /* We can't skip lazy calculations here because make_path_mutable + cares about those calculations. */ + /* Build up the parent path from FROM_PATH, making sure that it exists in FROM_ROOT */ SVN_ERR (open_path (&from_parent_path, from_root, from_path, @@ -2709,7 +2956,7 @@ txn_body_copy (void *baton, from_parent_path->node, args->preserve_history, svn_fs_revision_root_revision (from_root), - from_path, txn_id, trail)); + from_path, txn_id, to_path, trail)); /* Make a record of this modification in the changes table. */ SVN_ERR (get_dag (&new_node, to_root, to_path, trail)); @@ -2799,7 +3046,10 @@ txn_body_copied_from (void *baton, trail struct copied_from_args *args = baton; parent_path_t *path_down; - SVN_ERR (open_path (&path_down, args->root, args->path, 0, trail)); + /* Thankfully, we can skip laziness calculation for this + difficult operation. */ + SVN_ERR (open_path (&path_down, args->root, args->path, + open_path_skip_lazy_detection, trail)); SVN_ERR (svn_fs__dag_copied_from (&(args->result_rev), &(args->result_path), path_down->node, @@ -2853,6 +3103,7 @@ txn_body_make_file (void *baton, dag_node_t *child; const char *txn_id = svn_fs_txn_root_name (root, trail->pool); + /* We can't skip laziness calcs because make_path_mutable relies on it. */ SVN_ERR (open_path (&parent_path, root, path, open_path_last_optional, trail)); @@ -2867,6 +3118,7 @@ txn_body_make_file (void *baton, parent_path->parent->node, parent_path->entry, txn_id, + path, trail)); /* Make a record of this modification in the changes table. */ @@ -3301,8 +3553,10 @@ txn_body_contents_changed (void *baton, struct things_changed_args *args = baton; parent_path_t *parent_path_1, *parent_path_2; - SVN_ERR (open_path (&parent_path_1, args->root1, args->path1, 0, trail)); - SVN_ERR (open_path (&parent_path_2, args->root2, args->path2, 0, trail)); + SVN_ERR (open_path (&parent_path_1, args->root1, args->path1, + open_path_skip_lazy_detection, trail)); + SVN_ERR (open_path (&parent_path_2, args->root2, args->path2, + open_path_skip_lazy_detection, trail)); SVN_ERR (svn_fs__things_different (NULL, args->changed_p, @@ -3459,8 +3713,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 +3724,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 +3741,32 @@ 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) + { + 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)); + if (!b.cross_copy_history) + 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 +3790,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: libsvn_fs/dag.h =================================================================== --- libsvn_fs/dag.h (revision 4154) +++ libsvn_fs/dag.h (working copy) @@ -115,6 +115,10 @@ svn_error_t *svn_fs__dag_get_predecessor dag_node_t *node, trail_t *trail); +/* Set *PATH to the initial committed path of this node. */ +svn_error_t *svn_fs__dag_get_committed_path (const char **path, + dag_node_t *node, + trail_t *trail); /* Callback function type for svn_fs__dag_walk_predecessors() */ typedef svn_error_t *(*svn_fs__dag_pred_func_t) (void *baton, @@ -282,12 +286,16 @@ svn_error_t *svn_fs__dag_set_entry (dag_ indicates that this new node is being created as the result of a copy operation, and specifically which operation that was. - TXN_ID is the Subversion transaction under which this occurs. */ + TXN_ID is the Subversion transaction under which this occurs. + + PATH, if non-NULL, is the commit path for the cloned child. + */ svn_error_t *svn_fs__dag_clone_child (dag_node_t **child_p, dag_node_t *parent, const char *name, const char *copy_id, - const char *txn_id, + const char *txn_id, + const char *path, trail_t *trail); @@ -354,11 +362,12 @@ svn_error_t *svn_fs__dag_delete_if_mutab cannot be a slash-separated directory path. PARENT must not currently have an entry named NAME. Do any temporary allocation in TRAIL->pool. TXN_ID is the Subversion transaction under which this - occurs. */ + occurs. PATH is the initial committed path of this dag node.*/ svn_error_t *svn_fs__dag_make_dir (dag_node_t **child_p, dag_node_t *parent, const char *name, const char *txn_id, + const char *path, trail_t *trail); @@ -412,11 +421,13 @@ svn_error_t *svn_fs__dag_file_length (ap TRAIL->pool. The new file's contents are the empty string, and it has no properties. PARENT must be mutable. NAME must be a single path component; it cannot be a slash-separated directory path. - TXN_ID is the Subversion transaction under which this occurs. */ + TXN_ID is the Subversion transaction under which this occurs. + PATH is the committed tree path for this new file. */ svn_error_t *svn_fs__dag_make_file (dag_node_t **child_p, dag_node_t *parent, const char *name, const char *txn_id, + const char *path, trail_t *trail); @@ -439,7 +450,8 @@ svn_error_t *svn_fs__dag_copy (dag_node_ svn_boolean_t preserve_history, svn_revnum_t from_rev, const char *from_path, - const char *txn_id, + const char *txn_id, + const char *to_path, trail_t *trail); Index: libsvn_fs/fs.h =================================================================== --- libsvn_fs/fs.h (revision 4154) +++ libsvn_fs/fs.h (working copy) @@ -146,6 +146,9 @@ typedef struct -1 if not known (for backward compatibility). */ int predecessor_count; + /* path committed at */ + const char *committed_path; + /* representation key for this node's properties. may be NULL if there are no properties. */ const char *prop_key; Index: libsvn_fs/structure =================================================================== --- libsvn_fs/structure (revision 4154) +++ libsvn_fs/structure (working copy) @@ -781,8 +781,8 @@ Entries lists: Node revisions: NODE-REVISION ::= FILE | DIR ; - FILE ::= (HEADER PROP-KEY DATA-KEY [EDIT-DATA-KEY]) ; - DIR ::= (HEADER PROP-KEY ENTRIES-KEY) ; + FILE ::= (HEADER COMMIT-PATH PROP-KEY DATA-KEY [EDIT-DATA-KEY]) ; + DIR ::= (HEADER COMMIT-PATH PROP-KEY ENTRIES-KEY) ; HEADER ::= (KIND [PREDECESSOR-ID [PREDECESSOR-COUNT]]) ; KIND ::= "file" | "dir" ; @@ -790,6 +790,7 @@ Node revisions: PREDECESSOR-COUNT ::= number ; PROP-KEY ::= atom ; REP-KEY ::= atom ; + COMMIT-PATH ::= atom ; Representations: Index: libsvn_fs/dag.c =================================================================== --- libsvn_fs/dag.c (revision 4154) +++ libsvn_fs/dag.c (working copy) @@ -126,6 +126,7 @@ uncache_node_revision (void *baton) /* Dup NODEREV and all associated data into POOL */ static svn_fs__node_revision_t * copy_node_revision (svn_fs__node_revision_t *noderev, + const char *to_path, apr_pool_t *pool) { svn_fs__node_revision_t *nr = apr_pcalloc (pool, sizeof (*nr)); @@ -133,6 +134,10 @@ copy_node_revision (svn_fs__node_revisio if (noderev->predecessor_id) nr->predecessor_id = svn_fs__id_copy (noderev->predecessor_id, pool); nr->predecessor_count = noderev->predecessor_count; + if (to_path != NULL) + nr->committed_path = apr_pstrdup (pool, to_path); + else + nr->committed_path = apr_pstrdup (pool, noderev->committed_path); if (noderev->prop_key) nr->prop_key = apr_pstrdup (pool, noderev->prop_key); if (noderev->data_key) @@ -173,7 +178,7 @@ cache_node_revision (dag_node_t *node, { /* For immutable nodes, we can cache the contents permanently, but we need to copy them over into the node's own pool. */ - node->node_revision = copy_node_revision (noderev, node->pool); + node->node_revision = copy_node_revision (noderev, NULL, node->pool); } #endif /* 0 */ } @@ -310,6 +315,15 @@ svn_fs__dag_get_predecessor_count (int * return SVN_NO_ERROR; } +svn_error_t * +svn_fs__dag_get_committed_path (const char **path, dag_node_t *node, + trail_t *trail) +{ + svn_fs__node_revision_t *noderev; + SVN_ERR (get_node_revision (&noderev, node, trail)); + *path = noderev->committed_path; + return SVN_NO_ERROR; +} svn_error_t * svn_fs__dag_walk_predecessors (dag_node_t *node, @@ -364,6 +378,7 @@ txn_body_dag_init_fs (void *fs_baton, tr /* Create empty root directory with node revision 0.0.0. */ memset (&noderev, 0, sizeof (noderev)); noderev.kind = svn_node_dir; + noderev.committed_path = ""; SVN_ERR (svn_fs__put_node_revision (fs, root_id, &noderev, trail)); /* Create a new transaction (better have an id of "0") */ @@ -536,7 +551,7 @@ set_entry (dag_node_t *parent, if (! svn_fs__same_keys (rep_key, mutable_rep_key)) { svn_fs__node_revision_t *new_noderev = - copy_node_revision (parent_noderev, trail->pool); + copy_node_revision (parent_noderev, NULL, trail->pool); new_noderev->data_key = mutable_rep_key; SVN_ERR (set_node_revision (parent, new_noderev, trail)); } @@ -584,6 +599,7 @@ make_entry (dag_node_t **child_p, const char *name, svn_boolean_t is_dir, const char *txn_id, + const char *path, trail_t *trail) { const svn_fs_id_t *new_node_id; @@ -617,6 +633,8 @@ make_entry (dag_node_t **child_p, /* Create the new node's NODE-REVISION */ memset (&new_noderev, 0, sizeof (new_noderev)); new_noderev.kind = is_dir ? svn_node_dir : svn_node_file; + new_noderev.committed_path = apr_pstrdup(trail->pool, path); + SVN_ERR (svn_fs__create_node (&new_node_id, svn_fs__dag_get_fs (parent), &new_noderev, txn_id, trail)); @@ -809,6 +827,7 @@ svn_fs__dag_clone_child (dag_node_t **ch const char *name, const char *copy_id, const char *txn_id, + const char *path, trail_t *trail) { dag_node_t *cur_entry; /* parent's current entry named NAME */ @@ -848,6 +867,8 @@ svn_fs__dag_clone_child (dag_node_t **ch noderev->predecessor_id = svn_fs__id_copy (cur_entry->id, trail->pool); if (noderev->predecessor_count != -1) noderev->predecessor_count++; + if (path) + noderev->committed_path = apr_pstrdup (trail->pool, path); SVN_ERR (svn_fs__create_successor (&new_node_id, fs, cur_entry->id, noderev, copy_id, txn_id, trail)); @@ -979,7 +1000,7 @@ delete_entry (dag_node_t *parent, if (! svn_fs__same_keys (mutable_rep_key, rep_key)) { svn_fs__node_revision_t *new_noderev = - copy_node_revision (parent_noderev, trail->pool); + copy_node_revision (parent_noderev, NULL, trail->pool); new_noderev->data_key = mutable_rep_key; SVN_ERR (set_node_revision (parent, new_noderev, trail)); } @@ -1138,11 +1159,12 @@ svn_error_t * svn_fs__dag_make_file (dag_node_t **child_p, dag_node_t *parent, const char *name, - const char *txn_id, + const char *txn_id, + const char *path, trail_t *trail) { /* Call our little helper function */ - return make_entry (child_p, parent, name, FALSE, txn_id, trail); + return make_entry (child_p, parent, name, FALSE, txn_id, path, trail); } @@ -1150,11 +1172,12 @@ svn_error_t * svn_fs__dag_make_dir (dag_node_t **child_p, dag_node_t *parent, const char *name, - const char *txn_id, + const char *txn_id, + const char *path, trail_t *trail) { /* Call our little helper function */ - return make_entry (child_p, parent, name, TRUE, txn_id, trail); + return make_entry (child_p, parent, name, TRUE, txn_id, path, trail); } @@ -1421,7 +1444,8 @@ svn_fs__dag_copy (dag_node_t *to_node, svn_boolean_t preserve_history, svn_revnum_t from_rev, const char *from_path, - const char *txn_id, + const char *txn_id, + const char *to_path, trail_t *trail) { const svn_fs_id_t *id; @@ -1436,7 +1460,7 @@ svn_fs__dag_copy (dag_node_t *to_node, /* Make a copy of the original node revision. */ SVN_ERR (get_node_revision (&from_noderev, from_node, trail)); - to_noderev = copy_node_revision (from_noderev, trail->pool); + to_noderev = copy_node_revision (from_noderev, to_path, trail->pool); /* Reserve a copy ID for this new copy. */ SVN_ERR (svn_fs__reserve_copy_id (©_id, fs, trail)); Index: libsvn_fs/util/fs_skels.c =================================================================== --- libsvn_fs/util/fs_skels.c (revision 4154) +++ libsvn_fs/util/fs_skels.c (working copy) @@ -244,17 +244,17 @@ is_valid_node_revision_skel (skel_t *ske if (is_valid_node_revision_header_skel (header, &kind)) { if (svn_fs__matches_atom (kind, "dir") - && len == 3 + && len == 4 && header->next->is_atom && header->next->next->is_atom) return 1; if (svn_fs__matches_atom (kind, "file") - && ((len == 3) || (len == 4)) - && header->next->is_atom - && header->next->next->is_atom) + && ((len == 4) || (len == 5)) + && header->next->next->is_atom + && header->next->next->next->is_atom) { - if ((len == 4) && (! header->next->next->next->is_atom)) + if ((len == 5) && (! header->next->next->next->next->is_atom)) return 0; return 1; } @@ -591,24 +591,29 @@ svn_fs__parse_node_revision_skel (svn_fs else if (noderev->predecessor_id) noderev->predecessor_count = -1; } + + /* COMMITTED-PATH */ + noderev->committed_path = apr_pstrmemdup (pool, skel->children->next->data, + skel->children->next->len); /* PROP-KEY */ - if (skel->children->next->len) - noderev->prop_key = apr_pstrmemdup (pool, skel->children->next->data, - skel->children->next->len); - - /* DATA-KEY */ if (skel->children->next->next->len) - noderev->data_key = apr_pstrmemdup (pool, skel->children->next->next->data, + noderev->prop_key = apr_pstrmemdup (pool, skel->children->next->next->data, skel->children->next->next->len); + /* DATA-KEY */ + if (skel->children->next->next->next->len) + noderev->data_key = apr_pstrmemdup (pool, + skel->children->next->next->next->data, + skel->children->next->next->next->len); + /* EDIT-DATA-KEY (optional, files only) */ if ((noderev->kind == svn_node_file) - && skel->children->next->next->next - && skel->children->next->next->next->len) + && skel->children->next->next->next->next + && skel->children->next->next->next->next->len) noderev->edit_key - = apr_pstrmemdup (pool, skel->children->next->next->next->data, - skel->children->next->next->next->len); + = apr_pstrmemdup (pool, skel->children->next->next->next->next->data, + skel->children->next->next->next->next->len); /* Return the structure. */ *noderev_p = noderev; @@ -1061,6 +1066,12 @@ svn_fs__unparse_node_revision_skel (skel else svn_fs__prepend (svn_fs__mem_atom (NULL, 0, pool), skel); + /* COMMITTED-PATH */ + svn_fs__prepend (svn_fs__mem_atom (noderev->committed_path, + strlen(noderev->committed_path), + pool), + skel); + /* HEADER */ svn_fs__prepend (header_skel, skel); Index: tests/libsvn_fs/fs-test.c =================================================================== --- tests/libsvn_fs/fs-test.c (revision 4154) +++ tests/libsvn_fs/fs-test.c (working copy) @@ -5585,27 +5585,565 @@ 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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)); + if (revs->nelts != 0) + 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 +6187,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 };