diff options
Diffstat (limited to 'subversion/libsvn_fs_fs/tree.c')
-rw-r--r-- | subversion/libsvn_fs_fs/tree.c | 955 |
1 files changed, 757 insertions, 198 deletions
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c index b3943cc..acd1eb4 100644 --- a/subversion/libsvn_fs_fs/tree.c +++ b/subversion/libsvn_fs_fs/tree.c @@ -41,10 +41,10 @@ #include <apr_pools.h> #include <apr_hash.h> +#include "svn_hash.h" #include "svn_private_config.h" #include "svn_pools.h" #include "svn_error.h" -#include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_mergeinfo.h" #include "svn_fs.h" @@ -57,8 +57,10 @@ #include "tree.h" #include "fs_fs.h" #include "id.h" +#include "temp_serializer.h" #include "private/svn_mergeinfo_private.h" +#include "private/svn_subr_private.h" #include "private/svn_fs_util.h" #include "private/svn_fspath.h" #include "../libsvn_fs/fs-loader.h" @@ -114,14 +116,18 @@ typedef struct fs_rev_root_data_t typedef struct fs_txn_root_data_t { + const char *txn_id; + /* Cache of txn DAG nodes (without their nested noderevs, because - * it's mutable). */ + * it's mutable). Same keys/values as ffd->rev_node_cache. */ svn_cache__t *txn_node_cache; } fs_txn_root_data_t; /* Declared here to resolve the circular dependencies. */ -static svn_error_t * get_dag(dag_node_t **dag_node_p, svn_fs_root_t *root, - const char *path, apr_pool_t *pool); +static svn_error_t * get_dag(dag_node_t **dag_node_p, + svn_fs_root_t *root, + const char *path, + apr_pool_t *pool); static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev, dag_node_t *root_dir, @@ -135,6 +141,166 @@ static svn_error_t *make_txn_root(svn_fs_root_t **root_p, /*** Node Caching ***/ +/* 1st level cache */ + +/* An entry in the first-level cache. REVISION and PATH form the key that + will ultimately be matched. + */ +typedef struct cache_entry_t +{ + /* hash value derived from PATH, REVISION. + Used to short-circuit failed lookups. */ + apr_uint32_t hash_value; + + /* revision to which the NODE belongs */ + svn_revnum_t revision; + + /* path of the NODE */ + char *path; + + /* cached value of strlen(PATH). */ + apr_size_t path_len; + + /* the node allocated in the cache's pool. NULL for empty entries. */ + dag_node_t *node; +} cache_entry_t; + +/* Number of entries in the cache. Keep this low to keep pressure on the + CPU caches low as well. A binary value is most efficient. If we walk + a directory tree, we want enough entries to store nodes for all files + without overwriting the nodes for the parent folder. That way, there + will be no unnecessary misses (except for a few random ones caused by + hash collision). + + The actual number of instances may be higher but entries that got + overwritten are no longer visible. + */ +enum { BUCKET_COUNT = 256 }; + +/* The actual cache structure. All nodes will be allocated in POOL. + When the number of INSERTIONS (i.e. objects created form that pool) + exceeds a certain threshold, the pool will be cleared and the cache + with it. + */ +struct fs_fs_dag_cache_t +{ + /* fixed number of (possibly empty) cache entries */ + cache_entry_t buckets[BUCKET_COUNT]; + + /* pool used for all node allocation */ + apr_pool_t *pool; + + /* number of entries created from POOL since the last cleanup */ + apr_size_t insertions; + + /* Property lookups etc. have a very high locality (75% re-hit). + Thus, remember the last hit location for optimistic lookup. */ + apr_size_t last_hit; +}; + +fs_fs_dag_cache_t* +svn_fs_fs__create_dag_cache(apr_pool_t *pool) +{ + fs_fs_dag_cache_t *result = apr_pcalloc(pool, sizeof(*result)); + result->pool = svn_pool_create(pool); + + return result; +} + +/* Clears the CACHE at regular intervals (destroying all cached nodes) + */ +static void +auto_clear_dag_cache(fs_fs_dag_cache_t* cache) +{ + if (cache->insertions > BUCKET_COUNT) + { + svn_pool_clear(cache->pool); + + memset(cache->buckets, 0, sizeof(cache->buckets)); + cache->insertions = 0; + } +} + +/* For the given REVISION and PATH, return the respective entry in CACHE. + If the entry is empty, its NODE member will be NULL and the caller + may then set it to the corresponding DAG node allocated in CACHE->POOL. + */ +static cache_entry_t * +cache_lookup( fs_fs_dag_cache_t *cache + , svn_revnum_t revision + , const char *path) +{ + apr_size_t i, bucket_index; + apr_size_t path_len = strlen(path); + apr_uint32_t hash_value = (apr_uint32_t)revision; + +#if SVN_UNALIGNED_ACCESS_IS_OK + /* "randomizing" / distributing factor used in our hash function */ + const apr_uint32_t factor = 0xd1f3da69; +#endif + + /* optimistic lookup: hit the same bucket again? */ + cache_entry_t *result = &cache->buckets[cache->last_hit]; + if ( (result->revision == revision) + && (result->path_len == path_len) + && !memcmp(result->path, path, path_len)) + { + return result; + } + + /* need to do a full lookup. Calculate the hash value + (HASH_VALUE has been initialized to REVISION). */ + i = 0; +#if SVN_UNALIGNED_ACCESS_IS_OK + /* We relax the dependency chain between iterations by processing + two chunks from the input per hash_value self-multiplication. + The HASH_VALUE update latency is now 1 MUL latency + 1 ADD latency + per 2 chunks instead of 1 chunk. + */ + for (; i + 8 <= path_len; i += 8) + hash_value = hash_value * factor * factor + + ( *(const apr_uint32_t*)(path + i) * factor + + *(const apr_uint32_t*)(path + i + 4)); +#endif + + for (; i < path_len; ++i) + /* Help GCC to minimize the HASH_VALUE update latency by splitting the + MUL 33 of the naive implementation: h = h * 33 + path[i]. This + shortens the dependency chain from 1 shift + 2 ADDs to 1 shift + 1 ADD. + */ + hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]); + + bucket_index = hash_value + (hash_value >> 16); + bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT; + + /* access the corresponding bucket and remember its location */ + result = &cache->buckets[bucket_index]; + cache->last_hit = bucket_index; + + /* if it is *NOT* a match, clear the bucket, expect the caller to fill + in the node and count it as an insertion */ + if ( (result->hash_value != hash_value) + || (result->revision != revision) + || (result->path_len != path_len) + || memcmp(result->path, path, path_len)) + { + result->hash_value = hash_value; + result->revision = revision; + if (result->path_len < path_len) + result->path = apr_palloc(cache->pool, path_len + 1); + result->path_len = path_len; + memcpy(result->path, path, path_len + 1); + + result->node = NULL; + + cache->insertions++; + } + + return result; +} + +/* 2nd level cache */ + /* Find and return the DAG node cache for ROOT and the key that should be used for PATH. */ static void @@ -154,13 +320,13 @@ locate_cache(svn_cache__t **cache, { fs_fs_data_t *ffd = root->fs->fsap_data; if (cache) *cache = ffd->rev_node_cache; - if (key && path) *key = apr_psprintf(pool, "%ld%s", - root->rev, path); + if (key && path) *key + = svn_fs_fs__combine_number_and_string(root->rev, path, pool); } } -/* Return NODE for PATH from ROOT's node cache, or NULL if the node - isn't cached; the node is copied into POOL. */ +/* Return NODE_P for PATH from ROOT's node cache, or NULL if the node + isn't cached; read it from the FS. *NODE_P is allocated in POOL. */ static svn_error_t * dag_node_cache_get(dag_node_t **node_p, svn_fs_root_t *root, @@ -168,24 +334,59 @@ dag_node_cache_get(dag_node_t **node_p, apr_pool_t *pool) { svn_boolean_t found; - dag_node_t *node; + dag_node_t *node = NULL; svn_cache__t *cache; const char *key; SVN_ERR_ASSERT(*path == '/'); - locate_cache(&cache, &key, root, path, pool); - - SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool)); - if (found && node) + if (!root->is_txn_root) { - /* Patch up the FS, since this might have come from an old FS - * object. */ - svn_fs_fs__dag_set_fs(node, root->fs); - *node_p = node; + /* immutable DAG node. use the global caches for it */ + + fs_fs_data_t *ffd = root->fs->fsap_data; + cache_entry_t *bucket; + + auto_clear_dag_cache(ffd->dag_node_cache); + bucket = cache_lookup(ffd->dag_node_cache, root->rev, path); + if (bucket->node == NULL) + { + locate_cache(&cache, &key, root, path, pool); + SVN_ERR(svn_cache__get((void **)&node, &found, cache, key, pool)); + if (found && node) + { + /* Patch up the FS, since this might have come from an old FS + * object. */ + svn_fs_fs__dag_set_fs(node, root->fs); + + /* Retain the DAG node in L1 cache. */ + bucket->node = svn_fs_fs__dag_dup(node, + ffd->dag_node_cache->pool); + } + } + else + { + /* Copy the node from L1 cache into the passed-in POOL. */ + node = svn_fs_fs__dag_dup(bucket->node, pool); + } } else - *node_p = NULL; + { + /* DAG is mutable / may become invalid. Use the TXN-local cache */ + + locate_cache(&cache, &key, root, path, pool); + + SVN_ERR(svn_cache__get((void **) &node, &found, cache, key, pool)); + if (found && node) + { + /* Patch up the FS, since this might have come from an old FS + * object. */ + svn_fs_fs__dag_set_fs(node, root->fs); + } + } + + *node_p = node; + return SVN_NO_ERROR; } @@ -202,6 +403,9 @@ dag_node_cache_set(svn_fs_root_t *root, SVN_ERR_ASSERT(*path == '/'); + /* Do *not* attempt to dup and put the node into L1. + * dup() is twice as expensive as an L2 lookup (which will set also L1). + */ locate_cache(&cache, &key, root, path, pool); return svn_cache__set(cache, key, node, pool); @@ -228,7 +432,7 @@ find_descendents_in_cache(void *baton, struct fdic_baton *b = baton; const char *item_path = key; - if (svn_dirent_is_ancestor(b->path, item_path)) + if (svn_fspath__skip_ancestor(b->path, item_path)) APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path); return SVN_NO_ERROR; @@ -287,12 +491,10 @@ svn_fs_fs__txn_root(svn_fs_root_t **root_p, SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool)); if (txnprops) { - if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, - APR_HASH_KEY_STRING)) + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD)) flags |= SVN_FS_TXN_CHECK_OOD; - if (apr_hash_get(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, - APR_HASH_KEY_STRING)) + if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS)) flags |= SVN_FS_TXN_CHECK_LOCKS; } @@ -358,7 +560,7 @@ mutable_root_node(dag_node_t **node_p, return svn_fs_fs__dag_clone_root(node_p, root->fs, root->txn, pool); else /* If it's not a transaction root, we can't change its contents. */ - return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path, pool); + return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path); } @@ -502,7 +704,7 @@ get_copy_inheritance(copy_id_inherit_t *inherit_p, or if it is a branch point that we are accessing via its original copy destination path. */ SVN_ERR(svn_fs_fs__dag_get_copyroot(©root_rev, ©root_path, - child->node,pool)); + child->node)); SVN_ERR(svn_fs_fs__revision_root(©root_root, fs, copyroot_rev, pool)); SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool)); copyroot_id = svn_fs_fs__dag_get_id(copyroot_node); @@ -552,20 +754,28 @@ 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, + + /* When this flag is set, don't bother to lookup the DAG node in + our caches because we already tried this. Ignoring this flag + has no functional impact. */ + open_path_uncached = 2, + /* The caller does not care about the parent node chain but only + the final DAG node. */ + open_path_node_only = 4 } open_path_flags_t; /* Open the node identified by PATH in ROOT, allocating in POOL. Set *PARENT_PATH_P to a path from the node up to ROOT. The resulting **PARENT_PATH_P value is guaranteed to contain at least one - *element, for the root directory. + *element, for the root directory. PATH must be in canonical form. If resulting *PARENT_PATH_P will eventually be made mutable and modified, or if copy ID inheritance information is otherwise needed, TXN_ID should be the ID of the mutability transaction. If - TXN_ID is NULL, no copy ID in heritance information will be + TXN_ID is NULL, no copy ID inheritance information will be calculated for the *PARENT_PATH_P chain. If FLAGS & open_path_last_optional is zero, return the error @@ -576,6 +786,11 @@ typedef enum open_path_flags_t { callers that create new nodes --- we find the parent directory for them, and tell them whether the entry exists already. + The remaining bits in FLAGS are hints that allow this function + to take shortcuts based on knowledge that the caller provides, + such as the caller is not actually being interested in PARENT_PATH_P, + but only in (*PARENT_PATH_P)->NODE. + NOTE: Public interfaces which only *read* from the filesystem should not call this function directly, but should instead use get_dag(). @@ -589,20 +804,44 @@ open_path(parent_path_t **parent_path_p, apr_pool_t *pool) { svn_fs_t *fs = root->fs; - dag_node_t *here; /* The directory we're currently looking at. */ - parent_path_t *parent_path; /* The path from HERE up to the root. */ + dag_node_t *here = NULL; /* 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, pool); + + /* ensure a canonical path representation */ const char *path_so_far = "/"; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* callers often traverse the tree in some path-based order. That means + a sibling of PATH has been presently accessed. Try to start the lookup + directly at the parent node, if the caller did not requested the full + parent chain. */ + const char *directory; + assert(svn_fs__is_canonical_abspath(path)); + if (flags & open_path_node_only) + { + directory = svn_dirent_dirname(path, pool); + if (directory[1] != 0) /* root nodes are covered anyway */ + SVN_ERR(dag_node_cache_get(&here, root, directory, pool)); + } + + /* did the shortcut work? */ + if (here) + { + path_so_far = directory; + rest = path + strlen(directory) + 1; + } + else + { + /* Make a parent_path item for the root node, using its own current + copy id. */ + SVN_ERR(root_node(&here, root, pool)); + rest = path + 1; /* skip the leading '/', it saves in iteration */ + } - /* Make a parent_path item for the root node, using its own current - copy id. */ - SVN_ERR(root_node(&here, root, pool)); parent_path = make_parent_path(here, 0, 0, pool); parent_path->copy_inherit = copy_id_inherit_self; - rest = canon_path + 1; /* skip the leading '/', it saves in iteration */ - /* Whenever we are at the top of this loop: - HERE is our current directory, - ID is the node revision ID of HERE, @@ -614,6 +853,8 @@ open_path(parent_path_t **parent_path_p, char *entry; dag_node_t *child; + svn_pool_clear(iterpool); + /* Parse out the next entry from the path. */ entry = svn_fs__next_entry_name(&next, rest, pool); @@ -633,16 +874,20 @@ open_path(parent_path_t **parent_path_p, copy_id_inherit_t inherit; const char *copy_path = NULL; svn_error_t *err = SVN_NO_ERROR; - dag_node_t *cached_node; + dag_node_t *cached_node = NULL; /* If we found a directory entry, follow it. First, we check our node cache, and, failing that, we hit the DAG - layer. */ - SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool)); + layer. Don't bother to contact the cache for the last + element if we already know the lookup to fail for the + complete path. */ + if (next || !(flags & open_path_uncached)) + SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool)); + if (cached_node) child = cached_node; else - err = svn_fs_fs__dag_open(&child, here, entry, pool); + err = svn_fs_fs__dag_open(&child, here, entry, pool, iterpool); /* "file not found" requires special handling. */ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) @@ -671,19 +916,27 @@ open_path(parent_path_t **parent_path_p, /* Other errors we return normally. */ SVN_ERR(err); - /* Now, make a parent_path item for CHILD. */ - parent_path = make_parent_path(child, entry, parent_path, pool); - if (txn_id) + if (flags & open_path_node_only) { - SVN_ERR(get_copy_inheritance(&inherit, ©_path, - fs, parent_path, txn_id, pool)); - parent_path->copy_inherit = inherit; - parent_path->copy_src_path = apr_pstrdup(pool, copy_path); + /* Shortcut: the caller only wan'ts the final DAG node. */ + parent_path->node = child; + } + else + { + /* Now, make a parent_path item for CHILD. */ + parent_path = make_parent_path(child, entry, parent_path, pool); + if (txn_id) + { + SVN_ERR(get_copy_inheritance(&inherit, ©_path, fs, + parent_path, txn_id, iterpool)); + parent_path->copy_inherit = inherit; + parent_path->copy_src_path = apr_pstrdup(pool, copy_path); + } } /* Cache the node we found (if it wasn't already cached). */ if (! cached_node) - SVN_ERR(dag_node_cache_set(root, path_so_far, child, pool)); + SVN_ERR(dag_node_cache_set(root, path_so_far, child, iterpool)); } /* Are we finished traversing the path? */ @@ -692,13 +945,14 @@ open_path(parent_path_t **parent_path_p, /* The path isn't finished yet; we'd better be in a directory. */ if (svn_fs_fs__dag_node_kind(child) != svn_node_dir) - SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far, pool), - apr_psprintf(pool, _("Failure opening '%s'"), path)); + SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far), + apr_psprintf(iterpool, _("Failure opening '%s'"), path)); rest = next; here = child; } + svn_pool_destroy(iterpool); *parent_path_p = parent_path; return SVN_NO_ERROR; } @@ -763,7 +1017,7 @@ make_path_mutable(svn_fs_root_t *root, /* Determine what copyroot our new child node should use. */ SVN_ERR(svn_fs_fs__dag_get_copyroot(©root_rev, ©root_path, - parent_path->node, pool)); + parent_path->node)); SVN_ERR(svn_fs_fs__revision_root(©root_root, root->fs, copyroot_rev, pool)); SVN_ERR(get_dag(©root_node, copyroot_root, copyroot_path, pool)); @@ -811,21 +1065,35 @@ get_dag(dag_node_t **dag_node_p, apr_pool_t *pool) { parent_path_t *parent_path; - dag_node_t *node; + dag_node_t *node = NULL; - /* Canonicalize the input PATH. */ - path = svn_fs__canonicalize_abspath(path, pool); + /* First we look for the DAG in our cache + (if the path may be canonical). */ + if (*path == '/') + SVN_ERR(dag_node_cache_get(&node, root, path, pool)); - /* First we look for the DAG in our cache. */ - SVN_ERR(dag_node_cache_get(&node, root, path, pool)); if (! node) { - /* 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, NULL, pool)); - node = parent_path->node; + /* Canonicalize the input PATH. */ + if (! svn_fs__is_canonical_abspath(path)) + { + path = svn_fs__canonicalize_abspath(path, pool); + + /* Try again with the corrected path. */ + SVN_ERR(dag_node_cache_get(&node, root, path, pool)); + } - /* No need to cache our find -- open_path() will do that for us. */ + if (! node) + { + /* 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, + open_path_uncached | open_path_node_only, + NULL, pool)); + node = parent_path->node; + + /* No need to cache our find -- open_path() will do that for us. */ + } } *dag_node_p = node; @@ -988,7 +1256,7 @@ fs_node_prop(svn_string_t **value_p, SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, node, pool)); *value_p = NULL; if (proplist) - *value_p = apr_hash_get(proplist, propname, APR_HASH_KEY_STRING); + *value_p = svn_hash_gets(proplist, propname); return SVN_NO_ERROR; } @@ -1048,6 +1316,7 @@ fs_change_node_prop(svn_fs_root_t *root, return SVN_FS__NOT_TXN(root); txn_id = root->txn; + path = svn_fs__canonicalize_abspath(path, pool); SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool)); /* Check (non-recursively) to see if path is locked; if so, check @@ -1072,8 +1341,7 @@ fs_change_node_prop(svn_fs_root_t *root, { apr_int64_t increment = 0; svn_boolean_t had_mergeinfo; - SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&had_mergeinfo, parent_path->node, - pool)); + SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&had_mergeinfo, parent_path->node)); if (value && !had_mergeinfo) increment = 1; @@ -1089,7 +1357,7 @@ fs_change_node_prop(svn_fs_root_t *root, } /* Set the property. */ - apr_hash_set(proplist, name, APR_HASH_KEY_STRING, value); + svn_hash_sets(proplist, name, value); /* Overwrite the node's proplist. */ SVN_ERR(svn_fs_fs__dag_set_proplist(parent_path->node, proplist, @@ -1127,7 +1395,7 @@ fs_props_changed(svn_boolean_t *changed_p, SVN_ERR(get_dag(&node1, root1, path1, pool)); SVN_ERR(get_dag(&node2, root2, path2, pool)); return svn_fs_fs__dag_things_different(changed_p, NULL, - node1, node2, pool); + node1, node2); } @@ -1138,7 +1406,7 @@ fs_props_changed(svn_boolean_t *changed_p, static svn_error_t * get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool) { - return get_dag(node, root, "", pool); + return get_dag(node, root, "/", pool); } @@ -1194,6 +1462,7 @@ merge(svn_stringbuf_t *conflict_p, svn_fs_t *fs; apr_pool_t *iterpool; apr_int64_t mergeinfo_increment = 0; + svn_boolean_t fs_supports_mergeinfo; /* Make sure everyone comes from the same filesystem. */ fs = svn_fs_fs__dag_get_fs(ancestor); @@ -1337,9 +1606,11 @@ merge(svn_stringbuf_t *conflict_p, /* ### todo: it would be more efficient to simply check for a NULL entries hash where necessary below than to allocate an empty hash here, but another day, another day... */ - SVN_ERR(svn_fs_fs__dag_dir_entries(&s_entries, source, pool, pool)); - SVN_ERR(svn_fs_fs__dag_dir_entries(&t_entries, target, pool, pool)); - SVN_ERR(svn_fs_fs__dag_dir_entries(&a_entries, ancestor, pool, pool)); + SVN_ERR(svn_fs_fs__dag_dir_entries(&s_entries, source, pool)); + SVN_ERR(svn_fs_fs__dag_dir_entries(&t_entries, target, pool)); + SVN_ERR(svn_fs_fs__dag_dir_entries(&a_entries, ancestor, pool)); + + fs_supports_mergeinfo = svn_fs_fs__fs_supports_mergeinfo(fs); /* for each entry E in a_entries... */ iterpool = svn_pool_create(pool); @@ -1372,12 +1643,11 @@ merge(svn_stringbuf_t *conflict_p, dag_node_t *t_ent_node; SVN_ERR(svn_fs_fs__dag_get_node(&t_ent_node, fs, t_entry->id, iterpool)); - if (svn_fs_fs__fs_supports_mergeinfo(fs)) + if (fs_supports_mergeinfo) { apr_int64_t mergeinfo_start; SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_start, - t_ent_node, - iterpool)); + t_ent_node)); mergeinfo_increment -= mergeinfo_start; } @@ -1387,12 +1657,11 @@ merge(svn_stringbuf_t *conflict_p, SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node, fs, s_entry->id, iterpool)); - if (svn_fs_fs__fs_supports_mergeinfo(fs)) + if (fs_supports_mergeinfo) { apr_int64_t mergeinfo_end; SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_end, - s_ent_node, - iterpool)); + s_ent_node)); mergeinfo_increment += mergeinfo_end; } @@ -1465,7 +1734,7 @@ merge(svn_stringbuf_t *conflict_p, txn_id, &sub_mergeinfo_increment, iterpool)); - if (svn_fs_fs__fs_supports_mergeinfo(fs)) + if (fs_supports_mergeinfo) mergeinfo_increment += sub_mergeinfo_increment; } @@ -1501,12 +1770,11 @@ merge(svn_stringbuf_t *conflict_p, SVN_ERR(svn_fs_fs__dag_get_node(&s_ent_node, fs, s_entry->id, iterpool)); - if (svn_fs_fs__fs_supports_mergeinfo(fs)) + if (fs_supports_mergeinfo) { apr_int64_t mergeinfo_s; SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_s, - s_ent_node, - iterpool)); + s_ent_node)); mergeinfo_increment += mergeinfo_s; } @@ -1518,7 +1786,7 @@ merge(svn_stringbuf_t *conflict_p, SVN_ERR(svn_fs_fs__dag_update_ancestry(target, source, pool)); - if (svn_fs_fs__fs_supports_mergeinfo(fs)) + if (fs_supports_mergeinfo) SVN_ERR(svn_fs_fs__dag_increment_mergeinfo_count(target, mergeinfo_increment, pool)); @@ -1621,7 +1889,7 @@ svn_fs_fs__commit_txn(const char **conflict_p, */ svn_error_t *err = SVN_NO_ERROR; - svn_stringbuf_t *conflict = svn_stringbuf_create("", pool); + svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); svn_fs_t *fs = txn->fs; /* Limit memory usage when the repository has a high commit rate and @@ -1729,7 +1997,7 @@ fs_merge(const char **conflict_p, dag_node_t *source, *ancestor; svn_fs_txn_t *txn; svn_error_t *err; - svn_stringbuf_t *conflict = svn_stringbuf_create("", pool); + svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool); if (! target_root->is_txn_root) return SVN_FS__NOT_TXN(target_root); @@ -1803,9 +2071,23 @@ fs_dir_entries(apr_hash_t **table_p, /* Get the entries for this path in the caller's pool. */ SVN_ERR(get_dag(&node, root, path, pool)); - return svn_fs_fs__dag_dir_entries(table_p, node, pool, pool); + return svn_fs_fs__dag_dir_entries(table_p, node, pool); } +/* Raise an error if PATH contains a newline because FSFS cannot handle + * such paths. See issue #4340. */ +static svn_error_t * +check_newline(const char *path, apr_pool_t *pool) +{ + char *c = strchr(path, '\n'); + + if (c) + return svn_error_createf(SVN_ERR_FS_PATH_SYNTAX, NULL, + _("Invalid control character '0x%02x' in path '%s'"), + (unsigned char)*c, svn_path_illegal_path_escape(path, pool)); + + return SVN_NO_ERROR; +} /* Create a new directory named PATH in ROOT. The new directory has no entries, and no properties. ROOT must be the root of a @@ -1820,6 +2102,9 @@ fs_make_dir(svn_fs_root_t *root, dag_node_t *sub_dir; const char *txn_id = root->txn; + SVN_ERR(check_newline(path, pool)); + + path = svn_fs__canonicalize_abspath(path, pool); SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, txn_id, pool)); @@ -1833,7 +2118,7 @@ fs_make_dir(svn_fs_root_t *root, /* If there's already a sub-directory by that name, complain. This also catches the case of trying to make a subdirectory named `/'. */ if (parent_path->node) - return SVN_FS__ALREADY_EXISTS(root, path, pool); + return SVN_FS__ALREADY_EXISTS(root, path); /* Create the subdirectory. */ SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool)); @@ -1865,12 +2150,13 @@ fs_delete_node(svn_fs_root_t *root, { parent_path_t *parent_path; const char *txn_id = root->txn; - apr_int64_t mergeinfo_count; + apr_int64_t mergeinfo_count = 0; svn_node_kind_t kind; if (! root->is_txn_root) return SVN_FS__NOT_TXN(root); + path = svn_fs__canonicalize_abspath(path, pool); SVN_ERR(open_path(&parent_path, root, path, 0, txn_id, pool)); kind = svn_fs_fs__dag_node_kind(parent_path->node); @@ -1889,8 +2175,7 @@ fs_delete_node(svn_fs_root_t *root, SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool)); if (svn_fs_fs__fs_supports_mergeinfo(root->fs)) SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_count, - parent_path->node, - pool)); + parent_path->node)); SVN_ERR(svn_fs_fs__dag_delete(parent_path->parent->node, parent_path->entry, txn_id, pool)); @@ -1900,7 +2185,7 @@ fs_delete_node(svn_fs_root_t *root, pool)); /* Update mergeinfo counts for parents */ - if (svn_fs_fs__fs_supports_mergeinfo(root->fs) && mergeinfo_count > 0) + if (mergeinfo_count > 0) SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent, -mergeinfo_count, pool)); @@ -1922,17 +2207,7 @@ fs_same_p(svn_boolean_t *same_p, svn_fs_t *fs2, apr_pool_t *pool) { - const char *uuid1; - const char *uuid2; - - /* Random thought: if fetching UUIDs to compare filesystems is too - expensive, one solution would be to cache the UUID in each fs - object (copying the UUID into fs->pool, of course). */ - - SVN_ERR(fs1->vtable->get_uuid(fs1, &uuid1, pool)); - SVN_ERR(fs2->vtable->get_uuid(fs2, &uuid2, pool)); - - *same_p = ! strcmp(uuid1, uuid2); + *same_p = ! strcmp(fs1->uuid, fs2->uuid); return SVN_NO_ERROR; } @@ -2005,8 +2280,7 @@ copy_helper(svn_fs_root_t *from_root, kind = svn_fs_path_change_replace; if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs)) SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_start, - to_parent_path->node, - pool)); + to_parent_path->node)); } else { @@ -2016,7 +2290,7 @@ copy_helper(svn_fs_root_t *from_root, if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs)) SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_end, - from_node, pool)); + from_node)); /* Make sure the target node's parents are mutable. */ SVN_ERR(make_path_mutable(to_root, to_parent_path->parent, @@ -2082,7 +2356,14 @@ fs_copy(svn_fs_root_t *from_root, const char *to_path, apr_pool_t *pool) { - return svn_error_trace(copy_helper(from_root, from_path, to_root, to_path, + SVN_ERR(check_newline(to_path, pool)); + + return svn_error_trace(copy_helper(from_root, + svn_fs__canonicalize_abspath(from_path, + pool), + to_root, + svn_fs__canonicalize_abspath(to_path, + pool), TRUE, pool)); } @@ -2099,6 +2380,7 @@ fs_revision_link(svn_fs_root_t *from_root, if (! to_root->is_txn_root) return SVN_FS__NOT_TXN(to_root); + path = svn_fs__canonicalize_abspath(path, pool); return svn_error_trace(copy_helper(from_root, path, to_root, path, FALSE, pool)); } @@ -2117,13 +2399,13 @@ fs_copied_from(svn_revnum_t *rev_p, dag_node_t *node; const char *copyfrom_path, *copyfrom_str = NULL; svn_revnum_t copyfrom_rev; - char *str, *last_str, *buf; + char *str, *buf; /* Check to see if there is a cached version of this copyfrom entry. */ if (! root->is_txn_root) { fs_rev_root_data_t *frd = root->fsap_data; - copyfrom_str = apr_hash_get(frd->copyfrom_cache, path, APR_HASH_KEY_STRING); + copyfrom_str = svn_hash_gets(frd->copyfrom_cache, path); } if (copyfrom_str) @@ -2139,9 +2421,9 @@ fs_copied_from(svn_revnum_t *rev_p, { /* Parse the copyfrom string for our cached entry. */ buf = apr_pstrdup(pool, copyfrom_str); - str = apr_strtok(buf, " ", &last_str); + str = svn_cstring_tokenize(" ", &buf); copyfrom_rev = SVN_STR_TO_REV(str); - copyfrom_path = last_str; + copyfrom_path = buf; } } else @@ -2149,8 +2431,8 @@ fs_copied_from(svn_revnum_t *rev_p, /* There is no cached entry, look it up the old-fashioned way. */ SVN_ERR(get_dag(&node, root, path, pool)); - SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(©from_rev, node, pool)); - SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©from_path, node, pool)); + SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(©from_rev, node)); + SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©from_path, node)); } *rev_p = copyfrom_rev; @@ -2174,13 +2456,16 @@ fs_make_file(svn_fs_root_t *root, dag_node_t *child; const char *txn_id = root->txn; + SVN_ERR(check_newline(path, pool)); + + path = svn_fs__canonicalize_abspath(path, pool); SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional, - txn_id, pool)); + txn_id, pool)); /* If there's already a file by that name, complain. This also catches the case of trying to make a file named `/'. */ if (parent_path->node) - return SVN_FS__ALREADY_EXISTS(root, path, pool); + return SVN_FS__ALREADY_EXISTS(root, path); /* Check (non-recursively) to see if path is locked; if so, check that we can use it. */ @@ -2270,6 +2555,25 @@ fs_file_contents(svn_stream_t **contents, /* --- End machinery for svn_fs_file_contents() --- */ +/* --- Machinery for svn_fs_try_process_file_contents() --- */ + +static svn_error_t * +fs_try_process_file_contents(svn_boolean_t *success, + svn_fs_root_t *root, + const char *path, + svn_fs_process_contents_func_t processor, + void* baton, + apr_pool_t *pool) +{ + dag_node_t *node; + SVN_ERR(get_dag(&node, root, path, pool)); + + return svn_fs_fs__dag_try_process_file_contents(success, node, + processor, baton, pool); +} + +/* --- End machinery for svn_fs_try_process_file_contents() --- */ + /* --- Machinery for svn_fs_apply_textdelta() --- */ @@ -2361,7 +2665,7 @@ window_consumer(svn_txdelta_window_t *window, void *baton) SVN_ERR(svn_stream_write(tb->target_stream, tb->target_string->data, &len)); - svn_stringbuf_set(tb->target_string, ""); + svn_stringbuf_setempty(tb->target_string); } /* Is the window NULL? If so, we're done. */ @@ -2428,7 +2732,7 @@ apply_textdelta(void *baton, apr_pool_t *pool) /* Make a writable "string" stream which writes data to tb->target_string. */ - tb->target_string = svn_stringbuf_create("", tb->pool); + tb->target_string = svn_stringbuf_create_empty(tb->pool); tb->string_stream = svn_stream_create(tb, tb->pool); svn_stream_set_write(tb->string_stream, write_to_string); @@ -2464,19 +2768,10 @@ fs_apply_textdelta(svn_txdelta_window_handler_t *contents_p, txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); tb->root = root; - tb->path = path; + tb->path = svn_fs__canonicalize_abspath(path, pool); tb->pool = pool; - - if (base_checksum) - tb->base_checksum = svn_checksum_dup(base_checksum, pool); - else - tb->base_checksum = NULL; - - if (result_checksum) - tb->result_checksum = svn_checksum_dup(result_checksum, pool); - else - tb->result_checksum = NULL; - + tb->base_checksum = svn_checksum_dup(base_checksum, pool); + tb->result_checksum = svn_checksum_dup(result_checksum, pool); SVN_ERR(apply_textdelta(tb, pool)); @@ -2606,13 +2901,9 @@ fs_apply_text(svn_stream_t **contents_p, struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb)); tb->root = root; - tb->path = path; + tb->path = svn_fs__canonicalize_abspath(path, pool); tb->pool = pool; - - if (result_checksum) - tb->result_checksum = svn_checksum_dup(result_checksum, pool); - else - tb->result_checksum = NULL; + tb->result_checksum = svn_checksum_dup(result_checksum, pool); SVN_ERR(apply_text(tb, pool)); @@ -2660,7 +2951,7 @@ fs_contents_changed(svn_boolean_t *changed_p, SVN_ERR(get_dag(&node1, root1, path1, pool)); SVN_ERR(get_dag(&node2, root2, path2, pool)); return svn_fs_fs__dag_things_different(NULL, changed_p, - node1, node2, pool); + node1, node2); } @@ -2780,8 +3071,10 @@ find_youngest_copyroot(svn_revnum_t *rev_p, parent_path_t *parent_path, apr_pool_t *pool) { - svn_revnum_t rev_mine, rev_parent = -1; - const char *path_mine, *path_parent; + svn_revnum_t rev_mine; + svn_revnum_t rev_parent = SVN_INVALID_REVNUM; + const char *path_mine; + const char *path_parent = NULL; /* First find our parent's youngest copyroot. */ if (parent_path->parent) @@ -2790,7 +3083,7 @@ find_youngest_copyroot(svn_revnum_t *rev_p, /* Find our copyroot. */ SVN_ERR(svn_fs_fs__dag_get_copyroot(&rev_mine, &path_mine, - parent_path->node, pool)); + parent_path->node)); /* If a parent and child were copied to in the same revision, prefer the child copy target, since it is the copy relevant to the @@ -2828,6 +3121,7 @@ static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p, *root_p = NULL; *path_p = NULL; + path = svn_fs__canonicalize_abspath(path, pool); SVN_ERR(open_path(&parent_path, root, path, 0, NULL, pool)); /* Find the youngest copyroot in the path of this node-rev, which @@ -2846,7 +3140,7 @@ static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p, if (kind == svn_node_none) return SVN_NO_ERROR; SVN_ERR(open_path(©_dst_parent_path, copy_dst_root, path, - 0, NULL, pool)); + open_path_node_only, NULL, pool)); copy_dst_node = copy_dst_parent_path->node; if (! svn_fs_fs__id_check_related(svn_fs_fs__dag_get_id(copy_dst_node), svn_fs_fs__dag_get_id(parent_path->node))) @@ -2870,7 +3164,7 @@ static svn_error_t *fs_closest_copy(svn_fs_root_t **root_p, if (created_rev == copy_dst_rev) { const svn_fs_id_t *pred; - SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred, copy_dst_node, pool)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred, copy_dst_node)); if (! pred) return SVN_NO_ERROR; } @@ -2898,7 +3192,7 @@ prev_location(const char **prev_path, const char *path, apr_pool_t *pool) { - const char *copy_path, *copy_src_path, *remainder = ""; + const char *copy_path, *copy_src_path, *remainder_path; svn_fs_root_t *copy_root; svn_revnum_t copy_src_rev; @@ -2927,9 +3221,8 @@ prev_location(const char **prev_path, */ SVN_ERR(fs_copied_from(©_src_rev, ©_src_path, copy_root, copy_path, pool)); - if (strcmp(copy_path, path) != 0) - remainder = svn_relpath__is_child(copy_path, path, pool); - *prev_path = svn_fspath__join(copy_src_path, remainder, pool); + remainder_path = svn_fspath__skip_ancestor(copy_path, path); + *prev_path = svn_fspath__join(copy_src_path, remainder_path, pool); *prev_rev = copy_src_rev; return SVN_NO_ERROR; } @@ -2967,6 +3260,14 @@ fs_node_origin_rev(svn_revnum_t *revision, return SVN_NO_ERROR; } + /* The root node always has ID 0, created in revision 0 and will never + use the new-style ID format. */ + if (strcmp(node_id, "0") == 0) + { + *revision = 0; + return SVN_NO_ERROR; + } + /* OK, it's an old-style ID? Maybe it's cached. */ SVN_ERR(svn_fs_fs__get_node_origin(&cached_origin_id, fs, @@ -3021,7 +3322,7 @@ fs_node_origin_rev(svn_revnum_t *revision, /* Walk the predecessor links back to origin. */ SVN_ERR(svn_fs_fs__node_id(&pred_id, curroot, lastpath->data, predidpool)); - while (pred_id) + do { svn_pool_clear(subpool); SVN_ERR(svn_fs_fs__dag_get_node(&node, fs, pred_id, subpool)); @@ -3032,9 +3333,10 @@ fs_node_origin_rev(svn_revnum_t *revision, value cached in the node (which is allocated in SUBPOOL... maybe). */ svn_pool_clear(predidpool); - SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node, subpool)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node)); pred_id = pred_id ? svn_fs_fs__id_copy(pred_id, predidpool) : NULL; } + while (pred_id); /* When we get here, NODE should be the first node-revision in our chain. */ @@ -3133,7 +3435,7 @@ history_prev(void *baton, apr_pool_t *pool) no predecessor, in which case we're all done!). */ const svn_fs_id_t *pred_id; - SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node, pool)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node)); if (! pred_id) return SVN_NO_ERROR; @@ -3157,7 +3459,7 @@ history_prev(void *baton, apr_pool_t *pool) if (copyroot_rev > commit_rev) { - const char *remainder; + const char *remainder_path; const char *copy_dst, *copy_src; svn_fs_root_t *copyroot_root; @@ -3174,21 +3476,18 @@ history_prev(void *baton, apr_pool_t *pool) the copy source. Finally, if our current path doesn't meet one of these other criteria ... ### for now just fallback to the old copy hunt algorithm. */ - if (strcmp(path, copy_dst) == 0) - remainder = ""; - else - remainder = svn_relpath__is_child(copy_dst, path, pool); + remainder_path = svn_fspath__skip_ancestor(copy_dst, path); - if (remainder) + if (remainder_path) { /* If we get here, then our current path is the destination of, or the child of the destination of, a copy. Fill in the return values and get outta here. */ - SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&src_rev, node, pool)); - SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©_src, node, pool)); + SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&src_rev, node)); + SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(©_src, node)); dst_rev = copyroot_rev; - src_path = svn_fspath__join(copy_src, remainder, pool); + src_path = svn_fspath__join(copy_src, remainder_path, pool); } } @@ -3309,7 +3608,7 @@ assemble_history(svn_fs_t *fs, { svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history)); fs_history_data_t *fhd = apr_pcalloc(pool, sizeof(*fhd)); - fhd->path = path; + fhd->path = svn_fs__canonicalize_abspath(path, pool); fhd->revision = revision; fhd->is_interesting = is_interesting; fhd->path_hint = path_hint; @@ -3349,7 +3648,7 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, apr_pool_t *iterpool = svn_pool_create(scratch_pool); SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag, - scratch_pool, scratch_pool)); + scratch_pool)); for (hi = apr_hash_first(scratch_pool, entries); hi; @@ -3365,9 +3664,8 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, kid_path = svn_fspath__join(this_path, dirent->name, iterpool); SVN_ERR(get_dag(&kid_dag, root, kid_path, iterpool)); - SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, kid_dag, iterpool)); - SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down, kid_dag, - iterpool)); + SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, kid_dag)); + SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down, kid_dag)); if (has_mergeinfo) { @@ -3378,8 +3676,7 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, svn_error_t *err; SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, kid_dag, iterpool)); - mergeinfo_string = apr_hash_get(proplist, SVN_PROP_MERGEINFO, - APR_HASH_KEY_STRING); + mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); if (!mergeinfo_string) { svn_string_t *idstr = svn_fs_fs__id_unparse(dirent->id, iterpool); @@ -3404,10 +3701,8 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, } else { - apr_hash_set(result_catalog, - apr_pstrdup(result_pool, kid_path), - APR_HASH_KEY_STRING, - kid_mergeinfo); + svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path), + kid_mergeinfo); } } @@ -3424,6 +3719,24 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, return SVN_NO_ERROR; } +/* Return the cache key as a combination of REV_ROOT->REV, the inheritance + flags INHERIT and ADJUST_INHERITED_MERGEINFO, and the PATH. The result + will be allocated in POOL.. + */ +static const char * +mergeinfo_cache_key(const char *path, + svn_fs_root_t *rev_root, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *pool) +{ + apr_int64_t number = rev_root->rev; + number = number * 4 + + (inherit == svn_mergeinfo_nearest_ancestor ? 2 : 0) + + (adjust_inherited_mergeinfo ? 1 : 0); + + return svn_fs_fs__combine_number_and_string(number, path, pool); +} /* Calculates the mergeinfo for PATH under REV_ROOT using inheritance type INHERIT. Returns it in *MERGEINFO, or NULL if there is none. @@ -3431,19 +3744,17 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root, used for temporary allocations. */ static svn_error_t * -get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, - svn_fs_root_t *rev_root, - const char *path, - svn_mergeinfo_inheritance_t inherit, - apr_pool_t *result_pool, - apr_pool_t *scratch_pool) +get_mergeinfo_for_path_internal(svn_mergeinfo_t *mergeinfo, + svn_fs_root_t *rev_root, + const char *path, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { parent_path_t *parent_path, *nearest_ancestor; apr_hash_t *proplist; svn_string_t *mergeinfo_string; - apr_pool_t *iterpool = svn_pool_create(scratch_pool); - - *mergeinfo = NULL; path = svn_fs__canonicalize_abspath(path, scratch_pool); @@ -3461,17 +3772,14 @@ get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, { svn_boolean_t has_mergeinfo; - svn_pool_clear(iterpool); - SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, - nearest_ancestor->node, iterpool)); + nearest_ancestor->node)); if (has_mergeinfo) break; /* No need to loop if we're looking for explicit mergeinfo. */ if (inherit == svn_mergeinfo_explicit) { - svn_pool_destroy(iterpool); return SVN_NO_ERROR; } @@ -3480,17 +3788,13 @@ get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, /* Run out? There's no mergeinfo. */ if (!nearest_ancestor) { - svn_pool_destroy(iterpool); return SVN_NO_ERROR; } } - svn_pool_destroy(iterpool); - SVN_ERR(svn_fs_fs__dag_get_proplist(&proplist, nearest_ancestor->node, scratch_pool)); - mergeinfo_string = apr_hash_get(proplist, SVN_PROP_MERGEINFO, - APR_HASH_KEY_STRING); + mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO); if (!mergeinfo_string) return svn_error_createf (SVN_ERR_FS_CORRUPT, NULL, @@ -3521,7 +3825,7 @@ get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, can return the mergeinfo results directly. Otherwise, we're inheriting the mergeinfo, so we need to a) remove non-inheritable ranges and b) telescope the merged-from paths. */ - if (nearest_ancestor != parent_path) + if (adjust_inherited_mergeinfo && (nearest_ancestor != parent_path)) { svn_mergeinfo_t tmp_mergeinfo; @@ -3539,6 +3843,58 @@ get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, return SVN_NO_ERROR; } +/* Caching wrapper around get_mergeinfo_for_path_internal(). + */ +static svn_error_t * +get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo, + svn_fs_root_t *rev_root, + const char *path, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + fs_fs_data_t *ffd = rev_root->fs->fsap_data; + const char *cache_key; + svn_boolean_t found = FALSE; + svn_stringbuf_t *mergeinfo_exists; + + *mergeinfo = NULL; + + cache_key = mergeinfo_cache_key(path, rev_root, inherit, + adjust_inherited_mergeinfo, scratch_pool); + if (ffd->mergeinfo_existence_cache) + { + SVN_ERR(svn_cache__get((void **)&mergeinfo_exists, &found, + ffd->mergeinfo_existence_cache, + cache_key, result_pool)); + if (found && mergeinfo_exists->data[0] == '1') + SVN_ERR(svn_cache__get((void **)mergeinfo, &found, + ffd->mergeinfo_cache, + cache_key, result_pool)); + } + + if (! found) + { + SVN_ERR(get_mergeinfo_for_path_internal(mergeinfo, rev_root, path, + inherit, + adjust_inherited_mergeinfo, + result_pool, scratch_pool)); + if (ffd->mergeinfo_existence_cache) + { + mergeinfo_exists = svn_stringbuf_create(*mergeinfo ? "1" : "0", + scratch_pool); + SVN_ERR(svn_cache__set(ffd->mergeinfo_existence_cache, + cache_key, mergeinfo_exists, scratch_pool)); + if (*mergeinfo) + SVN_ERR(svn_cache__set(ffd->mergeinfo_cache, + cache_key, *mergeinfo, scratch_pool)); + } + } + + return SVN_NO_ERROR; +} + /* Adds mergeinfo for each descendant of PATH (but not PATH itself) under ROOT to RESULT_CATALOG. Returned values are allocated in RESULT_POOL; temporary values in POOL. */ @@ -3554,8 +3910,7 @@ add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog, SVN_ERR(get_dag(&this_dag, root, path, scratch_pool)); SVN_ERR(svn_fs_fs__dag_has_descendants_with_mergeinfo(&go_down, - this_dag, - scratch_pool)); + this_dag)); if (go_down) SVN_ERR(crawl_directory_dag_for_mergeinfo(root, path, @@ -3576,10 +3931,12 @@ get_mergeinfos_for_paths(svn_fs_root_t *root, const apr_array_header_t *paths, svn_mergeinfo_inheritance_t inherit, svn_boolean_t include_descendants, - apr_pool_t *pool) + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { - svn_mergeinfo_catalog_t result_catalog = apr_hash_make(pool); - apr_pool_t *iterpool = svn_pool_create(pool); + svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool); + apr_pool_t *iterpool = svn_pool_create(scratch_pool); int i; for (i = 0; i < paths->nelts; i++) @@ -3591,7 +3948,8 @@ get_mergeinfos_for_paths(svn_fs_root_t *root, svn_pool_clear(iterpool); err = get_mergeinfo_for_path(&path_mergeinfo, root, path, - inherit, pool, iterpool); + inherit, adjust_inherited_mergeinfo, + result_pool, iterpool); if (err) { if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR) @@ -3607,11 +3965,10 @@ get_mergeinfos_for_paths(svn_fs_root_t *root, } if (path_mergeinfo) - apr_hash_set(result_catalog, path, APR_HASH_KEY_STRING, - path_mergeinfo); + svn_hash_sets(result_catalog, path, path_mergeinfo); if (include_descendants) - SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path, pool, - iterpool)); + SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path, + result_pool, scratch_pool)); } svn_pool_destroy(iterpool); @@ -3627,7 +3984,9 @@ fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, const apr_array_header_t *paths, svn_mergeinfo_inheritance_t inherit, svn_boolean_t include_descendants, - apr_pool_t *pool) + svn_boolean_t adjust_inherited_mergeinfo, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) { fs_fs_data_t *ffd = root->fs->fsap_data; @@ -3646,7 +4005,9 @@ fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, /* Retrieve a path -> mergeinfo hash mapping. */ return get_mergeinfos_for_paths(root, catalog, paths, inherit, - include_descendants, pool); + include_descendants, + adjust_inherited_mergeinfo, + result_pool, scratch_pool); } @@ -3673,6 +4034,7 @@ static root_vtable_t root_vtable = { fs_file_length, fs_file_checksum, fs_file_contents, + fs_try_process_file_contents, fs_make_file, fs_apply_textdelta, fs_apply_text, @@ -3712,7 +4074,7 @@ make_revision_root(svn_fs_t *fs, root->rev = rev; frd->root_dir = root_dir; - frd->copyfrom_cache = apr_hash_make(root->pool); + frd->copyfrom_cache = svn_hash__make(root->pool); root->fsap_data = frd; @@ -3739,6 +4101,8 @@ make_txn_root(svn_fs_root_t **root_p, root->txn_flags = flags; root->rev = base_rev; + frd->txn_id = txn; + /* Because this cache actually tries to invalidate elements, keep the number of elements per page down. @@ -3749,7 +4113,8 @@ make_txn_root(svn_fs_root_t **root_p, svn_fs_fs__dag_deserialize, APR_HASH_KEY_STRING, 32, 20, FALSE, - apr_pstrcat(pool, txn, ":TXN", (char *)NULL), + apr_pstrcat(pool, txn, ":TXN", + (char *)NULL), root->pool)); /* Initialize transaction-local caches in FS. @@ -3763,3 +4128,197 @@ make_txn_root(svn_fs_root_t **root_p, *root_p = root; return SVN_NO_ERROR; } + + + +/* Verify. */ +static APR_INLINE const char * +stringify_node(dag_node_t *node, + apr_pool_t *pool) +{ + /* ### TODO: print some PATH@REV to it, too. */ + return svn_fs_fs__id_unparse(svn_fs_fs__dag_get_id(node), pool)->data; +} + +/* Check metadata sanity on NODE, and on its children. Manually verify + information for DAG nodes in revision REV, and trust the metadata + accuracy for nodes belonging to older revisions. */ +static svn_error_t * +verify_node(dag_node_t *node, + svn_revnum_t rev, + apr_pool_t *pool) +{ + svn_boolean_t has_mergeinfo; + apr_int64_t mergeinfo_count; + const svn_fs_id_t *pred_id; + svn_fs_t *fs = svn_fs_fs__dag_get_fs(node); + int pred_count; + svn_node_kind_t kind; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Fetch some data. */ + SVN_ERR(svn_fs_fs__dag_has_mergeinfo(&has_mergeinfo, node)); + SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&mergeinfo_count, node)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, node)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_count, node)); + kind = svn_fs_fs__dag_node_kind(node); + + /* Sanity check. */ + if (mergeinfo_count < 0) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Negative mergeinfo-count %" APR_INT64_T_FMT + " on node '%s'", + mergeinfo_count, stringify_node(node, iterpool)); + + /* Issue #4129. (This check will explicitly catch non-root instances too.) */ + if (pred_id) + { + dag_node_t *pred; + int pred_pred_count; + SVN_ERR(svn_fs_fs__dag_get_node(&pred, fs, pred_id, iterpool)); + SVN_ERR(svn_fs_fs__dag_get_predecessor_count(&pred_pred_count, pred)); + if (pred_pred_count+1 != pred_count) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Predecessor count mismatch: " + "%s has %d, but %s has %d", + stringify_node(node, iterpool), pred_count, + stringify_node(pred, iterpool), + pred_pred_count); + } + + /* Kind-dependent verifications. */ + if (kind == svn_node_none) + { + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Node '%s' has kind 'none'", + stringify_node(node, iterpool)); + } + if (kind == svn_node_file) + { + if (has_mergeinfo != mergeinfo_count) /* comparing int to bool */ + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "File node '%s' has inconsistent mergeinfo: " + "has_mergeinfo=%d, " + "mergeinfo_count=%" APR_INT64_T_FMT, + stringify_node(node, iterpool), + has_mergeinfo, mergeinfo_count); + } + if (kind == svn_node_dir) + { + apr_hash_t *entries; + apr_hash_index_t *hi; + apr_int64_t children_mergeinfo = 0; + + SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool)); + + /* Compute CHILDREN_MERGEINFO. */ + for (hi = apr_hash_first(pool, entries); + hi; + hi = apr_hash_next(hi)) + { + svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi); + dag_node_t *child; + svn_revnum_t child_rev; + apr_int64_t child_mergeinfo; + + svn_pool_clear(iterpool); + + /* Compute CHILD_REV. */ + SVN_ERR(svn_fs_fs__dag_get_node(&child, fs, dirent->id, iterpool)); + SVN_ERR(svn_fs_fs__dag_get_revision(&child_rev, child, iterpool)); + + if (child_rev == rev) + SVN_ERR(verify_node(child, rev, iterpool)); + + SVN_ERR(svn_fs_fs__dag_get_mergeinfo_count(&child_mergeinfo, child)); + children_mergeinfo += child_mergeinfo; + } + + /* Side-effect of issue #4129. */ + if (children_mergeinfo+has_mergeinfo != mergeinfo_count) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Mergeinfo-count discrepancy on '%s': " + "expected %" APR_INT64_T_FMT "+%d, " + "counted %" APR_INT64_T_FMT, + stringify_node(node, iterpool), + mergeinfo_count, has_mergeinfo, + children_mergeinfo); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + +svn_error_t * +svn_fs_fs__verify_root(svn_fs_root_t *root, + apr_pool_t *pool) +{ + svn_fs_t *fs = root->fs; + dag_node_t *root_dir; + + /* Issue #4129: bogus pred-counts and minfo-cnt's on the root node-rev + (and elsewhere). This code makes more thorough checks than the + commit-time checks in validate_root_noderev(). */ + + /* Callers should disable caches by setting SVN_FS_CONFIG_FSFS_CACHE_NS; + see r1462436. + + When this code is called in the library, we want to ensure we + use the on-disk data --- rather than some data that was read + in the possibly-distance past and cached since. */ + + if (root->is_txn_root) + { + fs_txn_root_data_t *frd = root->fsap_data; + SVN_ERR(svn_fs_fs__dag_txn_root(&root_dir, fs, frd->txn_id, pool)); + } + else + { + fs_rev_root_data_t *frd = root->fsap_data; + root_dir = frd->root_dir; + } + + /* Recursively verify ROOT_DIR. */ + SVN_ERR(verify_node(root_dir, root->rev, pool)); + + /* Verify explicitly the predecessor of the root. */ + { + const svn_fs_id_t *pred_id; + + /* Only r0 should have no predecessor. */ + SVN_ERR(svn_fs_fs__dag_get_predecessor_id(&pred_id, root_dir)); + if (! root->is_txn_root && !!pred_id != !!root->rev) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "r%ld's root node's predecessor is " + "unexpectedly '%s'", + root->rev, + (pred_id + ? svn_fs_fs__id_unparse(pred_id, pool)->data + : "(null)")); + if (root->is_txn_root && !pred_id) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Transaction '%s''s root node's predecessor is " + "unexpectedly NULL", + root->txn); + + /* Check the predecessor's revision. */ + if (pred_id) + { + svn_revnum_t pred_rev = svn_fs_fs__id_rev(pred_id); + if (! root->is_txn_root && pred_rev+1 != root->rev) + /* Issue #4129. */ + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "r%ld's root node's predecessor is r%ld" + " but should be r%ld", + root->rev, pred_rev, root->rev - 1); + if (root->is_txn_root && pred_rev != root->rev) + return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL, + "Transaction '%s''s root node's predecessor" + " is r%ld" + " but should be r%ld", + root->txn, pred_rev, root->rev); + } + } + + return SVN_NO_ERROR; +} |