summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_fs/tree.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_fs/tree.c')
-rw-r--r--subversion/libsvn_fs_fs/tree.c955
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(&copyroot_rev, &copyroot_path,
- child->node,pool));
+ child->node));
SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, fs, copyroot_rev, pool));
SVN_ERR(get_dag(&copyroot_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, &copy_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, &copy_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(&copyroot_rev, &copyroot_path,
- parent_path->node, pool));
+ parent_path->node));
SVN_ERR(svn_fs_fs__revision_root(&copyroot_root, root->fs,
copyroot_rev, pool));
SVN_ERR(get_dag(&copyroot_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(&copyfrom_rev, node, pool));
- SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copyfrom_path, node, pool));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&copyfrom_rev, node));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copyfrom_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(&copy_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(&copy_src_rev, &copy_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(&copy_src, node, pool));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_rev(&src_rev, node));
+ SVN_ERR(svn_fs_fs__dag_get_copyfrom_path(&copy_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;
+}