summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_fs
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2018-05-08 03:44:38 +0000
committerPeter Wemm <peter@FreeBSD.org>2018-05-08 03:44:38 +0000
commit3faf8d6bffc5d0fb2525ba37bb504c53366caf9d (patch)
tree7e47911263e75034b767fe34b2f8d3d17e91f66d /subversion/libsvn_fs_fs
parenta55fb3c0d5eca7d887798125d5b95942b1f01d4b (diff)
Diffstat (limited to 'subversion/libsvn_fs_fs')
-rw-r--r--subversion/libsvn_fs_fs/cached_data.c648
-rw-r--r--subversion/libsvn_fs_fs/cached_data.h37
-rw-r--r--subversion/libsvn_fs_fs/caching.c205
-rw-r--r--subversion/libsvn_fs_fs/dag.c13
-rw-r--r--subversion/libsvn_fs_fs/fs.c66
-rw-r--r--subversion/libsvn_fs_fs/fs.h117
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.c370
-rw-r--r--subversion/libsvn_fs_fs/fs_fs.h18
-rw-r--r--subversion/libsvn_fs_fs/fs_init.h32
-rw-r--r--subversion/libsvn_fs_fs/hotcopy.c131
-rw-r--r--subversion/libsvn_fs_fs/hotcopy.h26
-rw-r--r--subversion/libsvn_fs_fs/id.c12
-rw-r--r--subversion/libsvn_fs_fs/index.c45
-rw-r--r--subversion/libsvn_fs_fs/load-index.c98
-rw-r--r--subversion/libsvn_fs_fs/lock.c8
-rw-r--r--subversion/libsvn_fs_fs/low_level.c238
-rw-r--r--subversion/libsvn_fs_fs/low_level.h9
-rw-r--r--subversion/libsvn_fs_fs/pack.c415
-rw-r--r--subversion/libsvn_fs_fs/recovery.c6
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.h61
-rw-r--r--subversion/libsvn_fs_fs/rep-cache-db.sql31
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.c60
-rw-r--r--subversion/libsvn_fs_fs/rep-cache.h6
-rw-r--r--subversion/libsvn_fs_fs/rev_file.c1
-rw-r--r--subversion/libsvn_fs_fs/revprops.c362
-rw-r--r--subversion/libsvn_fs_fs/revprops.h26
-rw-r--r--subversion/libsvn_fs_fs/stats.c256
-rw-r--r--subversion/libsvn_fs_fs/structure17
-rw-r--r--subversion/libsvn_fs_fs/structure-indexes24
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.c229
-rw-r--r--subversion/libsvn_fs_fs/temp_serializer.h128
-rw-r--r--subversion/libsvn_fs_fs/transaction.c763
-rw-r--r--subversion/libsvn_fs_fs/tree.c558
-rw-r--r--subversion/libsvn_fs_fs/util.c93
-rw-r--r--subversion/libsvn_fs_fs/util.h9
-rw-r--r--subversion/libsvn_fs_fs/verify.c53
36 files changed, 3624 insertions, 1547 deletions
diff --git a/subversion/libsvn_fs_fs/cached_data.c b/subversion/libsvn_fs_fs/cached_data.c
index 6581a6c8831c6..f8fa2d05bfd68 100644
--- a/subversion/libsvn_fs_fs/cached_data.c
+++ b/subversion/libsvn_fs_fs/cached_data.c
@@ -57,7 +57,7 @@ block_read(void **result,
apr_pool_t *scratch_pool);
-/* Defined this to enable access logging via dgb__log_access
+/* Define this to enable access logging via dbg_log_access
#define SVN_FS_FS__LOG_ACCESS
*/
@@ -91,7 +91,7 @@ dbg_log_access(svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file;
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, fs, revision,
- scratch_pool));
+ scratch_pool, scratch_pool));
/* determine rev / pack file offset */
SVN_ERR(svn_fs_fs__item_offset(&offset, fs, rev_file, revision, NULL,
@@ -158,7 +158,8 @@ dbg_log_access(svn_fs_t *fs,
{
/* reverse index lookup: get item description in ENTRY */
SVN_ERR(svn_fs_fs__p2l_entry_lookup(&entry, fs, rev_file, revision,
- offset, scratch_pool));
+ offset, scratch_pool,
+ scratch_pool));
if (entry)
{
/* more details */
@@ -183,6 +184,10 @@ dbg_log_access(svn_fs_t *fs,
description);
}
+ /* We don't know when SCRATCH_POOL will be cleared, so close the rev file
+ explicitly. */
+ SVN_ERR(svn_fs_fs__close_revision_file(rev_file));
+
#endif
return SVN_NO_ERROR;
@@ -286,6 +291,114 @@ use_block_read(svn_fs_t *fs)
return svn_fs_fs__use_log_addressing(fs) && ffd->use_block_read;
}
+svn_error_t *
+svn_fs_fs__fixup_expanded_size(svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *scratch_pool)
+{
+ svn_checksum_t checksum;
+ svn_checksum_t *empty_md5;
+ svn_fs_fs__revision_file_t *revision_file;
+ svn_fs_fs__rep_header_t *rep_header;
+
+ /* Anything to do at all?
+ *
+ * Note that a 0 SIZE is only possible for PLAIN reps due to the SVN\1
+ * magic prefix in any DELTA rep. */
+ if (!rep || rep->expanded_size != 0 || rep->size == 0)
+ return SVN_NO_ERROR;
+
+ /* This function may only be called for committed data. */
+ assert(!svn_fs_fs__id_txn_used(&rep->txn_id));
+
+ /* EXPANDED_SIZE is 0. If the MD5 does not match the one for empty
+ * contents, we know that EXPANDED_SIZE == 0 is wrong and needs to
+ * be set to the actual value given by SIZE.
+ *
+ * Using svn_checksum_match() will also accept all-zero values for
+ * the MD5 digest and only report a mismatch if the MD5 has actually
+ * been given. */
+ empty_md5 = svn_checksum_empty_checksum(svn_checksum_md5, scratch_pool);
+
+ checksum.digest = rep->md5_digest;
+ checksum.kind = svn_checksum_md5;
+ if (!svn_checksum_match(empty_md5, &checksum))
+ {
+ rep->expanded_size = rep->size;
+ return SVN_NO_ERROR;
+ }
+
+ /* Data in the rep-cache.db does not have MD5 checksums (all zero) on it.
+ * Compare SHA1 instead. */
+ if (rep->has_sha1)
+ {
+ svn_checksum_t *empty_sha1
+ = svn_checksum_empty_checksum(svn_checksum_sha1, scratch_pool);
+
+ checksum.digest = rep->sha1_digest;
+ checksum.kind = svn_checksum_sha1;
+ if (!svn_checksum_match(empty_sha1, &checksum))
+ {
+ rep->expanded_size = rep->size;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Only two cases are left here.
+ * (1) A non-empty PLAIN rep with a MD5 collision on EMPTY_MD5.
+ * (2) A DELTA rep with zero-length output. */
+
+ /* SVN always stores a DELTA rep with zero-length output as an empty
+ * sequence of txdelta windows, i.e. as "SVN\1". In that case, SIZE is
+ * 4 bytes. There is no other possible DELTA rep of that size and any
+ * PLAIN rep of 4 bytes would produce a different MD5. Hence, if SIZE is
+ * actually 4 here, we know that this is an empty DELTA rep.
+ *
+ * Note that it is technically legal to have DELTA reps with a 0 length
+ * output window. Their on-disk size would be longer. We handle that
+ * case later together with the equally unlikely MD5 collision. */
+ if (rep->size == 4)
+ {
+ /* EXPANDED_SIZE is already 0. */
+ return SVN_NO_ERROR;
+ }
+
+ /* We still have the two options, PLAIN or DELTA rep. At this point, we
+ * are in an extremely unlikely case and can spend some time to figure it
+ * out. So, let's just look at the representation header. */
+ SVN_ERR(open_and_seek_revision(&revision_file, fs, rep->revision,
+ rep->item_index, scratch_pool));
+ SVN_ERR(svn_fs_fs__read_rep_header(&rep_header, revision_file->stream,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_fs_fs__close_revision_file(revision_file));
+
+ /* Only for PLAIN reps do we have to correct EXPANDED_SIZE. */
+ if (rep_header->type == svn_fs_fs__rep_plain)
+ rep->expanded_size = rep->size;
+
+ return SVN_NO_ERROR;
+}
+
+/* Correct known issues with committed NODEREV in FS.
+ * Uses SCRATCH_POOL for temporaries.
+ */
+static svn_error_t *
+fixup_node_revision(svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *scratch_pool)
+{
+ /* Workaround issue #4031: is-fresh-txn-root in revision files. */
+ noderev->is_fresh_txn_root = FALSE;
+
+ /* Make sure EXPANDED_SIZE has the correct value for every rep. */
+ SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->data_rep,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, noderev->prop_rep,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
/* Get the node-revision for the node ID in FS.
Set *NODEREV_P to the new node-revision structure, allocated in POOL.
See svn_fs_fs__get_node_revision, which wraps this and adds another
@@ -312,14 +425,13 @@ get_node_revision_body(node_revision_t **noderev_p,
scratch_pool),
APR_READ | APR_BUFFERED, APR_OS_DEFAULT,
scratch_pool);
- if (err)
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err_dangling_id(fs, id));
+ }
+ else if (err)
{
- if (APR_STATUS_IS_ENOENT(err->apr_err))
- {
- svn_error_clear(err);
- return svn_error_trace(err_dangling_id(fs, id));
- }
-
return svn_error_trace(err);
}
@@ -376,9 +488,7 @@ get_node_revision_body(node_revision_t **noderev_p,
revision_file->stream,
result_pool,
scratch_pool));
-
- /* Workaround issue #4031: is-fresh-txn-root in revision files. */
- (*noderev_p)->is_fresh_txn_root = FALSE;
+ SVN_ERR(fixup_node_revision(fs, *noderev_p, scratch_pool));
/* The noderev is not in cache, yet. Add it, if caching has been enabled. */
if (ffd->node_revision_cache)
@@ -636,15 +746,15 @@ typedef struct rep_state_t
int chunk_index; /* number of the window to read */
} rep_state_t;
-/* Simple wrapper around svn_fs_fs__get_file_offset to simplify callers. */
+/* Simple wrapper around svn_io_file_get_offset to simplify callers. */
static svn_error_t *
get_file_offset(apr_off_t *offset,
rep_state_t *rs,
apr_pool_t *pool)
{
- return svn_error_trace(svn_fs_fs__get_file_offset(offset,
- rs->sfile->rfile->file,
- pool));
+ return svn_error_trace(svn_io_file_get_offset(offset,
+ rs->sfile->rfile->file,
+ pool));
}
/* Simple wrapper around svn_io_file_aligned_seek to simplify callers. */
@@ -756,7 +866,7 @@ create_rep_state_body(rep_state_t **rep_state,
rs->size = rep->size;
rs->revision = rep->revision;
rs->item_index = rep->item_index;
- rs->raw_window_cache = ffd->raw_window_cache;
+ rs->raw_window_cache = use_block_read(fs) ? ffd->raw_window_cache : NULL;
rs->ver = -1;
rs->start = -1;
@@ -765,9 +875,7 @@ create_rep_state_body(rep_state_t **rep_state,
Since we don't know the depth of the delta chain, let's assume, the
whole contents get rewritten 3 times.
*/
- estimated_window_storage
- = 4 * ( (rep->expanded_size ? rep->expanded_size : rep->size)
- + SVN_DELTA_WINDOW_SIZE);
+ estimated_window_storage = 4 * (rep->expanded_size + SVN_DELTA_WINDOW_SIZE);
estimated_window_storage = MIN(estimated_window_storage, APR_SIZE_MAX);
rs->window_cache = ffd->txdelta_window_cache
@@ -1160,7 +1268,7 @@ parse_raw_window(void **out,
stream = svn_stream_from_string(&raw_window, result_pool);
/* parse it */
- SVN_ERR(svn_txdelta_read_svndiff_window(&result->window, stream, 1,
+ SVN_ERR(svn_txdelta_read_svndiff_window(&result->window, stream, window->ver,
result_pool));
/* complete the window and return it */
@@ -1322,15 +1430,11 @@ set_cached_combined_window(svn_stringbuf_t *window,
ID, and representation REP.
Also, set *WINDOW_P to the base window content for *LIST, if it
could be found in cache. Otherwise, *LIST will contain the base
- representation for the whole delta chain.
- Finally, return the expanded size of the representation in
- *EXPANDED_SIZE. It will take care of cases where only the on-disk
- size is known. */
+ representation for the whole delta chain. */
static svn_error_t *
build_rep_list(apr_array_header_t **list,
svn_stringbuf_t **window_p,
rep_state_t **src_state,
- svn_filesize_t *expanded_size,
svn_fs_t *fs,
representation_t *first_rep,
apr_pool_t *pool)
@@ -1345,24 +1449,9 @@ build_rep_list(apr_array_header_t **list,
*list = apr_array_make(pool, 1, sizeof(rep_state_t *));
rep = *first_rep;
- /* The value as stored in the data struct.
- 0 is either for unknown length or actually zero length. */
- *expanded_size = first_rep->expanded_size;
-
/* for the top-level rep, we need the rep_args */
SVN_ERR(create_rep_state(&rs, &rep_header, &shared_file, &rep, fs, pool,
iterpool));
-
- /* Unknown size or empty representation?
- That implies the this being the first iteration.
- Usually size equals on-disk size, except for empty,
- compressed representations (delta, size = 4).
- Please note that for all non-empty deltas have
- a 4-byte header _plus_ some data. */
- if (*expanded_size == 0)
- if (rep_header->type == svn_fs_fs__rep_plain || first_rep->size != 4)
- *expanded_size = first_rep->size;
-
while (1)
{
svn_pool_clear(iterpool);
@@ -1373,7 +1462,8 @@ build_rep_list(apr_array_header_t **list,
&rep, fs, pool, iterpool));
/* for txn reps, there won't be a cached combined window */
- if (!svn_fs_fs__id_txn_used(&rep.txn_id))
+ if ( !svn_fs_fs__id_txn_used(&rep.txn_id)
+ && rep.expanded_size < SVN_DELTA_WINDOW_SIZE)
SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
if (is_cached)
@@ -1686,7 +1776,7 @@ get_combined_window(svn_stringbuf_t **result,
}
/* Returns whether or not the expanded fulltext of the file is cachable
- * based on its size SIZE. The decision depends on the cache used by RB.
+ * based on its size SIZE. The decision depends on the cache used by FFD.
*/
static svn_boolean_t
fulltext_size_is_cachable(fs_fs_data_t *ffd, svn_filesize_t size)
@@ -1732,10 +1822,10 @@ get_contents_from_windows(struct rep_read_baton *rb,
This is where we need the pseudo rep_state created
by build_rep_list(). */
apr_size_t offset = (apr_size_t)rs->current;
- if (copy_len + offset > rb->base_window->len)
- copy_len = offset < rb->base_window->len
- ? rb->base_window->len - offset
- : 0ul;
+ if (offset >= rb->base_window->len)
+ copy_len = 0ul;
+ else if (copy_len > rb->base_window->len - offset)
+ copy_len = rb->base_window->len - offset;
memcpy (cur, rb->base_window->data + offset, copy_len);
}
@@ -1969,11 +2059,21 @@ skip_contents(struct rep_read_baton *baton,
len -= to_read;
buffer += to_read;
}
+
+ /* Make the MD5 calculation catch up with the data delivered
+ * (we did not run MD5 on the data that we took from the cache). */
+ if (!err)
+ {
+ SVN_ERR(svn_checksum_update(baton->md5_checksum_ctx,
+ baton->current_fulltext->data,
+ baton->current_fulltext->len));
+ baton->off += baton->current_fulltext->len;
+ }
}
else if (len > 0)
{
/* Simply drain LEN bytes from the window stream. */
- apr_pool_t *subpool = subpool = svn_pool_create(baton->pool);
+ apr_pool_t *subpool = svn_pool_create(baton->pool);
char *buffer = apr_palloc(subpool, SVN__STREAM_CHUNK_SIZE);
while (len > 0 && !err)
@@ -1984,6 +2084,15 @@ skip_contents(struct rep_read_baton *baton,
err = get_contents_from_windows(baton, buffer, &to_read);
len -= to_read;
+
+ /* Make the MD5 calculation catch up with the data delivered
+ * (we did not run MD5 on the data that we took from the cache). */
+ if (!err)
+ {
+ SVN_ERR(svn_checksum_update(baton->md5_checksum_ctx,
+ buffer, to_read));
+ baton->off += to_read;
+ }
}
svn_pool_destroy(subpool);
@@ -2019,8 +2128,9 @@ rep_read_contents(void *baton,
if (!rb->rs_list)
{
/* Window stream not initialized, yet. Do it now. */
+ rb->len = rb->rep.expanded_size;
SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window,
- &rb->src_state, &rb->len, rb->fs, &rb->rep,
+ &rb->src_state, rb->fs, &rb->rep,
rb->filehandle_pool));
/* In case we did read from the fulltext cache before, make the
@@ -2092,7 +2202,6 @@ svn_fs_fs__get_contents(svn_stream_t **contents_p,
else
{
fs_fs_data_t *ffd = fs->fsap_data;
- svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
struct rep_read_baton *rb;
pair_cache_key_t fulltext_cache_key = { 0 };
@@ -2108,7 +2217,7 @@ svn_fs_fs__get_contents(svn_stream_t **contents_p,
* cache it. */
if (ffd->fulltext_cache && cache_fulltext
&& SVN_IS_VALID_REVNUM(rep->revision)
- && fulltext_size_is_cachable(ffd, len))
+ && fulltext_size_is_cachable(ffd, rep->expanded_size))
{
rb->fulltext_cache = ffd->fulltext_cache;
}
@@ -2202,7 +2311,7 @@ svn_fs_fs__get_contents_from_file(svn_stream_t **contents_p,
svn_fs_fs__id_txn_reset(&next_rep.txn_id);
SVN_ERR(build_rep_list(&rb->rs_list, &rb->base_window,
- &rb->src_state, &rb->len, rb->fs, &next_rep,
+ &rb->src_state, rb->fs, &next_rep,
rb->filehandle_pool));
/* Insert the access to REP as the first element of the delta chain. */
@@ -2447,12 +2556,12 @@ compare_dirent_name(const void *a, const void *b)
return strcmp(lhs->name, rhs);
}
-/* Into ENTRIES, read all directories entries from the key-value text in
+/* Into *ENTRIES_P, read all directories entries from the key-value text in
* STREAM. If INCREMENTAL is TRUE, read until the end of the STREAM and
* update the data. ID is provided for nicer error messages.
*/
static svn_error_t *
-read_dir_entries(apr_array_header_t *entries,
+read_dir_entries(apr_array_header_t **entries_p,
svn_stream_t *stream,
svn_boolean_t incremental,
const svn_fs_id_t *id,
@@ -2460,8 +2569,14 @@ read_dir_entries(apr_array_header_t *entries,
apr_pool_t *scratch_pool)
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
- apr_hash_t *hash = incremental ? svn_hash__make(scratch_pool) : NULL;
+ apr_hash_t *hash = NULL;
const char *terminator = SVN_HASH_TERMINATOR;
+ apr_array_header_t *entries = NULL;
+
+ if (incremental)
+ hash = svn_hash__make(scratch_pool);
+ else
+ entries = apr_array_make(result_pool, 16, sizeof(svn_fs_dirent_t *));
/* Read until the terminator (non-incremental) or the end of STREAM
(incremental mode). In the latter mode, we use a temporary HASH
@@ -2473,8 +2588,11 @@ read_dir_entries(apr_array_header_t *entries,
char *str;
svn_pool_clear(iterpool);
- SVN_ERR(svn_hash__read_entry(&entry, stream, terminator,
- incremental, iterpool));
+ SVN_ERR_W(svn_hash__read_entry(&entry, stream, terminator,
+ incremental, iterpool),
+ apr_psprintf(iterpool,
+ _("Directory representation corrupt in '%s'"),
+ svn_fs_fs__id_unparse(id, scratch_pool)->data));
/* End of directory? */
if (entry.key == NULL)
@@ -2542,6 +2660,9 @@ read_dir_entries(apr_array_header_t *entries,
if (incremental)
{
apr_hash_index_t *hi;
+
+ entries = apr_array_make(result_pool, apr_hash_count(hash),
+ sizeof(svn_fs_dirent_t *));
for (hi = apr_hash_first(iterpool, hash); hi; hi = apr_hash_next(hi))
APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = apr_hash_this_val(hi);
}
@@ -2551,14 +2672,45 @@ read_dir_entries(apr_array_header_t *entries,
svn_pool_destroy(iterpool);
+ *entries_p = entries;
return SVN_NO_ERROR;
}
-/* Fetch the contents of a directory into ENTRIES. Values are stored
+/* For directory NODEREV in FS, return the *FILESIZE of its in-txn
+ * representation. If the directory representation is comitted data,
+ * set *FILESIZE to SVN_INVALID_FILESIZE. Use SCRATCH_POOL for temporaries.
+ */
+static svn_error_t *
+get_txn_dir_info(svn_filesize_t *filesize,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ apr_pool_t *scratch_pool)
+{
+ if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id))
+ {
+ const svn_io_dirent2_t *dirent;
+ const char *filename;
+
+ filename = svn_fs_fs__path_txn_node_children(fs, noderev->id,
+ scratch_pool);
+
+ SVN_ERR(svn_io_stat_dirent2(&dirent, filename, FALSE, FALSE,
+ scratch_pool, scratch_pool));
+ *filesize = dirent->filesize;
+ }
+ else
+ {
+ *filesize = SVN_INVALID_FILESIZE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Fetch the contents of a directory into DIR. Values are stored
as filename to string mappings; further conversion is necessary to
convert them into svn_fs_dirent_t values. */
static svn_error_t *
-get_dir_contents(apr_array_header_t **entries,
+get_dir_contents(svn_fs_fs__dir_data_t *dir,
svn_fs_t *fs,
node_revision_t *noderev,
apr_pool_t *result_pool,
@@ -2566,18 +2718,30 @@ get_dir_contents(apr_array_header_t **entries,
{
svn_stream_t *contents;
- *entries = apr_array_make(result_pool, 16, sizeof(svn_fs_dirent_t *));
+ /* Initialize the result. */
+ dir->txn_filesize = SVN_INVALID_FILESIZE;
+
+ /* Read dir contents - unless there is none in which case we are done. */
if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id))
{
- const char *filename
- = svn_fs_fs__path_txn_node_children(fs, noderev->id, scratch_pool);
+ /* Get location & current size of the directory representation. */
+ const char *filename;
+ apr_file_t *file;
+
+ filename = svn_fs_fs__path_txn_node_children(fs, noderev->id,
+ scratch_pool);
/* The representation is mutable. Read the old directory
contents from the mutable children file, followed by the
changes we've made in this transaction. */
- SVN_ERR(svn_stream_open_readonly(&contents, filename, scratch_pool,
- scratch_pool));
- SVN_ERR(read_dir_entries(*entries, contents, TRUE, noderev->id,
+ SVN_ERR(svn_io_file_open(&file, filename, APR_READ | APR_BUFFERED,
+ APR_OS_DEFAULT, scratch_pool));
+
+ /* Obtain txn children file size. */
+ SVN_ERR(svn_io_file_size_get(&dir->txn_filesize, file, scratch_pool));
+
+ contents = svn_stream_from_aprfile2(file, FALSE, scratch_pool);
+ SVN_ERR(read_dir_entries(&dir->entries, contents, TRUE, noderev->id,
result_pool, scratch_pool));
SVN_ERR(svn_stream_close(contents));
}
@@ -2586,9 +2750,7 @@ get_dir_contents(apr_array_header_t **entries,
/* Undeltify content before parsing it. Otherwise, we could only
* parse it byte-by-byte.
*/
- apr_size_t len = noderev->data_rep->expanded_size
- ? (apr_size_t)noderev->data_rep->expanded_size
- : (apr_size_t)noderev->data_rep->size;
+ apr_size_t len = noderev->data_rep->expanded_size;
svn_stringbuf_t *text;
/* The representation is immutable. Read it normally. */
@@ -2599,9 +2761,13 @@ get_dir_contents(apr_array_header_t **entries,
/* de-serialize hash */
contents = svn_stream_from_stringbuf(text, scratch_pool);
- SVN_ERR(read_dir_entries(*entries, contents, FALSE, noderev->id,
+ SVN_ERR(read_dir_entries(&dir->entries, contents, FALSE, noderev->id,
result_pool, scratch_pool));
}
+ else
+ {
+ dir->entries = apr_array_make(result_pool, 0, sizeof(svn_fs_dirent_t *));
+ }
return SVN_NO_ERROR;
}
@@ -2620,27 +2786,27 @@ locate_dir_cache(svn_fs_t *fs,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
- if (svn_fs_fs__id_is_txn(noderev->id))
+ if (!noderev->data_rep)
+ {
+ /* no data rep -> empty directory.
+ A NULL key causes a cache miss. */
+ *key = NULL;
+ return ffd->dir_cache;
+ }
+
+ if (svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id))
{
/* data in txns requires the expensive fs_id-based addressing mode */
*key = svn_fs_fs__id_unparse(noderev->id, pool)->data;
+
return ffd->txn_dir_cache;
}
else
{
/* committed data can use simple rev,item pairs */
- if (noderev->data_rep)
- {
- pair_key->revision = noderev->data_rep->revision;
- pair_key->second = noderev->data_rep->item_index;
- *key = pair_key;
- }
- else
- {
- /* no data rep -> empty directory.
- A NULL key causes a cache miss. */
- *key = NULL;
- }
+ pair_key->revision = noderev->data_rep->revision;
+ pair_key->second = noderev->data_rep->item_index;
+ *key = pair_key;
return ffd->dir_cache;
}
@@ -2655,6 +2821,7 @@ svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
{
pair_cache_key_t pair_key = { 0 };
const void *key;
+ svn_fs_fs__dir_data_t *dir;
/* find the cache we may use */
svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev,
@@ -2663,23 +2830,36 @@ svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
{
svn_boolean_t found;
- SVN_ERR(svn_cache__get((void **)entries_p, &found, cache, key,
+ SVN_ERR(svn_cache__get((void **)&dir, &found, cache, key,
result_pool));
if (found)
- return SVN_NO_ERROR;
+ {
+ /* Verify that the cached dir info is not stale
+ * (no-op for committed data). */
+ svn_filesize_t filesize;
+ SVN_ERR(get_txn_dir_info(&filesize, fs, noderev, scratch_pool));
+
+ if (filesize == dir->txn_filesize)
+ {
+ /* Still valid. Done. */
+ *entries_p = dir->entries;
+ return SVN_NO_ERROR;
+ }
+ }
}
/* Read in the directory contents. */
- SVN_ERR(get_dir_contents(entries_p, fs, noderev, result_pool,
- scratch_pool));
+ dir = apr_pcalloc(scratch_pool, sizeof(*dir));
+ SVN_ERR(get_dir_contents(dir, fs, noderev, result_pool, scratch_pool));
+ *entries_p = dir->entries;
/* Update the cache, if we are to use one.
*
* Don't even attempt to serialize very large directories; it would cause
* an unnecessary memory allocation peak. 150 bytes/entry is about right.
*/
- if (cache && svn_cache__is_cachable(cache, 150 * (*entries_p)->nelts))
- SVN_ERR(svn_cache__set(cache, key, *entries_p, scratch_pool));
+ if (cache && svn_cache__is_cachable(cache, 150 * dir->entries->nelts))
+ SVN_ERR(svn_cache__set(cache, key, dir, scratch_pool));
return SVN_NO_ERROR;
}
@@ -2702,6 +2882,7 @@ svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
+ extract_dir_entry_baton_t baton;
svn_boolean_t found = FALSE;
/* find the cache we may use */
@@ -2711,30 +2892,42 @@ svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
scratch_pool);
if (cache)
{
+ svn_filesize_t filesize;
+ SVN_ERR(get_txn_dir_info(&filesize, fs, noderev, scratch_pool));
+
/* Cache lookup. */
+ baton.txn_filesize = filesize;
+ baton.name = name;
SVN_ERR(svn_cache__get_partial((void **)dirent,
&found,
cache,
key,
svn_fs_fs__extract_dir_entry,
- (void*)name,
+ &baton,
result_pool));
}
/* fetch data from disk if we did not find it in the cache */
- if (! found)
+ if (! found || baton.out_of_date)
{
- apr_array_header_t *entries;
svn_fs_dirent_t *entry;
svn_fs_dirent_t *entry_copy = NULL;
+ svn_fs_fs__dir_data_t dir;
- /* read the dir from the file system. It will probably be put it
- into the cache for faster lookup in future calls. */
- SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev,
- scratch_pool, scratch_pool));
+ /* Read in the directory contents. */
+ SVN_ERR(get_dir_contents(&dir, fs, noderev, scratch_pool,
+ scratch_pool));
+
+ /* Update the cache, if we are to use one.
+ *
+ * Don't even attempt to serialize very large directories; it would
+ * cause an unnecessary memory allocation peak. 150 bytes / entry is
+ * about right. */
+ if (cache && svn_cache__is_cachable(cache, 150 * dir.entries->nelts))
+ SVN_ERR(svn_cache__set(cache, key, &dir, scratch_pool));
/* find desired entry and return a copy in POOL, if found */
- entry = svn_fs_fs__find_dir_entry(entries, name, NULL);
+ entry = svn_fs_fs__find_dir_entry(dir.entries, name, NULL);
if (entry)
{
entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
@@ -2771,7 +2964,7 @@ svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
{
svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
- svn_error_clear(svn_stream_close(stream));
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_quick_wrapf(err,
_("malformed property list for node-revision '%s' in '%s'"),
id_str->data, filename);
@@ -2803,8 +2996,8 @@ svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
if (err)
{
svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
-
- svn_error_clear(svn_stream_close(stream));
+
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_quick_wrapf(err,
_("malformed property list for node-revision '%s'"),
id_str->data);
@@ -2826,23 +3019,42 @@ svn_fs_fs__get_proplist(apr_hash_t **proplist_p,
}
svn_error_t *
+svn_fs_fs__create_changes_context(svn_fs_fs__changes_context_t **context,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool)
+{
+ svn_fs_fs__changes_context_t *result = apr_pcalloc(result_pool,
+ sizeof(*result));
+ result->fs = fs;
+ result->revision = rev;
+ result->rev_file_pool = result_pool;
+
+ *context = result;
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_fs_fs__get_changes(apr_array_header_t **changes,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *result_pool)
+ svn_fs_fs__changes_context_t *context,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
- apr_off_t changes_offset = SVN_FS_FS__ITEM_INDEX_CHANGES;
- svn_fs_fs__revision_file_t *revision_file;
+ apr_off_t item_index = SVN_FS_FS__ITEM_INDEX_CHANGES;
svn_boolean_t found;
- fs_fs_data_t *ffd = fs->fsap_data;
- apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+ fs_fs_data_t *ffd = context->fs->fsap_data;
+ svn_fs_fs__changes_list_t *changes_list;
+
+ pair_cache_key_t key;
+ key.revision = context->revision;
+ key.second = context->next;
/* try cache lookup first */
if (ffd->changes_cache)
{
- SVN_ERR(svn_cache__get((void **) changes, &found, ffd->changes_cache,
- &rev, result_pool));
+ SVN_ERR(svn_cache__get((void **)&changes_list, &found,
+ ffd->changes_cache, &key, result_pool));
}
else
{
@@ -2853,61 +3065,113 @@ svn_fs_fs__get_changes(apr_array_header_t **changes,
{
/* read changes from revision file */
- SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool));
- SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&revision_file, fs, rev,
- scratch_pool, scratch_pool));
+ if (!context->revision_file)
+ {
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(context->revision,
+ context->fs,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&context->revision_file,
+ context->fs,
+ context->revision,
+ context->rev_file_pool,
+ scratch_pool));
+ }
- if (use_block_read(fs))
+ if (use_block_read(context->fs))
{
- /* 'block-read' will also provide us with the desired data */
- SVN_ERR(block_read((void **)changes, fs,
- rev, SVN_FS_FS__ITEM_INDEX_CHANGES,
- revision_file, result_pool, scratch_pool));
+ /* 'block-read' will probably populate the cache with the data
+ * that we want. However, we won't want to force it to process
+ * very large change lists as part of this prefetching mechanism.
+ * Those would be better handled by the iterative code below. */
+ SVN_ERR(block_read(NULL, context->fs,
+ context->revision, SVN_FS_FS__ITEM_INDEX_CHANGES,
+ context->revision_file, scratch_pool,
+ scratch_pool));
+
+ /* This may succeed now ... */
+ SVN_ERR(svn_cache__get((void **)&changes_list, &found,
+ ffd->changes_cache, &key, result_pool));
}
- else
+
+ /* If we still have no data, read it here. */
+ if (!found)
{
+ apr_off_t changes_offset;
+
/* Addressing is very different for old formats
* (needs to read the revision trailer). */
- if (svn_fs_fs__use_log_addressing(fs))
- SVN_ERR(svn_fs_fs__item_offset(&changes_offset, fs,
- revision_file, rev, NULL,
- SVN_FS_FS__ITEM_INDEX_CHANGES,
- scratch_pool));
+ if (svn_fs_fs__use_log_addressing(context->fs))
+ {
+ SVN_ERR(svn_fs_fs__item_offset(&changes_offset, context->fs,
+ context->revision_file,
+ context->revision, NULL,
+ SVN_FS_FS__ITEM_INDEX_CHANGES,
+ scratch_pool));
+ }
else
- SVN_ERR(get_root_changes_offset(NULL, &changes_offset,
- revision_file, fs, rev,
- scratch_pool));
+ {
+ SVN_ERR(get_root_changes_offset(NULL, &changes_offset,
+ context->revision_file,
+ context->fs, context->revision,
+ scratch_pool));
+
+ /* This variable will be used for debug logging only. */
+ item_index = changes_offset;
+ }
/* Actual reading and parsing are the same, though. */
- SVN_ERR(aligned_seek(fs, revision_file->file, NULL, changes_offset,
+ SVN_ERR(aligned_seek(context->fs, context->revision_file->file,
+ NULL, changes_offset + context->next_offset,
scratch_pool));
- SVN_ERR(svn_fs_fs__read_changes(changes, revision_file->stream,
+
+ SVN_ERR(svn_fs_fs__read_changes(changes,
+ context->revision_file->stream,
+ SVN_FS_FS__CHANGES_BLOCK_SIZE,
result_pool, scratch_pool));
+ /* Construct the info object for the entries block we just read. */
+ changes_list = apr_pcalloc(scratch_pool, sizeof(*changes_list));
+ SVN_ERR(svn_io_file_get_offset(&changes_list->end_offset,
+ context->revision_file->file,
+ scratch_pool));
+ changes_list->end_offset -= changes_offset;
+ changes_list->start_offset = context->next_offset;
+ changes_list->count = (*changes)->nelts;
+ changes_list->changes = (change_t **)(*changes)->elts;
+ changes_list->eol = changes_list->count < SVN_FS_FS__CHANGES_BLOCK_SIZE;
+
/* cache for future reference */
if (ffd->changes_cache)
- {
- /* Guesstimate for the size of the in-cache representation. */
- apr_size_t estimated_size = (apr_size_t)250 * (*changes)->nelts;
-
- /* Don't even serialize data that probably won't fit into the
- * cache. This often implies that either CHANGES is very
- * large, memory is scarce or both. Having a huge temporary
- * copy would not be a good thing in either case. */
- if (svn_cache__is_cachable(ffd->changes_cache, estimated_size))
- SVN_ERR(svn_cache__set(ffd->changes_cache, &rev, *changes,
- scratch_pool));
- }
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &key, changes_list,
+ scratch_pool));
}
+ }
- SVN_ERR(svn_fs_fs__close_revision_file(revision_file));
+ if (found)
+ {
+ /* Return the block as a "proper" APR array. */
+ (*changes) = apr_array_make(result_pool, 0, sizeof(void *));
+ (*changes)->elts = (char *)changes_list->changes;
+ (*changes)->nelts = changes_list->count;
+ (*changes)->nalloc = changes_list->count;
+ }
+
+ /* Where to look next - if there is more data. */
+ context->next += (*changes)->nelts;
+ context->next_offset = changes_list->end_offset;
+ context->eol = changes_list->eol;
+
+ /* Close the revision file after we read all data. */
+ if (context->eol && context->revision_file)
+ {
+ SVN_ERR(svn_fs_fs__close_revision_file(context->revision_file));
+ context->revision_file = NULL;
}
- SVN_ERR(dbg_log_access(fs, rev, changes_offset, *changes,
+ SVN_ERR(dbg_log_access(context->fs, context->revision, item_index, *changes,
SVN_FS_FS__ITEM_TYPE_CHANGES, scratch_pool));
- svn_pool_destroy(scratch_pool);
return SVN_NO_ERROR;
}
@@ -2942,7 +3206,7 @@ init_rep_state(rep_state_t *rs,
rs->start = entry->offset + rs->header_size;
rs->current = rep_header->type == svn_fs_fs__rep_plain ? 0 : 4;
rs->size = entry->size - rep_header->header_size - 7;
- rs->ver = 1;
+ rs->ver = -1;
rs->chunk_index = 0;
rs->raw_window_cache = ffd->raw_window_cache;
rs->window_cache = ffd->txdelta_window_cache;
@@ -3000,6 +3264,9 @@ cache_windows(svn_fs_t *fs,
apr_pool_t *pool)
{
apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(auto_read_diff_version(rs, iterpool));
+
while (rs->current < rs->size)
{
apr_off_t end_offset;
@@ -3060,6 +3327,7 @@ cache_windows(svn_fs_t *fs,
window.end_offset = rs->current;
window.window.len = window_len;
window.window.data = buf;
+ window.ver = rs->ver;
/* cache the window now */
SVN_ERR(svn_cache__set(rs->raw_window_cache, &key, &window,
@@ -3181,9 +3449,8 @@ read_rep_header(svn_fs_fs__rep_header_t **rep_header,
/* Fetch the representation data (header, txdelta / plain windows)
* addressed by ENTRY->ITEM in FS and cache it if caches are enabled.
- * Read the data from the already open FILE and the wrapping
- * STREAM object. If MAX_OFFSET is not -1, don't read windows that start
- * at or beyond that offset.
+ * Read the data from REV_FILE. If MAX_OFFSET is not -1, don't read
+ * windows that start at or beyond that offset.
* Use SCRATCH_POOL for temporary allocations.
*/
static svn_error_t *
@@ -3191,7 +3458,6 @@ block_read_contents(svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_fs__p2l_entry_t* entry,
apr_off_t max_offset,
- apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
pair_cache_key_t header_key = { 0 };
@@ -3201,9 +3467,9 @@ block_read_contents(svn_fs_t *fs,
header_key.second = entry->item.number;
SVN_ERR(read_rep_header(&rep_header, fs, rev_file->stream, &header_key,
- result_pool, scratch_pool));
+ scratch_pool, scratch_pool));
SVN_ERR(block_read_windows(rep_header, fs, rev_file, entry, max_offset,
- result_pool, scratch_pool));
+ scratch_pool, scratch_pool));
return SVN_NO_ERROR;
}
@@ -3252,37 +3518,39 @@ read_item(svn_stream_t **stream,
_("Low-level checksum mismatch while reading\n"
"%s bytes of meta data at offset %s "
"for item %s in revision %ld"),
- apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->size),
- apr_psprintf(pool, "%" APR_OFF_T_FMT, entry->offset),
+ apr_off_t_toa(pool, entry->size),
+ apr_off_t_toa(pool, entry->offset),
apr_psprintf(pool, "%" APR_UINT64_T_FMT, entry->item.number),
entry->item.revision);
}
-/* If not already cached or if MUST_READ is set, read the changed paths
- * list addressed by ENTRY in FS and retúrn it in *CHANGES. Cache the
- * result if caching is enabled. Read the data from the already open
- * FILE and wrapping FILE_STREAM. Use POOL for allocations.
+/* If not already cached, read the changed paths list addressed by ENTRY in
+ * FS and cache it if it has no more than SVN_FS_FS__CHANGES_BLOCK_SIZE
+ * entries and caching is enabled. Read the data from REV_FILE.
+ * Allocate temporaries in SCRATCH_POOL.
*/
static svn_error_t *
-block_read_changes(apr_array_header_t **changes,
- svn_fs_t *fs,
+block_read_changes(svn_fs_t *fs,
svn_fs_fs__revision_file_t *rev_file,
svn_fs_fs__p2l_entry_t *entry,
- svn_boolean_t must_read,
- apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
svn_stream_t *stream;
- if (!must_read && !ffd->changes_cache)
+ apr_array_header_t *changes;
+
+ pair_cache_key_t key;
+ key.revision = entry->item.revision;
+ key.second = 0;
+
+ if (!ffd->changes_cache)
return SVN_NO_ERROR;
/* already in cache? */
- if (!must_read && ffd->changes_cache)
+ if (ffd->changes_cache)
{
svn_boolean_t is_cached;
- SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_cache,
- &entry->item.revision,
+ SVN_ERR(svn_cache__has_key(&is_cached, ffd->changes_cache, &key,
scratch_pool));
if (is_cached)
return SVN_NO_ERROR;
@@ -3290,22 +3558,40 @@ block_read_changes(apr_array_header_t **changes,
SVN_ERR(read_item(&stream, fs, rev_file, entry, scratch_pool));
- /* read changes from revision file */
- SVN_ERR(svn_fs_fs__read_changes(changes, stream, result_pool,
- scratch_pool));
+ /* Read changes from revision file. But read just past the first block to
+ enable us to determine whether the first block already hit the EOL.
- /* cache for future reference */
- if (ffd->changes_cache)
- SVN_ERR(svn_cache__set(ffd->changes_cache, &entry->item.revision,
- *changes, scratch_pool));
+ Note: A 100 entries block is already > 10kB on disk. With a 4kB default
+ disk block size, this function won't even be called for larger
+ changed paths lists. */
+ SVN_ERR(svn_fs_fs__read_changes(&changes, stream,
+ SVN_FS_FS__CHANGES_BLOCK_SIZE + 1,
+ scratch_pool, scratch_pool));
+
+ /* We can only cache small lists that don't need to be split up.
+ For longer lists, we miss the file offset info for the respective */
+ if (changes->nelts <= SVN_FS_FS__CHANGES_BLOCK_SIZE)
+ {
+ svn_fs_fs__changes_list_t changes_list;
+
+ /* Construct the info object for the entries block we just read. */
+ changes_list.end_offset = entry->size;
+ changes_list.start_offset = 0;
+ changes_list.count = changes->nelts;
+ changes_list.changes = (change_t **)changes->elts;
+ changes_list.eol = TRUE;
+
+ SVN_ERR(svn_cache__set(ffd->changes_cache, &key, &changes_list,
+ scratch_pool));
+ }
return SVN_NO_ERROR;
}
-/* If not already cached or if MUST_READ is set, read the nod revision
+/* If not already cached or if MUST_READ is set, read the node revision
* addressed by ENTRY in FS and retúrn it in *NODEREV_P. Cache the
- * result if caching is enabled. Read the data from the already open
- * FILE and wrapping FILE_STREAM. Use SCRATCH_POOL for temporary allocations.
+ * result if caching is enabled. Read the data from REV_FILE. Allocate
+ * *NODEREV_P in RESUSLT_POOL and allocate temporaries in SCRATCH_POOL.
*/
static svn_error_t *
block_read_noderev(node_revision_t **noderev_p,
@@ -3341,9 +3627,7 @@ block_read_noderev(node_revision_t **noderev_p,
/* read node rev from revision file */
SVN_ERR(svn_fs_fs__read_noderev(noderev_p, stream,
result_pool, scratch_pool));
-
- /* Workaround issue #4031: is-fresh-txn-root in revision files. */
- (*noderev_p)->is_fresh_txn_root = FALSE;
+ SVN_ERR(fixup_node_revision(fs, *noderev_p, scratch_pool));
if (ffd->node_revision_cache)
SVN_ERR(svn_cache__set(ffd->node_revision_cache, &key, *noderev_p,
@@ -3457,7 +3741,7 @@ block_read(void **result,
is_wanted
? -1
: block_start + ffd->block_size,
- pool, iterpool));
+ iterpool));
break;
case SVN_FS_FS__ITEM_TYPE_NODEREV:
@@ -3469,10 +3753,8 @@ block_read(void **result,
break;
case SVN_FS_FS__ITEM_TYPE_CHANGES:
- SVN_ERR(block_read_changes((apr_array_header_t **)&item,
- fs, revision_file,
- entry, is_result,
- pool, iterpool));
+ SVN_ERR(block_read_changes(fs, revision_file,
+ entry, iterpool));
break;
default:
@@ -3485,7 +3767,7 @@ block_read(void **result,
/* if we crossed a block boundary, read the remainder of
* the last block as well */
offset = entry->offset + entry->size;
- if (offset > block_start + ffd->block_size)
+ if (offset - block_start > ffd->block_size)
++run_count;
}
}
diff --git a/subversion/libsvn_fs_fs/cached_data.h b/subversion/libsvn_fs_fs/cached_data.h
index 07fa956765e02..7b25fcf43cdac 100644
--- a/subversion/libsvn_fs_fs/cached_data.h
+++ b/subversion/libsvn_fs_fs/cached_data.h
@@ -30,6 +30,18 @@
+/* Resolve a FSFS quirk: if REP in FS is a "PLAIN" representation, its
+ * EXPANDED_SIZE element may be 0, in which case its value has to be taken
+ * from SIZE.
+ *
+ * This function ensures that EXPANDED_SIZE in REP always contains the
+ * actual value. No-op if REP is NULL. Uses SCRATCH_POOL for temporaries.
+ */
+svn_error_t *
+svn_fs_fs__fixup_expanded_size(svn_fs_t *fs,
+ representation_t *rep,
+ apr_pool_t *scratch_pool);
+
/* Set *NODEREV_P to the node-revision for the node ID in FS. Do any
allocations in POOL. */
svn_error_t *
@@ -69,7 +81,7 @@ svn_fs_fs__rep_chain_length(int *chain_length,
svn_fs_t *fs,
apr_pool_t *scratch_pool);
-/* Set *CONTENTS to be a readable svn_stream_t that receives the text
+/* Set *CONTENTS_P to be a readable svn_stream_t that receives the text
representation REP as seen in filesystem FS. If CACHE_FULLTEXT is
not set, bypass fulltext cache lookup for this rep and don't put the
reconstructed fulltext into cache.
@@ -158,21 +170,22 @@ svn_fs_fs__get_proplist(apr_hash_t **proplist,
node_revision_t *noderev,
apr_pool_t *pool);
-/* Set *HAS_PROPS to TRUE if NODEREV has properties in FS, otherwise
- to FALSE. Use SCRATCH_POOL for temporary allocations. */
+/* Create a changes retrieval context object in *RESULT_POOL and return it
+ * in *CONTEXT. It will allow svn_fs_x__get_changes to fetch consecutive
+ * blocks (one per invocation) from REV's changed paths list in FS. */
svn_error_t *
-svn_fs_fs__has_props(svn_boolean_t *has_props,
- svn_fs_t *fs,
- node_revision_t *noderev,
- apr_pool_t *scratch_pool);
+svn_fs_fs__create_changes_context(svn_fs_fs__changes_context_t **context,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *result_pool);
-/* Fetch the list of change in revision REV in FS and return it in *CHANGES.
- * Allocate the result in POOL.
+/* Fetch the block of changes from the CONTEXT and return it in *CHANGES.
+ * Allocate the result in RESULT_POOL and use SCRATCH_POOL for temporaries.
*/
svn_error_t *
svn_fs_fs__get_changes(apr_array_header_t **changes,
- svn_fs_t *fs,
- svn_revnum_t rev,
- apr_pool_t *pool);
+ svn_fs_fs__changes_context_t *context,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
#endif
diff --git a/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c
index b54d69b3039a4..39b2e757e5c87 100644
--- a/subversion/libsvn_fs_fs/caching.c
+++ b/subversion/libsvn_fs_fs/caching.c
@@ -66,8 +66,9 @@ normalize_key_part(const char *original,
return normalized->data;
}
-/* *CACHE_TXDELTAS, *CACHE_FULLTEXTS flags will be set according to
- FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to use.
+/* *CACHE_TXDELTAS, *CACHE_FULLTEXTS, *CACHE_NODEPROPS flags will be set
+ according to FS->CONFIG. *CACHE_NAMESPACE receives the cache prefix to
+ use.
Use FS->pool for allocating the memcache and CACHE_NAMESPACE, and POOL
for temporary allocations. */
@@ -75,6 +76,7 @@ static svn_error_t *
read_config(const char **cache_namespace,
svn_boolean_t *cache_txdeltas,
svn_boolean_t *cache_fulltexts,
+ svn_boolean_t *cache_nodeprops,
svn_fs_t *fs,
apr_pool_t *pool)
{
@@ -94,11 +96,8 @@ read_config(const char **cache_namespace,
""),
pool);
- /* don't cache text deltas by default.
- * Once we reconstructed the fulltexts from the deltas,
- * these deltas are rarely re-used. Therefore, only tools
- * like svnadmin will activate this to speed up operations
- * dump and verify.
+ /* Cache text deltas by default.
+ * They tend to be smaller and have finer granularity than fulltexts.
*/
*cache_txdeltas
= svn_hash__get_bool(fs->config,
@@ -117,6 +116,14 @@ read_config(const char **cache_namespace,
SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS,
TRUE);
+ /* by default, cache nodeprops.
+ * Pre-1.10, this was controlled by the SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS
+ * configuration option which defaulted to TRUE.
+ */
+ *cache_nodeprops
+ = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS,
+ TRUE);
return SVN_NO_ERROR;
}
@@ -274,7 +281,8 @@ init_callbacks(svn_cache__t *cache,
* MEMBUFFER is not NULL. Fallbacks to inprocess cache if MEMCACHE and
* MEMBUFFER are NULL and pages is non-zero. Sets *CACHE_P to NULL
* otherwise. Use the given PRIORITY class for the new cache. If it
- * is 0, then use the default priority class.
+ * is 0, then use the default priority class. HAS_NAMESPACE indicates
+ * whether we prefixed this cache instance with a namespace.
*
* Unless NO_HANDLER is true, register an error handler that reports errors
* as warnings to the FS warning callback.
@@ -292,6 +300,7 @@ create_cache(svn_cache__t **cache_p,
apr_ssize_t klen,
const char *prefix,
apr_uint32_t priority,
+ svn_boolean_t has_namespace,
svn_fs_t *fs,
svn_boolean_t no_handler,
apr_pool_t *result_pool,
@@ -314,9 +323,12 @@ create_cache(svn_cache__t **cache_p,
}
else if (membuffer)
{
+ /* We assume caches with namespaces to be relatively short-lived,
+ * i.e. their data will not be needed after a while. */
SVN_ERR(svn_cache__create_membuffer_cache(
cache_p, membuffer, serializer, deserializer,
- klen, prefix, priority, FALSE, result_pool, scratch_pool));
+ klen, prefix, priority, FALSE, has_namespace,
+ result_pool, scratch_pool));
}
else if (pages)
{
@@ -348,26 +360,30 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
svn_boolean_t no_handler = ffd->fail_stop;
svn_boolean_t cache_txdeltas;
svn_boolean_t cache_fulltexts;
+ svn_boolean_t cache_nodeprops;
const char *cache_namespace;
+ svn_boolean_t has_namespace;
/* Evaluating the cache configuration. */
SVN_ERR(read_config(&cache_namespace,
&cache_txdeltas,
&cache_fulltexts,
+ &cache_nodeprops,
fs,
pool));
prefix = apr_pstrcat(pool, "ns:", cache_namespace, ":", prefix, SVN_VA_NULL);
+ has_namespace = strlen(cache_namespace) > 0;
membuffer = svn_cache__get_global_membuffer_cache();
/* General rules for assigning cache priorities:
*
* - Data that can be reconstructed from other elements has low prio
- * (e.g. fulltexts, directories etc.)
+ * (e.g. fulltexts etc.)
* - Index data required to find any of the other data has high prio
* (e.g. noderevs, L2P and P2L index pages)
- * - everthing else should use default prio
+ * - everything else should use default prio
*/
#ifdef SVN_DEBUG_CACHE_DUMP_STATS
@@ -386,34 +402,35 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
* commands, this is only going to contain a few entries (svnadmin
* dump/verify is an exception here), so to reduce overhead let's
* try to keep it to just one page. I estimate each entry has about
- * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t +
- * id_private_t + 3 strings for value, and the cache_entry); the
- * default pool size is 8192, so about a hundred should fit
- * comfortably. */
+ * 130 bytes of overhead (svn_revnum_t key, ID struct, and the cache_entry);
+ * the default pool size is 8192, so about a fifty should fit comfortably.
+ */
SVN_ERR(create_cache(&(ffd->rev_root_id_cache),
NULL,
membuffer,
- 1, 100,
+ 1, 50,
svn_fs_fs__serialize_id,
svn_fs_fs__deserialize_id,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "RRI", SVN_VA_NULL),
0,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
- /* Rough estimate: revision DAG nodes have size around 320 bytes, so
- * let's put 16 on a page. */
+ /* Rough estimate: revision DAG nodes have size around 1kBytes, so
+ * let's put 8 on a page. */
SVN_ERR(create_cache(&(ffd->rev_node_cache),
NULL,
membuffer,
- 1024, 16,
+ 1, 8,
svn_fs_fs__dag_serialize,
svn_fs_fs__dag_deserialize,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "DAG", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -425,28 +442,30 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->dir_cache),
NULL,
membuffer,
- 1024, 8,
+ 1, 8,
svn_fs_fs__serialize_dir_entries,
svn_fs_fs__deserialize_dir_entries,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "DIR", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
- /* Only 16 bytes per entry (a revision number + the corresponding offset).
- Since we want ~8k pages, that means 512 entries per page. */
+ /* 8 kBytes per entry (1000 revs / shared, one file offset per rev).
+ Covering about 8 pack files gives us an "o.k." hit rate. */
SVN_ERR(create_cache(&(ffd->packed_offset_cache),
NULL,
membuffer,
- 32, 1,
+ 8, 1,
svn_fs_fs__serialize_manifest,
svn_fs_fs__deserialize_manifest,
sizeof(svn_revnum_t),
apr_pstrcat(pool, prefix, "PACK-MANIFEST",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -455,12 +474,13 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->node_revision_cache),
NULL,
membuffer,
- 32, 32, /* ~200 byte / entry; 1k entries total */
+ 2, 16, /* ~500 byte / entry; 32 entries total */
svn_fs_fs__serialize_node_revision,
svn_fs_fs__deserialize_node_revision,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "NODEREVS", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -469,12 +489,13 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->rep_header_cache),
NULL,
membuffer,
- 1, 1000, /* ~8 bytes / entry; 1k entries total */
+ 1, 200, /* ~40 bytes / entry; 200 entries total */
svn_fs_fs__serialize_rep_header,
svn_fs_fs__deserialize_rep_header,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "REPHEADER", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -486,9 +507,25 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
1, 8, /* 1k / entry; 8 entries total, rarely used */
svn_fs_fs__serialize_changes,
svn_fs_fs__deserialize_changes,
- sizeof(svn_revnum_t),
+ sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "CHANGES", SVN_VA_NULL),
0,
+ has_namespace,
+ fs,
+ no_handler,
+ fs->pool, pool));
+
+ /* if enabled, cache revprops */
+ SVN_ERR(create_cache(&(ffd->revprop_cache),
+ NULL,
+ membuffer,
+ 8, 20, /* ~400 bytes / entry, capa for ~2 packs */
+ svn_fs_fs__serialize_revprops,
+ svn_fs_fs__deserialize_revprops,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "REVPROP", SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
+ TRUE, /* contents is short-lived */
fs,
no_handler,
fs->pool, pool));
@@ -499,26 +536,13 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->fulltext_cache),
ffd->memcache,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "TEXT", SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
- fs,
- no_handler,
- fs->pool, pool));
-
- SVN_ERR(create_cache(&(ffd->properties_cache),
- NULL,
- membuffer,
- 0, 0, /* Do not use inprocess cache */
- svn_fs_fs__serialize_properties,
- svn_fs_fs__deserialize_properties,
- sizeof(pair_cache_key_t),
- apr_pstrcat(pool, prefix, "PROP",
- SVN_VA_NULL),
- SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -526,13 +550,14 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->mergeinfo_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_mergeinfo,
svn_fs_fs__deserialize_mergeinfo,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "MERGEINFO",
SVN_VA_NULL),
0,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -540,13 +565,14 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->mergeinfo_existence_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
APR_HASH_KEY_STRING,
apr_pstrcat(pool, prefix, "HAS_MERGEINFO",
SVN_VA_NULL),
0,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -554,24 +580,47 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
else
{
ffd->fulltext_cache = NULL;
- ffd->properties_cache = NULL;
ffd->mergeinfo_cache = NULL;
ffd->mergeinfo_existence_cache = NULL;
}
+ /* if enabled, cache node properties */
+ if (cache_nodeprops)
+ {
+ SVN_ERR(create_cache(&(ffd->properties_cache),
+ NULL,
+ membuffer,
+ 0, 0, /* Do not use the inprocess cache */
+ svn_fs_fs__serialize_properties,
+ svn_fs_fs__deserialize_properties,
+ sizeof(pair_cache_key_t),
+ apr_pstrcat(pool, prefix, "PROP",
+ SVN_VA_NULL),
+ SVN_CACHE__MEMBUFFER_DEFAULT_PRIORITY,
+ has_namespace,
+ fs,
+ no_handler,
+ fs->pool, pool));
+ }
+ else
+ {
+ ffd->properties_cache = NULL;
+ }
+
/* if enabled, cache text deltas and their combinations */
if (cache_txdeltas)
{
SVN_ERR(create_cache(&(ffd->raw_window_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_raw_window,
svn_fs_fs__deserialize_raw_window,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "RAW_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -579,13 +628,14 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->txdelta_window_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
svn_fs_fs__serialize_txdelta_window,
svn_fs_fs__deserialize_txdelta_window,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "TXDELTA_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -593,13 +643,14 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->combined_window_cache),
NULL,
membuffer,
- 0, 0, /* Do not use inprocess cache */
+ 0, 0, /* Do not use the inprocess cache */
/* Values are svn_stringbuf_t */
NULL, NULL,
sizeof(window_cache_key_t),
apr_pstrcat(pool, prefix, "COMBINED_WINDOW",
SVN_VA_NULL),
SVN_CACHE__MEMBUFFER_LOW_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -613,28 +664,34 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
SVN_ERR(create_cache(&(ffd->l2p_header_cache),
NULL,
membuffer,
- 64, 16, /* entry size varies but we must cover
- a reasonable number of revisions (1k) */
+ 8, 16, /* entry size varies but we must cover a
+ reasonable number of rev / pack files
+ to allow for delta chains to be walked
+ efficiently etc. */
svn_fs_fs__serialize_l2p_header,
svn_fs_fs__deserialize_l2p_header,
sizeof(pair_cache_key_t),
apr_pstrcat(pool, prefix, "L2P_HEADER",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->l2p_page_cache),
NULL,
membuffer,
- 64, 16, /* entry size varies but we must cover
- a reasonable number of revisions (1k) */
+ 8, 16, /* entry size varies but we must cover a
+ reasonable number of rev / pack files
+ to allow for delta chains to be walked
+ efficiently etc. */
svn_fs_fs__serialize_l2p_page,
svn_fs_fs__deserialize_l2p_page,
sizeof(svn_fs_fs__page_cache_key_t),
apr_pstrcat(pool, prefix, "L2P_PAGE",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -648,19 +705,21 @@ svn_fs_fs__initialize_caches(svn_fs_t *fs,
apr_pstrcat(pool, prefix, "P2L_HEADER",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
SVN_ERR(create_cache(&(ffd->p2l_page_cache),
NULL,
membuffer,
- 4, 16, /* Variably sized entries. Rarely used. */
+ 4, 1, /* Variably sized entries. Rarely used. */
svn_fs_fs__serialize_p2l_page,
svn_fs_fs__deserialize_p2l_page,
sizeof(svn_fs_fs__page_cache_key_t),
apr_pstrcat(pool, prefix, "P2L_PAGE",
(char *)NULL),
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ has_namespace,
fs,
no_handler,
fs->pool, pool));
@@ -773,18 +832,7 @@ svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
-
- /* Transaction content needs to be carefully prefixed to virtually
- eliminate any chance for conflicts. The (repo, txn_id) pair
- should be unique but if a transaction fails, it might be possible
- to start a new transaction later that receives the same id.
- Therefore, throw in a uuid as well - just to be sure. */
- const char *prefix = apr_pstrcat(pool,
- "fsfs:", fs->uuid,
- "/", fs->path,
- ":", txn_id,
- ":", svn_uuid_generate(pool), ":",
- SVN_VA_NULL);
+ const char *prefix;
/* We don't support caching for concurrent transactions in the SAME
* FSFS session. Maybe, you forgot to clean POOL. */
@@ -796,17 +844,40 @@ svn_fs_fs__initialize_txn_caches(svn_fs_t *fs,
return SVN_NO_ERROR;
}
+ /* Transaction content needs to be carefully prefixed to virtually
+ eliminate any chance for conflicts. The (repo, txn_id) pair
+ should be unique but if the filesystem format doesn't store the
+ global transaction ID via the txn-current file, and a transaction
+ fails, it might be possible to start a new transaction later that
+ receives the same id. For such older formats, throw in an uuid as
+ well -- just to be sure. */
+ if (ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+ prefix = apr_pstrcat(pool,
+ "fsfs:", fs->uuid,
+ "/", fs->path,
+ ":", txn_id,
+ ":", "TXNDIR",
+ SVN_VA_NULL);
+ else
+ prefix = apr_pstrcat(pool,
+ "fsfs:", fs->uuid,
+ "/", fs->path,
+ ":", txn_id,
+ ":", svn_uuid_generate(pool),
+ ":", "TXNDIR",
+ SVN_VA_NULL);
+
/* create a txn-local directory cache */
SVN_ERR(create_cache(&ffd->txn_dir_cache,
NULL,
svn_cache__get_global_membuffer_cache(),
1024, 8,
- svn_fs_fs__serialize_dir_entries,
+ svn_fs_fs__serialize_txndir_entries,
svn_fs_fs__deserialize_dir_entries,
APR_HASH_KEY_STRING,
- apr_pstrcat(pool, prefix, "TXNDIR",
- SVN_VA_NULL),
+ prefix,
SVN_CACHE__MEMBUFFER_HIGH_PRIORITY,
+ TRUE, /* The TXN-ID is our namespace. */
fs,
TRUE,
pool, pool));
diff --git a/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c
index d21c17c4b9aa5..714235dd0ee08 100644
--- a/subversion/libsvn_fs_fs/dag.c
+++ b/subversion/libsvn_fs_fs/dag.c
@@ -527,9 +527,7 @@ svn_fs_fs__dag_has_props(svn_boolean_t *has_props,
{
/* Properties are stored as a standard hash stream,
always ending with "END\n" (4 bytes) */
- *has_props = (noderev->prop_rep->expanded_size > 4
- || (noderev->prop_rep->expanded_size == 0
- && noderev->prop_rep->size > 4));
+ *has_props = noderev->prop_rep->expanded_size > 4;
}
return SVN_NO_ERROR;
@@ -755,8 +753,7 @@ svn_fs_fs__dag_clone_child(dag_node_t **child_p,
noderev->copyfrom_rev = SVN_INVALID_REVNUM;
noderev->predecessor_id = svn_fs_fs__id_copy(cur_entry->id, pool);
- if (noderev->predecessor_count != -1)
- noderev->predecessor_count++;
+ noderev->predecessor_count++;
noderev->created_path = svn_fspath__join(parent_path, name, pool);
SVN_ERR(svn_fs_fs__create_successor(&new_node_id, fs, cur_entry->id,
@@ -1269,8 +1266,7 @@ svn_fs_fs__dag_copy(dag_node_t *to_node,
/* Create a successor with its predecessor pointing at the copy
source. */
to_noderev->predecessor_id = svn_fs_fs__id_copy(src_id, pool);
- if (to_noderev->predecessor_count != -1)
- to_noderev->predecessor_count++;
+ to_noderev->predecessor_count++;
to_noderev->created_path =
svn_fspath__join(svn_fs_fs__dag_get_created_path(to_node), entry,
pool);
@@ -1425,8 +1421,7 @@ svn_fs_fs__dag_update_ancestry(dag_node_t *target,
target_noderev->predecessor_id = source->id;
target_noderev->predecessor_count = source_noderev->predecessor_count;
- if (target_noderev->predecessor_count != -1)
- target_noderev->predecessor_count++;
+ target_noderev->predecessor_count++;
return svn_fs_fs__put_node_revision(target->fs, target->id, target_noderev,
FALSE, pool);
diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c
index 1978798917b95..508ecdbaba334 100644
--- a/subversion/libsvn_fs_fs/fs.c
+++ b/subversion/libsvn_fs_fs/fs.c
@@ -27,7 +27,6 @@
#include <apr_general.h>
#include <apr_pools.h>
#include <apr_file_io.h>
-#include <apr_thread_mutex.h>
#include "svn_fs.h"
#include "svn_delta.h"
@@ -135,8 +134,29 @@ fs_serialized_init(svn_fs_t *fs, apr_pool_t *common_pool, apr_pool_t *pool)
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_fs_fs__initialize_shared_data(svn_fs_t *fs,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ SVN_MUTEX__WITH_LOCK(common_pool_lock,
+ fs_serialized_init(fs, common_pool, pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+fs_refresh_revprops(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_fs__reset_revprop_cache(fs);
+
+ return SVN_NO_ERROR;
+}
+
/* This function is provided for Subversion 1.0.x compatibility. It
has no effect for fsfs backed Subversion filesystems. It conforms
to the fs_library_vtable_t.bdb_set_errcall() API. */
@@ -238,6 +258,7 @@ fs_set_uuid(svn_fs_t *fs,
/* The vtable associated with a specific open filesystem. */
static fs_vtable_t fs_vtable = {
svn_fs_fs__youngest_rev,
+ fs_refresh_revprops,
svn_fs_fs__revision_prop,
svn_fs_fs__get_revision_proplist,
svn_fs_fs__change_rev_prop,
@@ -270,6 +291,8 @@ initialize_fs_struct(svn_fs_t *fs)
{
fs_fs_data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
ffd->use_log_addressing = FALSE;
+ ffd->revprop_prefix = 0;
+ ffd->flush_to_disk = TRUE;
fs->vtable = &fs_vtable;
fs->fsap_data = ffd;
@@ -293,18 +316,18 @@ static svn_error_t *
fs_create(svn_fs_t *fs,
const char *path,
svn_mutex__t *common_pool_lock,
- apr_pool_t *pool,
+ apr_pool_t *scratch_pool,
apr_pool_t *common_pool)
{
SVN_ERR(svn_fs__check_fs(fs, FALSE));
SVN_ERR(initialize_fs_struct(fs));
- SVN_ERR(svn_fs_fs__create(fs, path, pool));
+ SVN_ERR(svn_fs_fs__create(fs, path, scratch_pool));
- SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(fs, scratch_pool));
SVN_MUTEX__WITH_LOCK(common_pool_lock,
- fs_serialized_init(fs, common_pool, pool));
+ fs_serialized_init(fs, common_pool, scratch_pool));
return SVN_NO_ERROR;
}
@@ -322,10 +345,10 @@ static svn_error_t *
fs_open(svn_fs_t *fs,
const char *path,
svn_mutex__t *common_pool_lock,
- apr_pool_t *pool,
+ apr_pool_t *scratch_pool,
apr_pool_t *common_pool)
{
- apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
SVN_ERR(svn_fs__check_fs(fs, FALSE));
@@ -481,28 +504,19 @@ fs_hotcopy(svn_fs_t *src_fs,
apr_pool_t *pool,
apr_pool_t *common_pool)
{
- /* Open the source repo as usual. */
SVN_ERR(fs_open(src_fs, src_path, common_pool_lock, pool, common_pool));
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
- /* Test target repo when in INCREMENTAL mode, initialize it when not.
- * For this, we need our FS internal data structures to be temporarily
- * available. */
+ SVN_ERR(svn_fs__check_fs(dst_fs, FALSE));
SVN_ERR(initialize_fs_struct(dst_fs));
- SVN_ERR(svn_fs_fs__hotcopy_prepare_target(src_fs, dst_fs, dst_path,
- incremental, pool));
- uninitialize_fs_struct(dst_fs);
-
- /* Now, the destination repo should open just fine. */
- SVN_ERR(fs_open(dst_fs, dst_path, common_pool_lock, pool, common_pool));
- if (cancel_func)
- SVN_ERR(cancel_func(cancel_baton));
-
- /* Now, we may copy data as needed ... */
- return svn_fs_fs__hotcopy(src_fs, dst_fs, incremental,
- notify_func, notify_baton,
- cancel_func, cancel_baton, pool);
+
+ /* In INCREMENTAL mode, svn_fs_fs__hotcopy() will open DST_FS.
+ Otherwise, it's not an FS yet --- possibly just an empty dir --- so
+ can't be opened.
+ */
+ return svn_fs_fs__hotcopy(src_fs, dst_fs, src_path, dst_path,
+ incremental, notify_func, notify_baton,
+ cancel_func, cancel_baton, common_pool_lock,
+ pool, common_pool);
}
diff --git a/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h
index c75eafbd5c09d..d11d923d9b057 100644
--- a/subversion/libsvn_fs_fs/fs.h
+++ b/subversion/libsvn_fs_fs/fs.h
@@ -37,7 +37,7 @@
#include "private/svn_sqlite.h"
#include "private/svn_mutex.h"
-#include "id.h"
+#include "rev_file.h"
#ifdef __cplusplus
extern "C" {
@@ -82,8 +82,6 @@ extern "C" {
/* Names of special files and file extensions for transactions */
#define PATH_CHANGES "changes" /* Records changes made so far */
#define PATH_TXN_PROPS "props" /* Transaction properties */
-#define PATH_TXN_PROPS_FINAL "props-final" /* Final transaction properties
- before moving to revprops */
#define PATH_NEXT_IDS "next-ids" /* Next temporary ID assignments */
#define PATH_PREFIX_NODE "node." /* Prefix for node filename */
#define PATH_EXT_TXN ".txn" /* Extension of txn dir */
@@ -119,6 +117,8 @@ extern "C" {
#define CONFIG_OPTION_P2L_PAGE_SIZE "p2l-page-size"
#define CONFIG_SECTION_DEBUG "debug"
#define CONFIG_OPTION_PACK_AFTER_COMMIT "pack-after-commit"
+#define CONFIG_OPTION_VERIFY_BEFORE_COMMIT "verify-before-commit"
+#define CONFIG_OPTION_COMPRESSION "compression"
/* The format number of this filesystem.
This is independent of the repository format number, and
@@ -127,7 +127,7 @@ extern "C" {
Note: If you bump this, please update the switch statement in
svn_fs_fs__create() as well.
*/
-#define SVN_FS_FS__FORMAT_NUMBER 7
+#define SVN_FS_FS__FORMAT_NUMBER 8
/* The minimum format number that supports svndiff version 1. */
#define SVN_FS_FS__MIN_SVNDIFF1_FORMAT 2
@@ -163,6 +163,9 @@ extern "C" {
* issues with very old servers, restrict those options to the 1.6+ format*/
#define SVN_FS_FS__MIN_DELTIFICATION_FORMAT 4
+/* The minimum format number that supports a configuration file (fsfs.conf) */
+#define SVN_FS_FS__MIN_CONFIG_FILE 4
+
/* The 1.7-dev format, never released, that packed revprops into SQLite
revprops.db . */
#define SVN_FS_FS__PACKED_REVPROP_SQLITE_DEV_FORMAT 5
@@ -182,8 +185,20 @@ extern "C" {
/* Minimum format number that supports per-instance filesystem IDs. */
#define SVN_FS_FS__MIN_INSTANCE_ID_FORMAT 7
-/* The minimum format number that supports a configuration file (fsfs.conf) */
-#define SVN_FS_FS__MIN_CONFIG_FILE 4
+/* The minimum format number that supports svndiff version 2. */
+#define SVN_FS_FS__MIN_SVNDIFF2_FORMAT 8
+
+/* The minimum format number that supports the special notation ("-")
+ for optional values that are not present in the representation strings,
+ such as SHA1 or the uniquifier. For example:
+
+ 15 0 563 7809 28ef320a82e7bd11eebdf3502d69e608 - 14-g/_5
+ */
+#define SVN_FS_FS__MIN_REP_STRING_OPTIONAL_VALUES_FORMAT 8
+
+ /* The minimum format number that supports V2 schema of the rep-cache.db
+ database. */
+#define SVN_FS_FS__MIN_REP_CACHE_SCHEMA_V2_FORMAT 8
/* On most operating systems apr implements file locks per process, not
per file. On Windows apr implements the locking as per file handle
@@ -195,6 +210,11 @@ extern "C" {
#define SVN_FS_FS__USE_LOCK_MUTEX 0
#endif
+/* Maximum number of changes we deliver per request when listing the
+ changed paths for a given revision. Anything > 0 will do.
+ At 100..300 bytes per entry, this limits the allocation to ~30kB. */
+#define SVN_FS_FS__CHANGES_BLOCK_SIZE 100
+
/* Private FSFS-specific data shared between all svn_txn_t objects that
relate to a particular transaction in a filesystem (as identified
by transaction id and filesystem UUID). Objects of this type are
@@ -291,6 +311,13 @@ typedef struct window_cache_key_t
apr_uint64_t item_index;
} window_cache_key_t;
+typedef enum compression_type_t
+{
+ compression_type_none,
+ compression_type_zlib,
+ compression_type_lz4
+} compression_type_t;
+
/* Private (non-shared) FSFS-specific data for each svn_fs_t object.
Any caches in here may be NULL. */
typedef struct fs_fs_data_t
@@ -353,6 +380,15 @@ typedef struct fs_fs_data_t
rep key (revision/offset) to svn_stringbuf_t. */
svn_cache__t *fulltext_cache;
+ /* The current prefix to be used for revprop cache entries.
+ If this is 0, a new unique prefix must be chosen. */
+ apr_uint64_t revprop_prefix;
+
+ /* Revision property cache. Maps from (rev,prefix) to apr_hash_t.
+ Unparsed svn_string_t representations of the serialized hash
+ will be written to the cache but the getter returns apr_hash_t. */
+ svn_cache__t *revprop_cache;
+
/* Node properties cache. Maps from rep key to apr_hash_t. */
svn_cache__t *properties_cache;
@@ -376,8 +412,8 @@ typedef struct fs_fs_data_t
/* Cache for node_revision_t objects; the key is (revision, item_index) */
svn_cache__t *node_revision_cache;
- /* Cache for change lists as APR arrays of change_t * objects; the key
- is the revision */
+ /* Cache for change lists n blocks as svn_fs_fs__changes_list_t * objects;
+ the key is the (revision, first-element-in-block) pair. */
svn_cache__t *changes_cache;
/* Cache for svn_fs_fs__rep_header_t objects; the key is a
@@ -457,18 +493,27 @@ typedef struct fs_fs_data_t
* deltification history after which skip deltas will be used. */
apr_int64_t max_linear_deltification;
- /* Compression level to use with txdelta storage format in new revs. */
+ /* Compression type to use with txdelta storage format in new revs. */
+ compression_type_t delta_compression_type;
+
+ /* Compression level (currently, only used with compression_type_zlib). */
int delta_compression_level;
/* Pack after every commit. */
svn_boolean_t pack_after_commit;
+ /* Verify each new revision before commit. */
+ svn_boolean_t verify_before_commit;
+
/* Per-instance filesystem ID, which provides an additional level of
uniqueness for filesystems that share the same UUID, but should
still be distinguishable (e.g. backups produced by svn_fs_hotcopy()
or dump / load cycles). */
const char *instance_id;
+ /* Ensure that all filesystem changes are written to disk. */
+ svn_boolean_t flush_to_disk;
+
/* Pointer to svn_fs_open. */
svn_error_t *(*svn_fs_open_)(svn_fs_t **, const char *, apr_hash_t *,
apr_pool_t *, apr_pool_t *);
@@ -515,7 +560,7 @@ typedef struct representation_t
/* Revision where this representation is located. */
svn_revnum_t revision;
- /* Item index with the the revision. */
+ /* Item index with the revision. */
apr_uint64_t item_index;
/* The size of the representation in bytes as seen in the revision
@@ -523,7 +568,14 @@ typedef struct representation_t
svn_filesize_t size;
/* The size of the fulltext of the representation. If this is 0,
- * the fulltext size is equal to representation size in the rev file, */
+ * for a plain rep, the real fulltext size is equal to the SIZE field.
+ * For a delta rep, this field is always the real fulltext size.
+ *
+ * Note that svn_fs_fs__fixup_expanded_size() checks for these special
+ * cases and ensures that this field contains the actual value. We call
+ * it early after reading a representation struct, so most code does not
+ * have to worry about it.
+ */
svn_filesize_t expanded_size;
/* Is this a representation (still) within a transaction? */
@@ -568,8 +620,8 @@ typedef struct node_revision_t
svn_revnum_t copyroot_rev;
const char *copyroot_path;
- /* number of predecessors this node revision has (recursively), or
- -1 if not known (for backward compatibility). */
+ /* Number of predecessors this node revision has (recursively).
+ A difference from the BDB backend is that it cannot be -1. */
int predecessor_count;
/* representation key for this node's properties. may be NULL if
@@ -606,6 +658,45 @@ typedef struct change_t
svn_fs_path_change2_t info;
} change_t;
+
+/*** Context for reading changed paths lists iteratively. */
+typedef struct svn_fs_fs__changes_context_t
+{
+ /* Repository to fetch from. */
+ svn_fs_t *fs;
+
+ /* Revision that we read from. */
+ svn_revnum_t revision;
+
+ /* Revision file object to use when needed. NULL until the first access. */
+ svn_fs_fs__revision_file_t *revision_file;
+
+ /* Pool to create REVISION_FILE in. */
+ apr_pool_t *rev_file_pool;
+
+ /* Index of the next change to fetch. */
+ apr_size_t next;
+
+ /* Offset, within the changed paths list on disk, of the next change to
+ fetch. */
+ apr_off_t next_offset;
+
+ /* Has the end of the list been reached? */
+ svn_boolean_t eol;
+
+} svn_fs_fs__changes_context_t;
+
+/*** Directory (only used at the cache interface) ***/
+typedef struct svn_fs_fs__dir_data_t
+{
+ /* Contents, i.e. all directory entries, sorted by name. */
+ apr_array_header_t *entries;
+
+ /* SVN_INVALID_FILESIZE for committed data, otherwise the length of the
+ * in-txn on-disk representation of that directory. */
+ svn_filesize_t txn_filesize;
+} svn_fs_fs__dir_data_t;
+
#ifdef __cplusplus
}
diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c
index 103458d8ef0ab..5cb1362d84515 100644
--- a/subversion/libsvn_fs_fs/fs_fs.c
+++ b/subversion/libsvn_fs_fs/fs_fs.c
@@ -623,8 +623,9 @@ svn_fs_fs__write_format(svn_fs_t *fs,
}
else
{
- SVN_ERR(svn_io_write_atomic(path, sb->data, sb->len,
- NULL /* copy_perms_path */, pool));
+ SVN_ERR(svn_io_write_atomic2(path, sb->data, sb->len,
+ NULL /* copy_perms_path */,
+ ffd->flush_to_disk, pool));
}
/* And set the perms to make it read only */
@@ -682,6 +683,60 @@ verify_block_size(apr_int64_t block_size,
return SVN_NO_ERROR;
}
+static svn_error_t *
+parse_compression_option(compression_type_t *compression_type_p,
+ int *compression_level_p,
+ const char *value)
+{
+ compression_type_t type;
+ int level;
+ svn_boolean_t is_valid = TRUE;
+
+ /* compression = none | lz4 | zlib | zlib-1 ... zlib-9 */
+ if (strcmp(value, "none") == 0)
+ {
+ type = compression_type_none;
+ level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
+ }
+ else if (strcmp(value, "lz4") == 0)
+ {
+ type = compression_type_lz4;
+ level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ else if (strncmp(value, "zlib", 4) == 0)
+ {
+ const char *p = value + 4;
+
+ type = compression_type_zlib;
+ if (*p == 0)
+ {
+ level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ else if (*p == '-')
+ {
+ p++;
+ SVN_ERR(svn_cstring_atoi(&level, p));
+ if (level < 1 || level > 9)
+ is_valid = FALSE;
+ }
+ else
+ is_valid = FALSE;
+ }
+ else
+ {
+ is_valid = FALSE;
+ }
+
+ if (!is_valid)
+ return svn_error_createf(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Invalid 'compression' value '%s' in the config"),
+ value);
+
+ *compression_type_p = type;
+ *compression_level_p = level;
+ return SVN_NO_ERROR;
+}
+
/* Read the configuration information of the file system at FS_PATH
* and set the respective values in FFD. Use pools as usual.
*/
@@ -708,8 +763,6 @@ read_config(fs_fs_data_t *ffd,
/* Initialize deltification settings in ffd. */
if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
{
- apr_int64_t compression_level;
-
SVN_ERR(svn_config_get_bool(config, &ffd->deltify_directories,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_ENABLE_DIR_DELTIFICATION,
@@ -726,14 +779,6 @@ read_config(fs_fs_data_t *ffd,
CONFIG_SECTION_DELTIFICATION,
CONFIG_OPTION_MAX_LINEAR_DELTIFICATION,
SVN_FS_FS_MAX_LINEAR_DELTIFICATION));
-
- SVN_ERR(svn_config_get_int64(config, &compression_level,
- CONFIG_SECTION_DELTIFICATION,
- CONFIG_OPTION_COMPRESSION_LEVEL,
- SVN_DELTA_COMPRESSION_LEVEL_DEFAULT));
- ffd->delta_compression_level
- = (int)MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE, compression_level),
- SVN_DELTA_COMPRESSION_LEVEL_MAX);
}
else
{
@@ -741,7 +786,6 @@ read_config(fs_fs_data_t *ffd,
ffd->deltify_properties = FALSE;
ffd->max_deltification_walk = SVN_FS_FS_MAX_DELTIFICATION_WALK;
ffd->max_linear_deltification = SVN_FS_FS_MAX_LINEAR_DELTIFICATION;
- ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
}
/* Initialize revprop packing settings in ffd. */
@@ -755,8 +799,8 @@ read_config(fs_fs_data_t *ffd,
CONFIG_SECTION_PACKED_REVPROPS,
CONFIG_OPTION_REVPROP_PACK_SIZE,
ffd->compress_packed_revprops
- ? 0x10
- : 0x4));
+ ? 0x40
+ : 0x10));
ffd->revprop_pack_size *= 1024;
}
@@ -816,6 +860,83 @@ read_config(fs_fs_data_t *ffd,
ffd->pack_after_commit = FALSE;
}
+ /* Initialize compression settings in ffd. */
+ if (ffd->format >= SVN_FS_FS__MIN_DELTIFICATION_FORMAT)
+ {
+ const char *compression_val;
+ const char *compression_level_val;
+
+ svn_config_get(config, &compression_val,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_COMPRESSION, NULL);
+ svn_config_get(config, &compression_level_val,
+ CONFIG_SECTION_DELTIFICATION,
+ CONFIG_OPTION_COMPRESSION_LEVEL, NULL);
+ if (compression_val && compression_level_val)
+ {
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("The 'compression' and 'compression-level' "
+ "config options are mutually exclusive"));
+ }
+ else if (compression_val)
+ {
+ SVN_ERR(parse_compression_option(&ffd->delta_compression_type,
+ &ffd->delta_compression_level,
+ compression_val));
+ if (ffd->delta_compression_type == compression_type_lz4 &&
+ ffd->format < SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
+ {
+ return svn_error_create(SVN_ERR_BAD_CONFIG_VALUE, NULL,
+ _("Compression type 'lz4' requires "
+ "filesystem format 8 or higher"));
+ }
+ }
+ else if (compression_level_val)
+ {
+ /* Handle the deprecated 'compression-level' option. */
+ ffd->delta_compression_type = compression_type_zlib;
+ SVN_ERR(svn_cstring_atoi(&ffd->delta_compression_level,
+ compression_level_val));
+ ffd->delta_compression_level =
+ MIN(MAX(SVN_DELTA_COMPRESSION_LEVEL_NONE,
+ ffd->delta_compression_level),
+ SVN_DELTA_COMPRESSION_LEVEL_MAX);
+ }
+ else
+ {
+ /* Nothing specified explicitly, use the default settings:
+ * LZ4 compression for formats supporting it and zlib otherwise. */
+ if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF2_FORMAT)
+ ffd->delta_compression_type = compression_type_lz4;
+ else
+ ffd->delta_compression_type = compression_type_zlib;
+
+ ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ }
+ else if (ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT)
+ {
+ ffd->delta_compression_type = compression_type_zlib;
+ ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_DEFAULT;
+ }
+ else
+ {
+ ffd->delta_compression_type = compression_type_none;
+ ffd->delta_compression_level = SVN_DELTA_COMPRESSION_LEVEL_NONE;
+ }
+
+#ifdef SVN_DEBUG
+ SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
+ CONFIG_SECTION_DEBUG,
+ CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
+ TRUE));
+#else
+ SVN_ERR(svn_config_get_bool(config, &ffd->verify_before_commit,
+ CONFIG_SECTION_DEBUG,
+ CONFIG_OPTION_VERIFY_BEFORE_COMMIT,
+ FALSE));
+#endif
+
/* memcached configuration */
SVN_ERR(svn_cache__make_memcache_from_config(&ffd->memcache, config,
result_pool, scratch_pool));
@@ -934,23 +1055,30 @@ write_config(svn_fs_t *fs,
"### For 1.8, the default value is 16; earlier versions use 1." NL
"# " CONFIG_OPTION_MAX_LINEAR_DELTIFICATION " = 16" NL
"###" NL
-"### After deltification, we compress the data through zlib to minimize on-" NL
-"### disk size. That can be an expensive and ineffective process. This" NL
-"### setting controls the usage of zlib in future revisions." NL
-"### Revisions with highly compressible data in them may shrink in size" NL
-"### if the setting is increased but may take much longer to commit. The" NL
-"### time taken to uncompress that data again is widely independent of the" NL
-"### compression level." NL
-"### Compression will be ineffective if the incoming content is already" NL
-"### highly compressed. In that case, disabling the compression entirely" NL
-"### will speed up commits as well as reading the data. Repositories with" NL
-"### many small compressible files (source code) but also a high percentage" NL
-"### of large incompressible ones (artwork) may benefit from compression" NL
-"### levels lowered to e.g. 1." NL
-"### Valid values are 0 to 9 with 9 providing the highest compression ratio" NL
-"### and 0 disabling it altogether." NL
-"### The default value is 5." NL
-"# " CONFIG_OPTION_COMPRESSION_LEVEL " = 5" NL
+"### After deltification, we compress the data to minimize on-disk size." NL
+"### This setting controls the compression algorithm, which will be used in" NL
+"### future revisions. It can be used to either disable compression or to" NL
+"### select between available algorithms (zlib, lz4). zlib is a general-" NL
+"### purpose compression algorithm. lz4 is a fast compression algorithm" NL
+"### which should be preferred for repositories with large and, possibly," NL
+"### incompressible files. Note that the compression ratio of lz4 is" NL
+"### usually lower than the one provided by zlib, but using it can" NL
+"### significantly speed up commits as well as reading the data." NL
+"### lz4 compression algorithm is supported, starting from format 8" NL
+"### repositories, available in Subversion 1.10 and higher." NL
+"### The syntax of this option is:" NL
+"### " CONFIG_OPTION_COMPRESSION " = none | lz4 | zlib | zlib-1 ... zlib-9" NL
+"### Versions prior to Subversion 1.10 will ignore this option." NL
+"### The default value is 'lz4' if supported by the repository format and" NL
+"### 'zlib' otherwise. 'zlib' is currently equivalent to 'zlib-5'." NL
+"# " CONFIG_OPTION_COMPRESSION " = lz4" NL
+"###" NL
+"### DEPRECATED: The new '" CONFIG_OPTION_COMPRESSION "' option deprecates previously used" NL
+"### '" CONFIG_OPTION_COMPRESSION_LEVEL "' option, which was used to configure zlib compression." NL
+"### For compatibility with previous versions of Subversion, this option can"NL
+"### still be used (and it will result in zlib compression with the" NL
+"### corresponding compression level)." NL
+"### " CONFIG_OPTION_COMPRESSION_LEVEL " = 0 ... 9 (default is 5)" NL
"" NL
"[" CONFIG_SECTION_PACKED_REVPROPS "]" NL
"### This parameter controls the size (in kBytes) of packed revprop files." NL
@@ -963,9 +1091,9 @@ write_config(svn_fs_t *fs,
"### latency and CPU usage reading and changing individual revprops." NL
"### Values smaller than 4 kByte will not improve latency any further and " NL
"### quickly render revprop packing ineffective." NL
-"### revprop-pack-size is 4 kBytes by default for non-compressed revprop" NL
-"### pack files and 16 kBytes when compression has been enabled." NL
-"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 4" NL
+"### revprop-pack-size is 16 kBytes by default for non-compressed revprop" NL
+"### pack files and 64 kBytes when compression has been enabled." NL
+"# " CONFIG_OPTION_REVPROP_PACK_SIZE " = 16" NL
"###" NL
"### To save disk space, packed revprop files may be compressed. Standard" NL
"### revprops tend to allow for very effective compression. Reading and" NL
@@ -1019,6 +1147,13 @@ write_config(svn_fs_t *fs,
"### Must be a power of 2." NL
"### p2l-page-size is given in kBytes and with a default of 1024 kBytes." NL
"# " CONFIG_OPTION_P2L_PAGE_SIZE " = 1024" NL
+"" NL
+"[" CONFIG_SECTION_DEBUG "]" NL
+"###" NL
+"### Whether to verify each new revision immediately before finalizing" NL
+"### the commit. This is disabled by default except in maintainer-mode" NL
+"### builds." NL
+"# " CONFIG_OPTION_VERIFY_BEFORE_COMMIT " = false" NL
;
#undef NL
return svn_io_file_create(svn_dirent_join(fs->path, PATH_CONFIG, pool),
@@ -1032,13 +1167,12 @@ read_global_config(svn_fs_t *fs)
{
fs_fs_data_t *ffd = fs->fsap_data;
- /* Providing a config hash is optional. */
- if (fs->config)
- ffd->use_block_read = svn_hash__get_bool(fs->config,
- SVN_FS_CONFIG_FSFS_BLOCK_READ,
- FALSE);
- else
- ffd->use_block_read = FALSE;
+ ffd->use_block_read = svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_FSFS_BLOCK_READ,
+ FALSE);
+ ffd->flush_to_disk = !svn_hash__get_bool(fs->config,
+ SVN_FS_CONFIG_NO_FLUSH_TO_DISK,
+ FALSE);
/* Ignore the user-specified larger block size if we don't use block-read.
Defaulting to 4k gives us the same access granularity in format 7 as in
@@ -1127,7 +1261,9 @@ svn_fs_fs__open(svn_fs_t *fs, const char *path, apr_pool_t *pool)
/* Global configuration options. */
SVN_ERR(read_global_config(fs));
- return get_youngest(&(ffd->youngest_rev_cache), fs, pool);
+ ffd->youngest_rev_cache = 0;
+
+ return SVN_NO_ERROR;
}
/* Wrapper around svn_io_file_create which ignores EEXIST. */
@@ -1334,7 +1470,10 @@ svn_fs_fs__min_unpacked_rev(svn_revnum_t *min_unpacked,
{
fs_fs_data_t *ffd = fs->fsap_data;
- SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
+ /* Calling this for pre-v4 repos is illegal. */
+ if (ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+ SVN_ERR(svn_fs_fs__update_min_unpacked_rev(fs, pool));
+
*min_unpacked = ffd->min_unpacked_rev;
return SVN_NO_ERROR;
@@ -1378,38 +1517,9 @@ svn_fs_fs__file_length(svn_filesize_t *length,
/* Treat "no representation" as "empty file". */
*length = 0;
}
- else if (data_rep->expanded_size)
- {
- /* Standard case: a non-empty file. */
- *length = data_rep->expanded_size;
- }
else
{
- /* Work around a FSFS format quirk (see issue #4554).
-
- A plain representation may specify its EXPANDED LENGTH as "0"
- in which case, the SIZE value is what we want.
-
- Because EXPANDED_LENGTH will also be 0 for empty files, while
- SIZE is non-null, we need to check wether the content is
- actually empty. We simply compare with the MD5 checksum of
- empty content (sha-1 is not always available).
- */
- svn_checksum_t *empty_md5
- = svn_checksum_empty_checksum(svn_checksum_md5, pool);
-
- if (memcmp(empty_md5->digest, data_rep->md5_digest,
- sizeof(data_rep->md5_digest)))
- {
- /* Contents is not empty, i.e. EXPANDED_LENGTH cannot be the
- actual file length. */
- *length = data_rep->size;
- }
- else
- {
- /* Contents is empty. */
- *length = 0;
- }
+ *length = data_rep->expanded_size;
}
return SVN_NO_ERROR;
@@ -1444,8 +1554,8 @@ svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
svn_stream_t *contents_a, *contents_b;
representation_t *rep_a = a->data_rep;
representation_t *rep_b = b->data_rep;
- svn_boolean_t a_empty = !rep_a;
- svn_boolean_t b_empty = !rep_b;
+ svn_boolean_t a_empty = !rep_a || rep_a->expanded_size == 0;
+ svn_boolean_t b_empty = !rep_b || rep_b->expanded_size == 0;
/* This makes sure that neither rep will be NULL later on */
if (a_empty && b_empty)
@@ -1454,35 +1564,33 @@ svn_fs_fs__file_text_rep_equal(svn_boolean_t *equal,
return SVN_NO_ERROR;
}
- /* Same path in same rev or txn? */
- if (svn_fs_fs__id_eq(a->id, b->id))
+ if (a_empty != b_empty)
{
- *equal = TRUE;
+ *equal = FALSE;
return SVN_NO_ERROR;
}
- /* Beware of the combination NULL rep and possibly empty rep.
- * Due to EXPANDED_SIZE not being reliable, we can't easily detect empty
- * reps. So, we can only take further shortcuts if both reps are given. */
- if (!a_empty && !b_empty)
+ /* File text representations always know their checksums - even in a txn. */
+ if (memcmp(rep_a->md5_digest, rep_b->md5_digest, sizeof(rep_a->md5_digest)))
{
- /* File text representations always know their checksums -
- * even in a txn. */
- if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
- sizeof(rep_a->md5_digest)))
- {
- *equal = FALSE;
- return SVN_NO_ERROR;
- }
+ *equal = FALSE;
+ return SVN_NO_ERROR;
+ }
- /* Paranoia. Compare SHA1 checksums because that's the level of
- confidence we require for e.g. the working copy. */
- if (rep_a->has_sha1 && rep_b->has_sha1)
- {
- *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
- sizeof(rep_a->sha1_digest)) == 0;
- return SVN_NO_ERROR;
- }
+ /* Paranoia. Compare SHA1 checksums because that's the level of
+ confidence we require for e.g. the working copy. */
+ if (rep_a->has_sha1 && rep_b->has_sha1)
+ {
+ *equal = memcmp(rep_a->sha1_digest, rep_b->sha1_digest,
+ sizeof(rep_a->sha1_digest)) == 0;
+ return SVN_NO_ERROR;
+ }
+
+ /* Same path in same rev or txn? */
+ if (svn_fs_fs__id_eq(a->id, b->id))
+ {
+ *equal = TRUE;
+ return SVN_NO_ERROR;
}
SVN_ERR(svn_fs_fs__get_contents(&contents_a, fs, rep_a, TRUE,
@@ -1519,14 +1627,27 @@ svn_fs_fs__prop_rep_equal(svn_boolean_t *equal,
&& !svn_fs_fs__id_txn_used(&rep_a->txn_id)
&& !svn_fs_fs__id_txn_used(&rep_b->txn_id))
{
- /* MD5 must be given. Having the same checksum is good enough for
- accepting the prop lists as equal. */
- *equal = memcmp(rep_a->md5_digest, rep_b->md5_digest,
- sizeof(rep_a->md5_digest)) == 0;
- return SVN_NO_ERROR;
+ /* Same representation? */
+ if ( (rep_a->revision == rep_b->revision)
+ && (rep_a->item_index == rep_b->item_index))
+ {
+ *equal = TRUE;
+ return SVN_NO_ERROR;
+ }
+
+ /* Known different content? MD5 must be given. */
+ if (memcmp(rep_a->md5_digest, rep_b->md5_digest,
+ sizeof(rep_a->md5_digest)))
+ {
+ *equal = FALSE;
+ return SVN_NO_ERROR;
+ }
}
- /* Same path in same txn? */
+ /* Same path in same txn?
+ *
+ * For committed reps, IDs cannot be the same here b/c we already know
+ * that they point to different representations. */
if (svn_fs_fs__id_eq(a->id, b->id))
{
*equal = TRUE;
@@ -1827,6 +1948,8 @@ svn_fs_fs__create(svn_fs_t *fs,
case 8: format = 6;
break;
+ case 9: format = 7;
+ break;
default:format = SVN_FS_FS__FORMAT_NUMBER;
}
@@ -1883,9 +2006,9 @@ svn_fs_fs__set_uuid(svn_fs_t *fs,
/* We use the permissions of the 'current' file, because the 'uuid'
file does not exist during repository creation. */
- SVN_ERR(svn_io_write_atomic(uuid_path, contents->data, contents->len,
- svn_fs_fs__path_current(fs, pool) /* perms */,
- pool));
+ SVN_ERR(svn_io_write_atomic2(uuid_path, contents->data, contents->len,
+ svn_fs_fs__path_current(fs, pool) /* perms */,
+ ffd->flush_to_disk, pool));
fs->uuid = apr_pstrdup(fs->pool, uuid);
@@ -2036,7 +2159,7 @@ set_node_origins_for_file(svn_fs_t *fs,
SVN_ERR(svn_stream_close(stream));
/* Rename the temp file as the real destination */
- return svn_io_file_rename(path_tmp, node_origins_path, pool);
+ return svn_io_file_rename2(path_tmp, node_origins_path, FALSE, pool);
}
@@ -2071,14 +2194,17 @@ svn_fs_fs__revision_prop(svn_string_t **value_p,
svn_fs_t *fs,
svn_revnum_t rev,
const char *propname,
- apr_pool_t *pool)
+ svn_boolean_t refresh,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
apr_hash_t *table;
SVN_ERR(svn_fs__check_fs(fs, TRUE));
- SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, pool));
+ SVN_ERR(svn_fs_fs__get_revision_proplist(&table, fs, rev, refresh,
+ scratch_pool, scratch_pool));
- *value_p = svn_hash_gets(table, propname);
+ *value_p = svn_string_dup(svn_hash_gets(table, propname), result_pool);
return SVN_NO_ERROR;
}
@@ -2101,13 +2227,17 @@ change_rev_prop_body(void *baton, apr_pool_t *pool)
{
struct change_rev_prop_baton *cb = baton;
apr_hash_t *table;
+ const svn_string_t *present_value;
- SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, pool));
+ /* We always need to read the current revprops from disk.
+ * Hence, always "refresh" here. */
+ SVN_ERR(svn_fs_fs__get_revision_proplist(&table, cb->fs, cb->rev, TRUE,
+ pool, pool));
+ present_value = svn_hash_gets(table, cb->name);
if (cb->old_value_p)
{
const svn_string_t *wanted_value = *cb->old_value_p;
- const svn_string_t *present_value = svn_hash_gets(table, cb->name);
if ((!wanted_value != !present_value)
|| (wanted_value && present_value
&& !svn_string_compare(wanted_value, present_value)))
@@ -2120,6 +2250,13 @@ change_rev_prop_body(void *baton, apr_pool_t *pool)
}
/* Fall through. */
}
+
+ /* If the prop-set is a no-op, skip the actual write. */
+ if ((!present_value && !cb->value)
+ || (present_value && cb->value
+ && svn_string_compare(present_value, cb->value)))
+ return SVN_NO_ERROR;
+
svn_hash_sets(table, cb->name, cb->value);
return svn_fs_fs__set_revision_proplist(cb->fs, cb->rev, table, pool);
@@ -2182,8 +2319,11 @@ svn_fs_fs__info_format(int *fs_format,
case 7:
(*supports_version)->minor = 9;
break;
+ case 8:
+ (*supports_version)->minor = 10;
+ break;
#ifdef SVN_DEBUG
-# if SVN_FS_FS__FORMAT_NUMBER != 7
+# if SVN_FS_FS__FORMAT_NUMBER != 8
# error "Need to add a 'case' statement here"
# endif
#endif
diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h
index b6c94c7e7ff3a..cef95fe3209e7 100644
--- a/subversion/libsvn_fs_fs/fs_fs.h
+++ b/subversion/libsvn_fs_fs/fs_fs.h
@@ -39,6 +39,15 @@ svn_error_t *svn_fs_fs__open(svn_fs_t *fs,
const char *path,
apr_pool_t *pool);
+/* Initialize parts of the FS data that are being shared across multiple
+ filesystem objects. Use COMMON_POOL for process-wide and POOL for
+ temporary allocations. Use COMMON_POOL_LOCK to ensure that the
+ initialization is serialized. */
+svn_error_t *svn_fs_fs__initialize_shared_data(svn_fs_t *fs,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool);
+
/* Upgrade the fsfs filesystem FS. Indicate progress via the optional
* NOTIFY_FUNC callback using NOTIFY_BATON. The optional CANCEL_FUNC
* will periodically be called with CANCEL_BATON to allow for preemption.
@@ -216,13 +225,16 @@ svn_fs_fs__with_all_locks(svn_fs_t *fs,
void *baton,
apr_pool_t *pool);
-/* Find the value of the property named PROPNAME in transaction TXN.
+/* Find the value of the property named PROPNAME in revision REV.
Return the contents in *VALUE_P. The contents will be allocated
- from POOL. */
+ from RESULT_POOL and SCRATCH_POOL is used for temporaries.
+ Invalidate any revprop cache is REFRESH is set. */
svn_error_t *svn_fs_fs__revision_prop(svn_string_t **value_p, svn_fs_t *fs,
svn_revnum_t rev,
const char *propname,
- apr_pool_t *pool);
+ svn_boolean_t refresh,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* Change, add, or delete a property on a revision REV in filesystem
FS. NAME gives the name of the property, and value, if non-NULL,
diff --git a/subversion/libsvn_fs_fs/fs_init.h b/subversion/libsvn_fs_fs/fs_init.h
new file mode 100644
index 0000000000000..c9f8474469dd0
--- /dev/null
+++ b/subversion/libsvn_fs_fs/fs_init.h
@@ -0,0 +1,32 @@
+/*
+ * libsvn_fs_fs/fs_init.h: Exported function of libsvn_fs_fs
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#ifndef LIBSVN_FS_LOADER_H
+#error Please include libsvn_fs/fs_loader.h instead of this file
+#else
+
+svn_error_t *svn_fs_fs__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable,
+ apr_pool_t* common_pool);
+
+#endif
diff --git a/subversion/libsvn_fs_fs/hotcopy.c b/subversion/libsvn_fs_fs/hotcopy.c
index 43f513e350f07..44a4ea442eb92 100644
--- a/subversion/libsvn_fs_fs/hotcopy.c
+++ b/subversion/libsvn_fs_fs/hotcopy.c
@@ -795,7 +795,7 @@ struct hotcopy_body_baton {
* An incremental hotcopy copies only changed or new files to the destination,
* and removes files from the destination no longer present in the source.
* While the incremental hotcopy is running, readers should still be able
- * to access the destintation repository without error and should not see
+ * to access the destination repository without error and should not see
* revisions currently in progress of being copied. Readers are able to see
* new fully copied revisions even if the entire incremental hotcopy procedure
* has not yet completed.
@@ -995,50 +995,33 @@ hotcopy_body(void *baton, apr_pool_t *pool)
SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
PATH_TXN_CURRENT, pool));
+ /* Hotcopied FS is complete. Stamp it with a format file. */
+ SVN_ERR(svn_fs_fs__write_format(dst_fs, TRUE, pool));
+
return SVN_NO_ERROR;
}
-/* Create an empty filesystem at DST_FS at DST_PATH with the same
- * configuration as SRC_FS (uuid, format, and other parameters).
- * After creation DST_FS has no revisions, not even revision zero. */
-static svn_error_t *
-hotcopy_create_empty_dest(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *dst_path,
- apr_pool_t *pool)
+svn_error_t *
+svn_fs_fs__hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
+ svn_boolean_t incremental,
+ svn_fs_hotcopy_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
{
- fs_fs_data_t *src_ffd = src_fs->fsap_data;
-
- /* Create the DST_FS repository with the same layout as SRC_FS. */
- SVN_ERR(svn_fs_fs__create_file_tree(dst_fs, dst_path, src_ffd->format,
- src_ffd->max_files_per_dir,
- src_ffd->use_log_addressing,
- pool));
-
- /* Copy the UUID. Hotcopy destination receives a new instance ID, but
- * has the same filesystem UUID as the source. */
- SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, NULL, pool));
-
- /* Remove revision 0 contents. Otherwise, it may not get overwritten
- * due to having a newer timestamp. */
- SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_rev(dst_fs, 0, pool), pool));
- SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_revprops(dst_fs, 0, pool),
- pool));
+ struct hotcopy_body_baton hbb;
- /* This filesystem is ready. Stamp it with a format number. Fail if
- * the 'format' file should already exist. */
- SVN_ERR(svn_fs_fs__write_format(dst_fs, FALSE, pool));
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
- return SVN_NO_ERROR;
-}
+ SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
-svn_error_t *
-svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *dst_path,
- svn_boolean_t incremental,
- apr_pool_t *pool)
-{
if (incremental)
{
const char *dst_format_abspath;
@@ -1050,39 +1033,51 @@ svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
SVN_ERR(svn_io_check_path(dst_format_abspath, &dst_format_kind, pool));
if (dst_format_kind == svn_node_none)
{
- /* Destination doesn't exist yet. Perform a normal hotcopy to a
- * empty destination using the same configuration as the source. */
- SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
- }
- else
- {
- /* Check the existing repository. */
- SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
- SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs,
- pool));
+ /* No destination? Fallback to a non-incremental hotcopy. */
+ incremental = FALSE;
}
}
+
+ if (incremental)
+ {
+ /* Check the existing repository. */
+ SVN_ERR(svn_fs_fs__open(dst_fs, dst_path, pool));
+ SVN_ERR(hotcopy_incremental_check_preconditions(src_fs, dst_fs, pool));
+
+ SVN_ERR(svn_fs_fs__initialize_shared_data(dst_fs, common_pool_lock,
+ pool, common_pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
+ }
else
{
/* Start out with an empty destination using the same configuration
* as the source. */
- SVN_ERR(hotcopy_create_empty_dest(src_fs, dst_fs, dst_path, pool));
+ fs_fs_data_t *src_ffd = src_fs->fsap_data;
+
+ /* Create the DST_FS repository with the same layout as SRC_FS. */
+ SVN_ERR(svn_fs_fs__create_file_tree(dst_fs, dst_path, src_ffd->format,
+ src_ffd->max_files_per_dir,
+ src_ffd->use_log_addressing,
+ pool));
+
+ /* Copy the UUID. Hotcopy destination receives a new instance ID, but
+ * has the same filesystem UUID as the source. */
+ SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, NULL, pool));
+
+ /* Remove revision 0 contents. Otherwise, it may not get overwritten
+ * due to having a newer timestamp. */
+ SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_rev(dst_fs, 0, pool),
+ pool));
+ SVN_ERR(hotcopy_remove_file(svn_fs_fs__path_revprops(dst_fs, 0, pool),
+ pool));
+
+ SVN_ERR(svn_fs_fs__initialize_shared_data(dst_fs, common_pool_lock,
+ pool, common_pool));
+ SVN_ERR(svn_fs_fs__initialize_caches(dst_fs, pool));
}
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
-svn_fs_fs__hotcopy(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- svn_boolean_t incremental,
- svn_fs_hotcopy_notify_t notify_func,
- void *notify_baton,
- svn_cancel_func_t cancel_func,
- void *cancel_baton,
- apr_pool_t *pool)
-{
- struct hotcopy_body_baton hbb;
+ if (cancel_func)
+ SVN_ERR(cancel_func(cancel_baton));
hbb.src_fs = src_fs;
hbb.dst_fs = dst_fs;
@@ -1091,7 +1086,15 @@ svn_fs_fs__hotcopy(svn_fs_t *src_fs,
hbb.notify_baton = notify_baton;
hbb.cancel_func = cancel_func;
hbb.cancel_baton = cancel_baton;
- SVN_ERR(svn_fs_fs__with_all_locks(dst_fs, hotcopy_body, &hbb, pool));
+
+ /* Lock the destination in the incremental mode. For a non-incremental
+ * hotcopy, don't take any locks. In that case the destination cannot be
+ * opened until the hotcopy finishes, and we don't have to worry about
+ * concurrency. */
+ if (incremental)
+ SVN_ERR(svn_fs_fs__with_all_locks(dst_fs, hotcopy_body, &hbb, pool));
+ else
+ SVN_ERR(hotcopy_body(&hbb, pool));
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/hotcopy.h b/subversion/libsvn_fs_fs/hotcopy.h
index ddd62183a927f..2f2322c410c2c 100644
--- a/subversion/libsvn_fs_fs/hotcopy.h
+++ b/subversion/libsvn_fs_fs/hotcopy.h
@@ -25,27 +25,23 @@
#include "fs.h"
-/* Create an empty copy of the fsfs filesystem SRC_FS into a new DST_FS at
- * DST_PATH. If INCREMENTAL is TRUE, perform a few pre-checks only if
- * a repo already exists at DST_PATH. Use POOL for temporary allocations. */
-svn_error_t *
-svn_fs_fs__hotcopy_prepare_target(svn_fs_t *src_fs,
- svn_fs_t *dst_fs,
- const char *dst_path,
- svn_boolean_t incremental,
- apr_pool_t *pool);
-
-/* Copy the fsfs filesystem SRC_FS into DST_FS. If INCREMENTAL is TRUE, do
- * not re-copy data which already exists in DST_FS. Indicate progress via
- * the optional NOTIFY_FUNC callback using NOTIFY_BATON. Use POOL for
- * temporary allocations. */
+/* Copy the fsfs filesystem SRC_FS at SRC_PATH into a new copy DST_FS at
+ * DST_PATH. If INCREMENTAL is TRUE, do not re-copy data which already
+ * exists in DST_FS. Indicate progress via the optional NOTIFY_FUNC
+ * callback using NOTIFY_BATON. Use COMMON_POOL for process-wide and
+ * POOL for temporary allocations. Use COMMON_POOL_LOCK to ensure
+ * that the initialization of the shared data is serialized. */
svn_error_t * svn_fs_fs__hotcopy(svn_fs_t *src_fs,
svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dst_path,
svn_boolean_t incremental,
svn_fs_hotcopy_notify_t notify_func,
void *notify_baton,
svn_cancel_func_t cancel_func,
void *cancel_baton,
- apr_pool_t *pool);
+ svn_mutex__t *common_pool_lock,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool);
#endif
diff --git a/subversion/libsvn_fs_fs/id.c b/subversion/libsvn_fs_fs/id.c
index bd505e06cbbc7..d22b8f7763596 100644
--- a/subversion/libsvn_fs_fs/id.c
+++ b/subversion/libsvn_fs_fs/id.c
@@ -82,9 +82,11 @@ locale_independent_strtol(long *result_p,
next = result * 10 + c;
- /* Overflow check. In case of an overflow, NEXT is 0..9.
- * In the non-overflow case, RESULT is either >= 10 or RESULT and NEXT
- * are both 0. */
+ /* Overflow check. In case of an overflow, NEXT is 0..9 and RESULT
+ * is much larger than 10. We will then return FALSE.
+ *
+ * In the non-overflow case, NEXT is >= 10 * RESULT but never smaller.
+ * We will continue the loop in that case. */
if (next < result)
return FALSE;
@@ -610,7 +612,9 @@ svn_fs_fs__id_serialize(svn_temp_serializer__context_t *context,
if (id == NULL)
return;
- /* serialize the id data struct itself */
+ /* Serialize the id data struct itself.
+ * Note that the structure behind IN is actually larger than a mere
+ * svn_fs_id_t . */
svn_temp_serializer__add_leaf(context,
(const void * const *)in,
sizeof(fs_fs__id_t));
diff --git a/subversion/libsvn_fs_fs/index.c b/subversion/libsvn_fs_fs/index.c
index a6695580a0f5f..1cb8fba80ad61 100644
--- a/subversion/libsvn_fs_fs/index.c
+++ b/subversion/libsvn_fs_fs/index.c
@@ -231,7 +231,7 @@ stream_error_create(svn_fs_fs__packed_number_stream_t *stream,
apr_off_t offset;
SVN_ERR(svn_io_file_name_get(&file_name, stream->file,
stream->pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, stream->file, stream->pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, stream->file, stream->pool));
return svn_error_createf(err, NULL, message, file_name,
apr_psprintf(stream->pool,
@@ -251,7 +251,7 @@ static svn_error_t *
packed_stream_read(svn_fs_fs__packed_number_stream_t *stream)
{
unsigned char buffer[MAX_NUMBER_PREFETCH];
- apr_size_t read = 0;
+ apr_size_t bytes_read = 0;
apr_size_t i;
value_position_pair_t *target;
apr_off_t block_start = 0;
@@ -273,33 +273,34 @@ packed_stream_read(svn_fs_fs__packed_number_stream_t *stream)
* boundaries. This shall prevent jumping back and forth between two
* blocks because the extra data was not actually request _now_.
*/
- read = sizeof(buffer);
+ bytes_read = sizeof(buffer);
block_left = stream->block_size - (stream->next_offset - block_start);
- if (block_left >= 10 && block_left < read)
- read = (apr_size_t)block_left;
+ if (block_left >= 10 && block_left < bytes_read)
+ bytes_read = (apr_size_t)block_left;
/* Don't read beyond the end of the file section that belongs to this
* index / stream. */
- read = (apr_size_t)MIN(read, stream->stream_end - stream->next_offset);
+ bytes_read = (apr_size_t)MIN(bytes_read,
+ stream->stream_end - stream->next_offset);
- err = apr_file_read(stream->file, buffer, &read);
+ err = apr_file_read(stream->file, buffer, &bytes_read);
if (err && !APR_STATUS_IS_EOF(err))
return stream_error_create(stream, err,
_("Can't read index file '%s' at offset 0x%s"));
/* if the last number is incomplete, trim it from the buffer */
- while (read > 0 && buffer[read-1] >= 0x80)
- --read;
+ while (bytes_read > 0 && buffer[bytes_read-1] >= 0x80)
+ --bytes_read;
/* we call read() only if get() requires more data. So, there must be
* at least *one* further number. */
- if SVN__PREDICT_FALSE(read == 0)
+ if SVN__PREDICT_FALSE(bytes_read == 0)
return stream_error_create(stream, err,
_("Unexpected end of index file %s at offset 0x%s"));
/* parse file buffer and expand into stream buffer */
target = stream->buffer;
- for (i = 0; i < read;)
+ for (i = 0; i < bytes_read;)
{
if (buffer[i] < 0x80)
{
@@ -558,13 +559,13 @@ read_uint64_from_proto_index(apr_file_t *proto_index,
apr_pool_t *scratch_pool)
{
apr_byte_t buffer[sizeof(*value_p)];
- apr_size_t read;
+ apr_size_t bytes_read;
/* Read the full 8 bytes or our 64 bit value, unless we hit EOF.
* Assert that we never read partial values. */
SVN_ERR(svn_io_file_read_full2(proto_index, buffer, sizeof(buffer),
- &read, eof, scratch_pool));
- SVN_ERR_ASSERT((eof && *eof) || read == sizeof(buffer));
+ &bytes_read, eof, scratch_pool));
+ SVN_ERR_ASSERT((eof && *eof) || bytes_read == sizeof(buffer));
/* If we did not hit EOF, reconstruct the uint64 value and return it. */
if (!eof || !*eof)
@@ -1735,7 +1736,7 @@ svn_fs_fs__l2p_get_max_ids(apr_array_header_t **max_ids,
apr_uint64_t item_count;
apr_size_t first_page_index, last_page_index;
- if (revision >= header->first_revision + header->revision_count)
+ if (revision - header->first_revision >= header->revision_count)
{
/* need to read the next index. Clear up memory used for the
* previous one. Note that intermittent pack runs do not change
@@ -2421,6 +2422,13 @@ read_entry(svn_fs_fs__packed_number_stream_t *stream,
return svn_error_create(SVN_ERR_FS_INDEX_CORRUPTION, NULL,
_("Empty regions must have item number 0 and checksum 0"));
+ /* Corrupted SIZE values might cause arithmetic overflow.
+ * The same can happen if you copy a repository from a system with 63 bit
+ * file lengths to one with 31 bit file lengths. */
+ if ((apr_uint64_t)entry.offset + (apr_uint64_t)entry.size > off_t_max)
+ return svn_error_create(SVN_ERR_FS_INDEX_OVERFLOW , NULL,
+ _("P2L index entry size overflow."));
+
APR_ARRAY_PUSH(result, svn_fs_fs__p2l_entry_t) = entry;
*item_offset += entry.size;
@@ -3207,18 +3215,11 @@ svn_fs_fs__l2p_index_from_p2l_entries(const char **protoname,
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
svn_revnum_t last_revision = SVN_INVALID_REVNUM;
- svn_revnum_t revision = SVN_INVALID_REVNUM;
/* L2P index must be written in revision order.
* Sort ENTRIES accordingly. */
svn_sort__array(entries, compare_p2l_entry_revision);
- /* Find the first revision in the index
- * (must exist since no truly empty revs are allowed). */
- for (i = 0; i < entries->nelts && !SVN_IS_VALID_REVNUM(revision); ++i)
- revision = APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t *)
- ->item.revision;
-
/* Create the temporary proto-rev file. */
SVN_ERR(svn_io_open_unique_file3(NULL, protoname, NULL,
svn_io_file_del_on_pool_cleanup,
diff --git a/subversion/libsvn_fs_fs/load-index.c b/subversion/libsvn_fs_fs/load-index.c
index 3142e8e354023..0ba66725d35cc 100644
--- a/subversion/libsvn_fs_fs/load-index.c
+++ b/subversion/libsvn_fs_fs/load-index.c
@@ -29,6 +29,53 @@
#include "util.h"
#include "transaction.h"
+/* From the ENTRIES array of svn_fs_fs__p2l_entry_t*, sorted by offset,
+ * return the first offset behind the last item. */
+static apr_off_t
+get_max_covered(apr_array_header_t *entries)
+{
+ const svn_fs_fs__p2l_entry_t *entry;
+ if (entries->nelts == 0)
+ return -1;
+
+ entry = APR_ARRAY_IDX(entries, entries->nelts - 1,
+ const svn_fs_fs__p2l_entry_t *);
+ return entry->offset + entry->size;
+}
+
+/* Make sure that the svn_fs_fs__p2l_entry_t* in ENTRIES are consecutive
+ * and non-overlapping. Use SCRATCH_POOL for temporaries. */
+static svn_error_t *
+check_all_covered(apr_array_header_t *entries,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ apr_off_t expected = 0;
+ for (i = 0; i < entries->nelts; ++i)
+ {
+ const svn_fs_fs__p2l_entry_t *entry
+ = APR_ARRAY_IDX(entries, i, const svn_fs_fs__p2l_entry_t *);
+
+ if (entry->offset < expected)
+ return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+ "Overlapping index data for offset %s",
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)expected));
+
+ if (entry->offset > expected)
+ return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+ "Missing index data for offset %s",
+ apr_psprintf(scratch_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)expected));
+
+ expected = entry->offset + entry->size;
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* A svn_sort__array compatible comparator function, sorting the
* svn_fs_fs__p2l_entry_t** given in LHS, RHS by offset. */
static int
@@ -52,7 +99,7 @@ svn_fs_fs__load_index(svn_fs_t *fs,
apr_array_header_t *entries,
apr_pool_t *scratch_pool)
{
- apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_pool_t *subpool = svn_pool_create(scratch_pool);
/* Check the FS format number. */
if (! svn_fs_fs__use_log_addressing(fs))
@@ -68,31 +115,60 @@ svn_fs_fs__load_index(svn_fs_t *fs,
const char *l2p_proto_index;
const char *p2l_proto_index;
svn_fs_fs__revision_file_t *rev_file;
+ svn_error_t *err;
+ apr_off_t max_covered = get_max_covered(entries);
+
+ /* Ensure that the index data is complete. */
+ SVN_ERR(check_all_covered(entries, scratch_pool));
/* Open rev / pack file & trim indexes + footer off it. */
SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs,
- revision, iterpool,
- iterpool));
- SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
- SVN_ERR(svn_io_file_trunc(rev_file->file, rev_file->l2p_offset,
- iterpool));
+ revision, subpool,
+ subpool));
+
+ /* Remove the existing index info. */
+ err = svn_fs_fs__auto_read_footer(rev_file);
+ if (err)
+ {
+ /* Even the index footer cannot be read, even less be trusted.
+ * Take the range of valid data from the new index data. */
+ svn_error_clear(err);
+ SVN_ERR(svn_io_file_trunc(rev_file->file, max_covered,
+ subpool));
+ }
+ else
+ {
+ /* We assume that the new index data covers all contents.
+ * Error out if it doesn't. The user can always truncate
+ * the file themselves. */
+ if (max_covered != rev_file->l2p_offset)
+ return svn_error_createf(SVN_ERR_INVALID_INPUT, NULL,
+ "New index data ends at %s, old index ended at %s",
+ apr_psprintf(scratch_pool, "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)max_covered),
+ apr_psprintf(scratch_pool, "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t) rev_file->l2p_offset));
+
+ SVN_ERR(svn_io_file_trunc(rev_file->file, rev_file->l2p_offset,
+ subpool));
+ }
/* Create proto index files for the new index data
* (will be cleaned up automatically with iterpool). */
SVN_ERR(svn_fs_fs__p2l_index_from_p2l_entries(&p2l_proto_index, fs,
rev_file, entries,
- iterpool, iterpool));
+ subpool, subpool));
SVN_ERR(svn_fs_fs__l2p_index_from_p2l_entries(&l2p_proto_index, fs,
- entries, iterpool,
- iterpool));
+ entries, subpool,
+ subpool));
/* Combine rev data with new index data. */
SVN_ERR(svn_fs_fs__add_index_data(fs, rev_file->file, l2p_proto_index,
p2l_proto_index,
- rev_file->start_revision, iterpool));
+ rev_file->start_revision, subpool));
}
- svn_pool_destroy(iterpool);
+ svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/lock.c b/subversion/libsvn_fs_fs/lock.c
index c852025f2dac7..3b8284d3ee463 100644
--- a/subversion/libsvn_fs_fs/lock.c
+++ b/subversion/libsvn_fs_fs/lock.c
@@ -222,7 +222,7 @@ write_digest_file(apr_hash_t *children,
svn_io_file_del_none, pool, pool));
if ((err = svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool)))
{
- svn_error_clear(svn_stream_close(stream));
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_createf(err->apr_err,
err,
_("Cannot write lock/entries hashfile '%s'"),
@@ -230,7 +230,7 @@ write_digest_file(apr_hash_t *children,
}
SVN_ERR(svn_stream_close(stream));
- SVN_ERR(svn_io_file_rename(tmp_path, digest_path, pool));
+ SVN_ERR(svn_io_file_rename2(tmp_path, digest_path, FALSE, pool));
SVN_ERR(svn_io_copy_perms(perms_reference, digest_path, pool));
return SVN_NO_ERROR;
}
@@ -273,7 +273,7 @@ read_digest_file(apr_hash_t **children_p,
hash = apr_hash_make(pool);
if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
{
- svn_error_clear(svn_stream_close(stream));
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_createf(err->apr_err,
err,
_("Can't parse lock/entries hashfile '%s'"),
@@ -847,7 +847,7 @@ lock_body(void *baton, apr_pool_t *pool)
apr_pool_t *iterpool = svn_pool_create(pool);
/* Until we implement directory locks someday, we only allow locks
- on files or non-existent paths. */
+ on files. */
/* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
library dependencies, which are not portable. */
SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
diff --git a/subversion/libsvn_fs_fs/low_level.c b/subversion/libsvn_fs_fs/low_level.c
index d21e312faa2e7..2854bc6e2d84e 100644
--- a/subversion/libsvn_fs_fs/low_level.c
+++ b/subversion/libsvn_fs_fs/low_level.c
@@ -189,6 +189,19 @@ svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
changes_offset);
}
+/* If ERR is not NULL, wrap it MESSAGE. The latter must have an %ld
+ * format parameter that will be filled with REV. */
+static svn_error_t *
+wrap_footer_error(svn_error_t *err,
+ const char *message,
+ svn_revnum_t rev)
+{
+ if (err)
+ return svn_error_quick_wrapf(err, message, rev);
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
svn_checksum_t **l2p_checksum,
@@ -196,6 +209,7 @@ svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
svn_checksum_t **p2l_checksum,
svn_stringbuf_t *footer,
svn_revnum_t rev,
+ apr_off_t footer_offset,
apr_pool_t *result_pool)
{
apr_int64_t val;
@@ -204,17 +218,20 @@ svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
/* Get the L2P offset. */
const char *str = svn_cstring_tokenize(" ", &last_str);
if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid revision footer"));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Invalid r%ld footer", rev);
- SVN_ERR(svn_cstring_atoi64(&val, str));
+ SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
+ footer_offset - 1, 10),
+ "Invalid L2P offset in r%ld footer",
+ rev));
*l2p_offset = (apr_off_t)val;
/* Get the L2P checksum. */
str = svn_cstring_tokenize(" ", &last_str);
if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid revision footer"));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Invalid r%ld footer", rev);
SVN_ERR(svn_checksum_parse_hex(l2p_checksum, svn_checksum_md5, str,
result_pool));
@@ -222,17 +239,33 @@ svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
/* Get the P2L offset. */
str = svn_cstring_tokenize(" ", &last_str);
if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid revision footer"));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Invalid r%ld footer", rev);
- SVN_ERR(svn_cstring_atoi64(&val, str));
+ SVN_ERR(wrap_footer_error(svn_cstring_strtoi64(&val, str, 0,
+ footer_offset - 1, 10),
+ "Invalid P2L offset in r%ld footer",
+ rev));
*p2l_offset = (apr_off_t)val;
+ /* The P2L indes follows the L2P index */
+ if (*p2l_offset <= *l2p_offset)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "P2L offset %s must be larger than L2P offset %s"
+ " in r%ld footer",
+ apr_psprintf(result_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)*p2l_offset),
+ apr_psprintf(result_pool,
+ "%" APR_UINT64_T_HEX_FMT,
+ (apr_uint64_t)*l2p_offset),
+ rev);
+
/* Get the P2L checksum. */
str = svn_cstring_tokenize(" ", &last_str);
if (str == NULL)
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Invalid revision footer"));
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Invalid r%ld footer", rev);
SVN_ERR(svn_checksum_parse_hex(p2l_checksum, svn_checksum_md5, str,
result_pool));
@@ -449,10 +482,10 @@ read_change(change_t **change_p,
svn_error_t *
svn_fs_fs__read_changes(apr_array_header_t **changes,
svn_stream_t *stream,
+ int max_count,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- change_t *change;
apr_pool_t *iterpool;
/* Pre-allocate enough room for most change lists.
@@ -465,13 +498,16 @@ svn_fs_fs__read_changes(apr_array_header_t **changes,
*/
*changes = apr_array_make(result_pool, 63, sizeof(change_t *));
- SVN_ERR(read_change(&change, stream, result_pool, scratch_pool));
iterpool = svn_pool_create(scratch_pool);
- while (change)
+ for (; max_count > 0; --max_count)
{
- APR_ARRAY_PUSH(*changes, change_t*) = change;
- SVN_ERR(read_change(&change, stream, result_pool, iterpool));
+ change_t *change;
svn_pool_clear(iterpool);
+ SVN_ERR(read_change(&change, stream, result_pool, iterpool));
+ if (!change)
+ break;
+
+ APR_ARRAY_PUSH(*changes, change_t*) = change;
}
svn_pool_destroy(iterpool);
@@ -705,6 +741,9 @@ read_header_block(apr_hash_t **headers,
return SVN_NO_ERROR;
}
+/* ### Ouch! The implementation of this function currently modifies
+ ### the input string when tokenizing it (so the input cannot be
+ ### used after that). */
svn_error_t *
svn_fs_fs__parse_representation(representation_t **rep_p,
svn_stringbuf_t *text,
@@ -775,13 +814,21 @@ svn_fs_fs__parse_representation(representation_t **rep_p,
if (str == NULL)
return SVN_NO_ERROR;
- /* Read the SHA1 hash. */
- if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
+ /* Is the SHA1 hash present? */
+ if (str[0] == '-' && str[1] == 0)
+ {
+ checksum = NULL;
+ }
+ else
+ {
+ /* Read the SHA1 hash. */
+ if (strlen(str) != (APR_SHA1_DIGESTSIZE * 2))
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
- SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
- scratch_pool));
+ SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, str,
+ scratch_pool));
+ }
/* We do have a valid SHA1 but it might be all 0.
We cannot be sure where that came from (Alas! legacy), so let's not
@@ -793,21 +840,36 @@ svn_fs_fs__parse_representation(representation_t **rep_p,
if (checksum)
memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
- /* Read the uniquifier. */
- str = svn_cstring_tokenize("/", &string);
+ str = svn_cstring_tokenize(" ", &string);
if (str == NULL)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Malformed text representation offset line in node-rev"));
- SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
+ /* Is the uniquifier present? */
+ if (str[0] == '-' && str[1] == 0)
+ {
+ end = string;
+ }
+ else
+ {
+ char *substring = str;
- str = svn_cstring_tokenize(" ", &string);
- if (str == NULL || *str != '_')
- return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
- _("Malformed text representation offset line in node-rev"));
+ /* Read the uniquifier. */
+ str = svn_cstring_tokenize("/", &substring);
+ if (str == NULL)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
- ++str;
- rep->uniquifier.number = svn__base36toui64(&end, str);
+ SVN_ERR(svn_fs_fs__id_txn_parse(&rep->uniquifier.noderev_txn_id, str));
+
+ str = svn_cstring_tokenize(" ", &substring);
+ if (str == NULL || *str != '_')
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Malformed text representation offset line in node-rev"));
+
+ ++str;
+ rep->uniquifier.number = svn__base36toui64(&end, str);
+ }
if (*end)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
@@ -998,25 +1060,37 @@ svn_fs_fs__read_noderev(node_revision_t **noderev_p,
}
/* Return a textual representation of the DIGEST of given KIND.
- * If IS_NULL is TRUE, no digest is available.
* Allocate the result in RESULT_POOL.
*/
static const char *
format_digest(const unsigned char *digest,
svn_checksum_kind_t kind,
- svn_boolean_t is_null,
apr_pool_t *result_pool)
{
svn_checksum_t checksum;
checksum.digest = digest;
checksum.kind = kind;
- if (is_null)
- return "(null)";
-
return svn_checksum_to_cstring_display(&checksum, result_pool);
}
+/* Return a textual representation of the uniquifier represented
+ * by NODEREV_TXN_ID and NUMBER. Use POOL for the allocations.
+ */
+static const char *
+format_uniquifier(const svn_fs_fs__id_part_t *noderev_txn_id,
+ apr_uint64_t number,
+ apr_pool_t *pool)
+{
+ char buf[SVN_INT64_BUFFER_SIZE];
+ const char *txn_id_str;
+
+ txn_id_str = svn_fs_fs__id_txn_unparse(noderev_txn_id, pool);
+ svn__ui64tobase36(buf, number);
+
+ return apr_psprintf(pool, "%s/_%s", txn_id_str, buf);
+}
+
svn_stringbuf_t *
svn_fs_fs__unparse_representation(representation_t *rep,
int format,
@@ -1024,32 +1098,80 @@ svn_fs_fs__unparse_representation(representation_t *rep,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- char buffer[SVN_INT64_BUFFER_SIZE];
+ svn_stringbuf_t *str;
+ const char *sha1_str;
+ const char *uniquifier_str;
+
if (svn_fs_fs__id_txn_used(&rep->txn_id) && mutable_rep_truncated)
return svn_stringbuf_ncreate("-1", 2, result_pool);
- if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT || !rep->has_sha1)
- return svn_stringbuf_createf
- (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
- " %" SVN_FILESIZE_T_FMT " %s",
- rep->revision, rep->item_index, rep->size,
- rep->expanded_size,
- format_digest(rep->md5_digest, svn_checksum_md5, FALSE,
- scratch_pool));
-
- svn__ui64tobase36(buffer, rep->uniquifier.number);
- return svn_stringbuf_createf
- (result_pool, "%ld %" APR_UINT64_T_FMT " %" SVN_FILESIZE_T_FMT
- " %" SVN_FILESIZE_T_FMT " %s %s %s/_%s",
- rep->revision, rep->item_index, rep->size,
- rep->expanded_size,
- format_digest(rep->md5_digest, svn_checksum_md5,
- FALSE, scratch_pool),
- format_digest(rep->sha1_digest, svn_checksum_sha1,
- !rep->has_sha1, scratch_pool),
- svn_fs_fs__id_txn_unparse(&rep->uniquifier.noderev_txn_id,
- scratch_pool),
- buffer);
+ /* Format of the string:
+ <rev> <item_index> <size> <expanded-size> <md5> [<sha1>] [<uniquifier>]
+ */
+ str = svn_stringbuf_createf(
+ result_pool,
+ "%ld"
+ " %" APR_UINT64_T_FMT
+ " %" SVN_FILESIZE_T_FMT
+ " %" SVN_FILESIZE_T_FMT
+ " %s",
+ rep->revision,
+ rep->item_index,
+ rep->size,
+ rep->expanded_size,
+ format_digest(rep->md5_digest, svn_checksum_md5, scratch_pool));
+
+ /* Compatibility: these formats don't understand <sha1> and <uniquifier>. */
+ if (format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+ return str;
+
+ if (format < SVN_FS_FS__MIN_REP_STRING_OPTIONAL_VALUES_FORMAT)
+ {
+ /* Compatibility: these formats can only have <sha1> and <uniquifier>
+ present simultaneously, or don't have them at all. */
+ if (rep->has_sha1)
+ {
+ sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
+ scratch_pool);
+ uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
+ rep->uniquifier.number,
+ scratch_pool);
+ svn_stringbuf_appendbyte(str, ' ');
+ svn_stringbuf_appendcstr(str, sha1_str);
+ svn_stringbuf_appendbyte(str, ' ');
+ svn_stringbuf_appendcstr(str, uniquifier_str);
+ }
+ return str;
+ }
+
+ /* The most recent formats support optional <sha1> and <uniquifier> values. */
+ if (rep->has_sha1)
+ {
+ sha1_str = format_digest(rep->sha1_digest, svn_checksum_sha1,
+ scratch_pool);
+ }
+ else
+ sha1_str = "-";
+
+ if (rep->uniquifier.number == 0 &&
+ rep->uniquifier.noderev_txn_id.number == 0 &&
+ rep->uniquifier.noderev_txn_id.revision == 0)
+ {
+ uniquifier_str = "-";
+ }
+ else
+ {
+ uniquifier_str = format_uniquifier(&rep->uniquifier.noderev_txn_id,
+ rep->uniquifier.number,
+ scratch_pool);
+ }
+
+ svn_stringbuf_appendbyte(str, ' ');
+ svn_stringbuf_appendcstr(str, sha1_str);
+ svn_stringbuf_appendbyte(str, ' ');
+ svn_stringbuf_appendcstr(str, uniquifier_str);
+
+ return str;
}
diff --git a/subversion/libsvn_fs_fs/low_level.h b/subversion/libsvn_fs_fs/low_level.h
index 35b9d0da5000e..3029e27890528 100644
--- a/subversion/libsvn_fs_fs/low_level.h
+++ b/subversion/libsvn_fs_fs/low_level.h
@@ -67,6 +67,8 @@ svn_fs_fs__unparse_revision_trailer(apr_off_t root_offset,
* *P2L_OFFSET, respectively. Also, return the expected checksums in
* in *L2P_CHECKSUM and *P2L_CHECKSUM.
*
+ * FOOTER_OFFSET is used for validation.
+ *
* Note that REV is only used to construct nicer error objects that
* mention this revision. Allocate the checksums in RESULT_POOL.
*/
@@ -77,6 +79,7 @@ svn_fs_fs__parse_footer(apr_off_t *l2p_offset,
svn_checksum_t **p2l_checksum,
svn_stringbuf_t *footer,
svn_revnum_t rev,
+ apr_off_t footer_offset,
apr_pool_t *result_pool);
/* Given the offset of the L2P index data in L2P_OFFSET, the content
@@ -94,11 +97,13 @@ svn_fs_fs__unparse_footer(apr_off_t l2p_offset,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
-/* Read all the changes from STREAM and store them in *CHANGES,
- allocated in RESULT_POOL. Do temporary allocations in SCRATCH_POOL. */
+/* Read up to MAX_COUNT of the changes from STREAM and store them in
+ *CHANGES, allocated in RESULT_POOL. Do temporary allocations in
+ SCRATCH_POOL. */
svn_error_t *
svn_fs_fs__read_changes(apr_array_header_t **changes,
svn_stream_t *stream,
+ int max_count,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
diff --git a/subversion/libsvn_fs_fs/pack.c b/subversion/libsvn_fs_fs/pack.c
index 8c4ef37fbaa40..1119857998fcf 100644
--- a/subversion/libsvn_fs_fs/pack.c
+++ b/subversion/libsvn_fs_fs/pack.c
@@ -108,8 +108,8 @@ typedef struct path_order_t
/* noderev predecessor count */
int predecessor_count;
- /* this is a directory node */
- svn_boolean_t is_dir;
+ /* this is a node is the latest for this PATH in this rev / pack file */
+ svn_boolean_t is_head;
/* length of the expanded representation content */
apr_int64_t expanded_size;
@@ -221,7 +221,7 @@ typedef struct pack_context_t
* to NULL that we already processed. */
apr_array_header_t *reps;
- /* array of int, marking for each revision, the which offset their items
+ /* array of int, marking for each revision, at which offset their items
* begin in REPS. Will be filled in phase 2 and be cleared after
* each revision range. */
apr_array_header_t *rev_offsets;
@@ -233,6 +233,9 @@ typedef struct pack_context_t
/* pool used for temporary data structures that will be cleaned up when
* the next range of revisions is being processed */
apr_pool_t *info_pool;
+
+ /* ensure that all filesystem changes are written to disk. */
+ svn_boolean_t flush_to_disk;
} pack_context_t;
/* Create and initialize a new pack context for packing shard SHARD_REV in
@@ -240,7 +243,7 @@ typedef struct pack_context_t
* and return the structure in *CONTEXT.
*
* Limit the number of items being copied per iteration to MAX_ITEMS.
- * Set CANCEL_FUNC and CANCEL_BATON as well.
+ * Set FLUSH_TO_DISK, CANCEL_FUNC and CANCEL_BATON as well.
*/
static svn_error_t *
initialize_pack_context(pack_context_t *context,
@@ -249,6 +252,7 @@ initialize_pack_context(pack_context_t *context,
const char *shard_dir,
svn_revnum_t shard_rev,
int max_items,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -273,6 +277,12 @@ initialize_pack_context(pack_context_t *context,
context->end_rev = shard_rev;
context->shard_end_rev = shard_rev + ffd->max_files_per_dir;
+ /* the pool used for temp structures */
+ context->info_pool = svn_pool_create(pool);
+ context->paths = svn_prefix_tree__create(context->info_pool);
+
+ context->flush_to_disk = flush_to_disk;
+
/* Create the new directory and pack file. */
context->shard_dir = shard_dir;
context->pack_file_dir = pack_file_dir;
@@ -300,15 +310,18 @@ initialize_pack_context(pack_context_t *context,
context->changes = apr_array_make(pool, max_items,
sizeof(svn_fs_fs__p2l_entry_t *));
SVN_ERR(svn_io_open_unique_file3(&context->changes_file, NULL, temp_dir,
- svn_io_file_del_on_close, pool, pool));
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
context->file_props = apr_array_make(pool, max_items,
sizeof(svn_fs_fs__p2l_entry_t *));
SVN_ERR(svn_io_open_unique_file3(&context->file_props_file, NULL, temp_dir,
- svn_io_file_del_on_close, pool, pool));
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
context->dir_props = apr_array_make(pool, max_items,
sizeof(svn_fs_fs__p2l_entry_t *));
SVN_ERR(svn_io_open_unique_file3(&context->dir_props_file, NULL, temp_dir,
- svn_io_file_del_on_close, pool, pool));
+ svn_io_file_del_on_close,
+ context->info_pool, pool));
/* noderev and representation item bucket */
context->rev_offsets = apr_array_make(pool, max_revs, sizeof(int));
@@ -321,10 +334,6 @@ initialize_pack_context(pack_context_t *context,
SVN_ERR(svn_io_open_unique_file3(&context->reps_file, NULL, temp_dir,
svn_io_file_del_on_close, pool, pool));
- /* the pool used for temp structures */
- context->info_pool = svn_pool_create(pool);
- context->paths = svn_prefix_tree__create(context->info_pool);
-
return SVN_NO_ERROR;
}
@@ -404,7 +413,8 @@ close_pack_context(pack_context_t *context,
SVN_ERR(svn_io_remove_file2(proto_p2l_index_path, FALSE, pool));
/* Ensure that packed file is written to disk.*/
- SVN_ERR(svn_io_file_flush_to_disk(context->pack_file, pool));
+ if (context->flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(context->pack_file, pool));
SVN_ERR(svn_io_file_close(context->pack_file, pool));
return SVN_NO_ERROR;
@@ -500,7 +510,7 @@ copy_item_to_temp(pack_context_t *context,
svn_fs_fs__p2l_entry_t *new_entry
= apr_pmemdup(context->info_pool, entry, sizeof(*entry));
- SVN_ERR(svn_fs_fs__get_file_offset(&new_entry->offset, temp_file, pool));
+ SVN_ERR(svn_io_file_get_offset(&new_entry->offset, temp_file, pool));
APR_ARRAY_PUSH(entries, svn_fs_fs__p2l_entry_t *) = new_entry;
SVN_ERR(copy_file_data(context, temp_file, rev_file, entry->size, pool));
@@ -587,13 +597,13 @@ copy_rep_to_temp(pack_context_t *context,
/* create a copy of ENTRY, make it point to the copy destination and
* store it in CONTEXT */
entry = apr_pmemdup(context->info_pool, entry, sizeof(*entry));
- SVN_ERR(svn_fs_fs__get_file_offset(&entry->offset, context->reps_file, pool));
+ SVN_ERR(svn_io_file_get_offset(&entry->offset, context->reps_file, pool));
add_item_rep_mapping(context, entry);
/* read & parse the representation header */
stream = svn_stream_from_aprfile2(rev_file, TRUE, pool);
SVN_ERR(svn_fs_fs__read_rep_header(&rep_header, stream, pool, pool));
- svn_stream_close(stream);
+ SVN_ERR(svn_stream_close(stream));
/* if the representation is a delta against some other rep, link the two */
if ( rep_header->type == svn_fs_fs__rep_delta
@@ -629,9 +639,6 @@ compare_dir_entries_format7(const svn_sort__item_t *a,
const svn_fs_dirent_t *lhs = (const svn_fs_dirent_t *) a->value;
const svn_fs_dirent_t *rhs = (const svn_fs_dirent_t *) b->value;
- if (lhs->kind != rhs->kind)
- return lhs->kind == svn_node_dir ? -1 : 1;
-
return strcmp(lhs->name, rhs->name);
}
@@ -685,7 +692,7 @@ svn_fs_fs__order_dir_entries(svn_fs_t *fs,
return result;
}
-/* Return a duplicate of the the ORIGINAL path and with special sub-strins
+/* Return a duplicate of the ORIGINAL path and with special sub-strings
* (e.g. "trunk") modified in such a way that have a lower lexicographic
* value than any other "normal" file name.
*/
@@ -722,7 +729,7 @@ tweak_path_for_ordering(const char *original,
*/
static svn_error_t *
copy_node_to_temp(pack_context_t *context,
- apr_file_t *rev_file,
+ svn_fs_fs__revision_file_t *rev_file,
svn_fs_fs__p2l_entry_t *entry,
apr_pool_t *pool)
{
@@ -730,25 +737,22 @@ copy_node_to_temp(pack_context_t *context,
sizeof(*path_order));
node_revision_t *noderev;
const char *sort_path;
- svn_stream_t *stream;
apr_off_t source_offset = entry->offset;
/* read & parse noderev */
- stream = svn_stream_from_aprfile2(rev_file, TRUE, pool);
- SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, pool, pool));
- svn_stream_close(stream);
+ SVN_ERR(svn_fs_fs__read_noderev(&noderev, rev_file->stream, pool, pool));
/* create a copy of ENTRY, make it point to the copy destination and
* store it in CONTEXT */
entry = apr_pmemdup(context->info_pool, entry, sizeof(*entry));
- SVN_ERR(svn_fs_fs__get_file_offset(&entry->offset, context->reps_file,
- pool));
+ SVN_ERR(svn_io_file_get_offset(&entry->offset, context->reps_file,
+ pool));
add_item_rep_mapping(context, entry);
/* copy the noderev to our temp file */
- SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &source_offset, pool));
- SVN_ERR(copy_file_data(context, context->reps_file, rev_file, entry->size,
- pool));
+ SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &source_offset, pool));
+ SVN_ERR(copy_file_data(context, context->reps_file, rev_file->file,
+ entry->size, pool));
/* if the node has a data representation, make that the node's "base".
* This will (often) cause the noderev to be placed right in front of
@@ -758,9 +762,7 @@ copy_node_to_temp(pack_context_t *context,
{
path_order->rep_id.revision = noderev->data_rep->revision;
path_order->rep_id.number = noderev->data_rep->item_index;
- path_order->expanded_size = noderev->data_rep->expanded_size
- ? noderev->data_rep->expanded_size
- : noderev->data_rep->size;
+ path_order->expanded_size = noderev->data_rep->expanded_size;
}
/* Sort path is the key used for ordering noderevs and associated reps.
@@ -770,7 +772,6 @@ copy_node_to_temp(pack_context_t *context,
path_order->node_id = *svn_fs_fs__id_node_id(noderev->id);
path_order->revision = svn_fs_fs__id_rev(noderev->id);
path_order->predecessor_count = noderev->predecessor_count;
- path_order->is_dir = noderev->kind == svn_node_dir;
path_order->noderev_id = *svn_fs_fs__id_rev_item(noderev->id);
APR_ARRAY_PUSH(context->path_order, path_order_t *) = path_order;
@@ -787,13 +788,8 @@ compare_path_order(const path_order_t * const * lhs_p,
const path_order_t * lhs = *lhs_p;
const path_order_t * rhs = *rhs_p;
- /* cluster all directories */
- int diff = rhs->is_dir - lhs->is_dir;
- if (diff)
- return diff;
-
/* lexicographic order on path and node (i.e. latest first) */
- diff = svn_prefix_string__compare(lhs->path, rhs->path);
+ int diff = svn_prefix_string__compare(lhs->path, rhs->path);
if (diff)
return diff;
@@ -831,22 +827,48 @@ compare_ref_to_item(const reference_t * const * lhs_p,
return svn_fs_fs__id_part_compare(&(*lhs_p)->from, rhs_p);
}
-/* implements compare_fn_t. Finds the DIR / FILE boundary.
- */
-static int
-compare_is_dir(const path_order_t * const * lhs_p,
- const void *unused)
-{
- return (*lhs_p)->is_dir ? -1 : 0;
-}
-
/* Look for the least significant bit set in VALUE and return the smallest
* number with the same property, i.e. the largest power of 2 that is a
- * factor in VALUE. */
+ * factor in VALUE. Edge case: roundness(0) := 0 . */
static int
roundness(int value)
{
- return value ? value - (value & (value - 1)) : INT_MAX;
+ return value - (value & (value - 1));
+}
+
+/* For all paths in first COUNT entries in PATH_ORDER, mark their latest
+ * node as "HEAD". PATH_ORDER must be ordered by path, revision.
+ */
+static void
+classify_nodes(path_order_t **path_order,
+ int count)
+{
+ const svn_prefix_string__t *path;
+ int i;
+
+ /* The logic below would fail for empty ranges. */
+ if (count == 0)
+ return;
+
+ /* All entries are sorted by path, followed by revision.
+ * So, the first index is also HEAD for the first path.
+ */
+ path = path_order[0]->path;
+ path_order[0]->is_head = TRUE;
+
+ /* Since the sorting implicitly groups all entries by path and then sorts
+ * by descending revision within the group, whenever we encounter a new
+ * path, the first entry is "HEAD" for that path.
+ */
+ for (i = 1; i < count; ++i)
+ {
+ /* New path? */
+ if (svn_prefix_string__compare(path, path_order[i]->path))
+ {
+ path = path_order[i]->path;
+ path_order[i]->is_head = TRUE;
+ }
+ }
}
/* Order a range of data collected in CONTEXT such that we can place them
@@ -855,13 +877,13 @@ roundness(int value)
*/
static void
sort_reps_range(pack_context_t *context,
- const path_order_t **path_order,
- const path_order_t **temp,
+ path_order_t **path_order,
+ path_order_t **temp,
int first,
int last)
{
const svn_prefix_string__t *path;
- int i, dest, best;
+ int i, dest;
svn_fs_fs__id_part_t rep_id;
fs_fs_data_t *ffd = context->fs->fsap_data;
@@ -878,49 +900,52 @@ sort_reps_range(pack_context_t *context,
* We simply pick & chose from the existing path, rev order.
*/
dest = first;
- path = path_order[first]->path;
- best = first;
-
- /* (1) For each path, pick the "roundest" representation and put it in
- * front of all other nodes in the pack file. The "roundest" rep is
- * the one most likely to be referenced from future pack files, i.e. we
- * concentrate those potential "foreign link targets" in one section of
- * the pack file.
+
+ /* (1) There are two classes of representations that are likely to be
+ * referenced from future shards. These form a "hot zone" of mostly
+ * relevant data, i.e. we try to include as many reps as possible that
+ * are needed for future checkouts while trying to exclude as many as
+ * possible that are likely not needed in future checkouts.
+ *
+ * First, "very round" representations from frequently changing nodes.
+ * That excludes many in-between representations not accessed from HEAD.
*
- * And we only apply this to reps outside the linear deltification
- * sections because references *into* linear deltification ranges are
- * much less likely.
+ * The second class are infrequently changing nodes. Because they are
+ * unlikely to change often in the future, they will remain relevant for
+ * HEAD even over long spans of revisions. They are most likely the only
+ * thing we need from very old pack files.
*/
for (i = first; i < last; ++i)
{
- /* Investigated all nodes for the current path? */
- if (svn_prefix_string__compare(path, path_order[i]->path))
+ int round = roundness(path_order[i]->predecessor_count);
+
+ /* Class 1:
+ * Pretty round _and_ a significant stop in the node's delta chain.
+ * This may pick up more than one representation from the same chain
+ * but that's rare and not a problem. Prefer simple checks here.
+ *
+ * The divider of 4 is arbitrary but seems to work well in practice.
+ * Larger values increase the number of items in the "hot zone".
+ * Smaller values make delta chains at HEAD more likely to contain
+ * "cold zone" representations. */
+ svn_boolean_t likely_target
+ = (round >= ffd->max_linear_deltification)
+ && (round >= path_order[i]->predecessor_count / 4);
+
+ /* Class 2:
+ * Anything from short node chains. The default of 16 is generous
+ * but we'd rather include too many than too few nodes here to keep
+ * seeks between different regions of this pack file at a minimum. */
+ svn_boolean_t likely_head
+ = path_order[i]->predecessor_count
+ < ffd->max_linear_deltification;
+
+ /* Pick any node that from either class. */
+ if (likely_target || likely_head)
{
- /* next path */
- path = path_order[i]->path;
-
- /* Pick roundest non-linear deltified node. */
- if (roundness(path_order[best]->predecessor_count)
- >= ffd->max_linear_deltification)
- {
- temp[dest++] = path_order[best];
- path_order[best] = NULL;
- best = i;
- }
+ temp[dest++] = path_order[i];
+ path_order[i] = NULL;
}
-
- /* next entry */
- if ( roundness(path_order[best]->predecessor_count)
- < roundness(path_order[i]->predecessor_count))
- best = i;
- }
-
- /* Treat the last path the same as all others. */
- if (roundness(path_order[best]->predecessor_count)
- >= ffd->max_linear_deltification)
- {
- temp[dest++] = path_order[best];
- path_order[best] = NULL;
}
/* (2) For each (remaining) path, pick the nodes along the delta chain
@@ -986,8 +1011,8 @@ static void
sort_reps(pack_context_t *context)
{
apr_pool_t *temp_pool;
- const path_order_t **temp, **path_order;
- int i, count, dir_count;
+ path_order_t **temp, **path_order;
+ int i, count;
/* We will later assume that there is at least one node / path.
*/
@@ -1012,13 +1037,11 @@ sort_reps(pack_context_t *context)
temp = apr_pcalloc(temp_pool, count * sizeof(*temp));
path_order = (void *)context->path_order->elts;
- /* Find the boundary between DIR and FILE section. */
- dir_count = svn_sort__bsearch_lower_bound(context->path_order, NULL,
- (int (*)(const void *, const void *))compare_is_dir);
+ /* Mark nodes depending on what other nodes exist for the same path etc. */
+ classify_nodes(path_order, count);
- /* Sort those sub-sections separately. */
- sort_reps_range(context, path_order, temp, 0, dir_count);
- sort_reps_range(context, path_order, temp, dir_count, count);
+ /* Rearrange those sub-sections separately. */
+ sort_reps_range(context, path_order, temp, 0, count);
/* We now know the final ordering. */
for (i = 0; i < count; ++i)
@@ -1054,7 +1077,7 @@ sort_items(apr_array_header_t *entries)
/* Return the remaining unused bytes in the current block in CONTEXT's
* pack file.
*/
-static apr_ssize_t
+static apr_off_t
get_block_left(pack_context_t *context)
{
fs_fs_data_t *ffd = context->fs->fsap_data;
@@ -1185,7 +1208,7 @@ copy_reps_from_temp(pack_context_t *context,
apr_array_header_t *path_order = context->path_order;
int i;
- /* copy items in path order. */
+ /* copy items in path order. Exclude the non-HEAD noderevs. */
for (i = 0; i < path_order->nelts; ++i)
{
path_order_t *current_path;
@@ -1195,13 +1218,30 @@ copy_reps_from_temp(pack_context_t *context,
svn_pool_clear(iterpool);
current_path = APR_ARRAY_IDX(path_order, i, path_order_t *);
- node_part = get_item(context, &current_path->noderev_id, TRUE);
+ if (current_path->is_head)
+ {
+ node_part = get_item(context, &current_path->noderev_id, TRUE);
+ if (node_part)
+ SVN_ERR(store_item(context, temp_file, node_part, iterpool));
+ }
+
rep_part = get_item(context, &current_path->rep_id, TRUE);
+ if (rep_part)
+ SVN_ERR(store_item(context, temp_file, rep_part, iterpool));
+ }
+ /* copy the remaining non-head noderevs. */
+ for (i = 0; i < path_order->nelts; ++i)
+ {
+ path_order_t *current_path;
+ svn_fs_fs__p2l_entry_t *node_part;
+
+ svn_pool_clear(iterpool);
+
+ current_path = APR_ARRAY_IDX(path_order, i, path_order_t *);
+ node_part = get_item(context, &current_path->noderev_id, TRUE);
if (node_part)
SVN_ERR(store_item(context, temp_file, node_part, iterpool));
- if (rep_part)
- SVN_ERR(store_item(context, temp_file, rep_part, iterpool));
}
svn_pool_destroy(iterpool);
@@ -1370,7 +1410,7 @@ pack_range(pack_context_t *context,
SVN_ERR(copy_rep_to_temp(context, rev_file->file, entry,
iterpool2));
else if (entry->type == SVN_FS_FS__ITEM_TYPE_NODEREV)
- SVN_ERR(copy_node_to_temp(context, rev_file->file, entry,
+ SVN_ERR(copy_node_to_temp(context, rev_file, entry,
iterpool2));
else
SVN_ERR_ASSERT(entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED);
@@ -1435,7 +1475,6 @@ append_revision(pack_context_t *context,
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, context->fs,
context->start_rev, pool,
iterpool));
-
SVN_ERR(svn_fs_fs__auto_read_footer(rev_file));
revdata_size = rev_file->l2p_offset;
@@ -1499,8 +1538,9 @@ append_revision(pack_context_t *context,
*
* Pack the revision shard starting at SHARD_REV in filesystem FS from
* SHARD_DIR into the PACK_FILE_DIR, using POOL for allocations. Limit
- * the extra memory consumption to MAX_MEM bytes. CANCEL_FUNC and
- * CANCEL_BATON are what you think they are.
+ * the extra memory consumption to MAX_MEM bytes. If FLUSH_TO_DISK is
+ * non-zero, do not return until the data has actually been written on
+ * the disk. CANCEL_FUNC and CANCEL_BATON are what you think they are.
*/
static svn_error_t *
pack_log_addressed(svn_fs_t *fs,
@@ -1508,6 +1548,7 @@ pack_log_addressed(svn_fs_t *fs,
const char *shard_dir,
svn_revnum_t shard_rev,
apr_size_t max_mem,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1540,8 +1581,8 @@ pack_log_addressed(svn_fs_t *fs,
/* set up a pack context */
SVN_ERR(initialize_pack_context(&context, fs, pack_file_dir, shard_dir,
- shard_rev, max_items, cancel_func,
- cancel_baton, pool));
+ shard_rev, max_items, flush_to_disk,
+ cancel_func, cancel_baton, pool));
/* phase 1: determine the size of the revisions to pack */
SVN_ERR(svn_fs_fs__l2p_get_max_ids(&max_ids, fs, shard_rev,
@@ -1550,7 +1591,8 @@ pack_log_addressed(svn_fs_t *fs,
/* pack revisions in ranges that don't exceed MAX_MEM */
for (i = 0; i < max_ids->nelts; ++i)
- if (APR_ARRAY_IDX(max_ids, i, apr_uint64_t) + item_count <= max_items)
+ if ( APR_ARRAY_IDX(max_ids, i, apr_uint64_t)
+ <= (apr_uint64_t)max_items - item_count)
{
item_count += APR_ARRAY_IDX(max_ids, i, apr_uint64_t);
context.end_rev++;
@@ -1664,14 +1706,16 @@ svn_fs_fs__get_packed_offset(apr_off_t *rev_offset,
*
* Pack the revision shard starting at SHARD_REV containing exactly
* MAX_FILES_PER_DIR revisions from SHARD_PATH into the PACK_FILE_DIR,
- * using POOL for allocations. CANCEL_FUNC and CANCEL_BATON are what you
- * think they are.
+ * using POOL for allocations. If FLUSH_TO_DISK is non-zero, do not
+ * return until the data has actually been written on the disk.
+ * CANCEL_FUNC and CANCEL_BATON are what you think they are.
*/
static svn_error_t *
pack_phys_addressed(const char *pack_file_dir,
const char *shard_path,
svn_revnum_t start_rev,
int max_files_per_dir,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1681,7 +1725,6 @@ pack_phys_addressed(const char *pack_file_dir,
apr_file_t *manifest_file;
svn_stream_t *manifest_stream;
svn_revnum_t end_rev, rev;
- apr_off_t next_offset;
apr_pool_t *iterpool;
/* Some useful paths. */
@@ -1702,32 +1745,37 @@ pack_phys_addressed(const char *pack_file_dir,
manifest_stream = svn_stream_from_aprfile2(manifest_file, TRUE, pool);
end_rev = start_rev + max_files_per_dir - 1;
- next_offset = 0;
iterpool = svn_pool_create(pool);
/* Iterate over the revisions in this shard, squashing them together. */
for (rev = start_rev; rev <= end_rev; rev++)
{
svn_stream_t *rev_stream;
- apr_finfo_t finfo;
const char *path;
+ apr_off_t offset;
+ apr_file_t *rev_file;
svn_pool_clear(iterpool);
- /* Get the size of the file. */
path = svn_dirent_join(shard_path, apr_psprintf(iterpool, "%ld", rev),
iterpool);
- SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
+
+ /* Obtain current offset in pack file. */
+ SVN_ERR(svn_io_file_get_offset(&offset, pack_file, iterpool));
/* build manifest */
SVN_ERR(svn_stream_printf(manifest_stream, iterpool,
- "%" APR_OFF_T_FMT "\n", next_offset));
- next_offset += finfo.size;
-
- /* Copy all the bits from the rev file to the end of the pack file. */
- SVN_ERR(svn_stream_open_readonly(&rev_stream, path, iterpool, iterpool));
+ "%" APR_OFF_T_FMT "\n", offset));
+
+ /* Copy all the bits from the rev file to the end of the pack file.
+ * Use unbuffered apr_file_t since we're going to write using 16kb
+ * chunks. */
+ SVN_ERR(svn_io_file_open(&rev_file, path, APR_READ, APR_OS_DEFAULT,
+ iterpool));
+ rev_stream = svn_stream_from_aprfile2(rev_file, FALSE, iterpool);
SVN_ERR(svn_stream_copy3(rev_stream,
- svn_stream_from_aprfile2(pack_file, TRUE, pool),
+ svn_stream_from_aprfile2(pack_file, TRUE,
+ iterpool),
cancel_func, cancel_baton, iterpool));
}
@@ -1735,14 +1783,16 @@ pack_phys_addressed(const char *pack_file_dir,
SVN_ERR(svn_stream_close(manifest_stream));
/* Ensure that pack file is written to disk. */
- SVN_ERR(svn_io_file_flush_to_disk(manifest_file, pool));
+ if (flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(manifest_file, pool));
SVN_ERR(svn_io_file_close(manifest_file, pool));
/* disallow write access to the manifest file */
SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
/* Ensure that pack file is written to disk. */
- SVN_ERR(svn_io_file_flush_to_disk(pack_file, pool));
+ if (flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(pack_file, pool));
SVN_ERR(svn_io_file_close(pack_file, pool));
svn_pool_destroy(iterpool);
@@ -1753,8 +1803,9 @@ pack_phys_addressed(const char *pack_file_dir,
/* In filesystem FS, pack the revision SHARD containing exactly
* MAX_FILES_PER_DIR revisions from SHARD_PATH into the PACK_FILE_DIR,
* using POOL for allocations. Try to limit the amount of temporary
- * memory needed to MAX_MEM bytes. CANCEL_FUNC and CANCEL_BATON are what
- * you think they are.
+ * memory needed to MAX_MEM bytes. If FLUSH_TO_DISK is non-zero, do
+ * not return until the data has actually been written on the disk.
+ * CANCEL_FUNC and CANCEL_BATON are what you think they are.
*
* If for some reason we detect a partial packing already performed, we
* remove the pack file and start again.
@@ -1768,6 +1819,7 @@ pack_rev_shard(svn_fs_t *fs,
apr_int64_t shard,
int max_files_per_dir,
apr_size_t max_mem,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *pool)
@@ -1787,12 +1839,13 @@ pack_rev_shard(svn_fs_t *fs,
/* Index information files */
if (svn_fs_fs__use_log_addressing(fs))
- SVN_ERR(pack_log_addressed(fs, pack_file_dir, shard_path, shard_rev,
- max_mem, cancel_func, cancel_baton, pool));
+ SVN_ERR(pack_log_addressed(fs, pack_file_dir, shard_path,
+ shard_rev, max_mem, flush_to_disk,
+ cancel_func, cancel_baton, pool));
else
SVN_ERR(pack_phys_addressed(pack_file_dir, shard_path, shard_rev,
- max_files_per_dir, cancel_func,
- cancel_baton, pool));
+ max_files_per_dir, flush_to_disk,
+ cancel_func, cancel_baton, pool));
SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, pool));
SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, pool));
@@ -1840,6 +1893,8 @@ synced_pack_shard(void *baton,
/* if enabled, pack the revprops in an equivalent way */
if (pb->revsprops_dir)
{
+ apr_int64_t pack_size_limit = 0.9 * ffd->revprop_pack_size;
+
revprops_pack_file_dir = svn_dirent_join(pb->revsprops_dir,
apr_psprintf(pool,
"%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
@@ -1853,10 +1908,11 @@ synced_pack_shard(void *baton,
revprops_shard_path,
pb->shard,
ffd->max_files_per_dir,
- (int)(0.9*ffd->revprop_pack_size),
+ pack_size_limit,
ffd->compress_packed_revprops
? SVN__COMPRESSION_ZLIB_DEFAULT
: SVN__COMPRESSION_NONE,
+ ffd->flush_to_disk,
pb->cancel_func,
pb->cancel_baton,
pool));
@@ -1933,8 +1989,8 @@ pack_shard(struct pack_baton *baton,
/* pack the revision content */
SVN_ERR(pack_rev_shard(baton->fs, rev_pack_file_dir, baton->rev_shard_path,
baton->shard, ffd->max_files_per_dir,
- baton->max_mem, baton->cancel_func,
- baton->cancel_baton, pool));
+ baton->max_mem, ffd->flush_to_disk,
+ baton->cancel_func, baton->cancel_baton, pool));
/* For newer repo formats, we only acquired the pack lock so far.
Before modifying the repo state by switching over to the packed
@@ -1953,6 +2009,34 @@ pack_shard(struct pack_baton *baton,
return SVN_NO_ERROR;
}
+/* Read the youngest rev and the first non-packed rev info for FS from disk.
+ Set *FULLY_PACKED when there is no completed unpacked shard.
+ Use SCRATCH_POOL for temporary allocations.
+ */
+static svn_error_t *
+get_pack_status(svn_boolean_t *fully_packed,
+ svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_int64_t completed_shards;
+ svn_revnum_t youngest;
+
+ SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev, fs,
+ scratch_pool));
+
+ SVN_ERR(svn_fs_fs__youngest_rev(&youngest, fs, scratch_pool));
+ completed_shards = (youngest + 1) / ffd->max_files_per_dir;
+
+ /* See if we've already completed all possible shards thus far. */
+ if (ffd->min_unpacked_rev == (completed_shards * ffd->max_files_per_dir))
+ *fully_packed = TRUE;
+ else
+ *fully_packed = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
/* The work-horse for svn_fs_fs__pack, called with the FS write lock.
This implements the svn_fs_fs__with_write_lock() 'body' callback
type. BATON is a 'struct pack_baton *'.
@@ -1974,30 +2058,23 @@ pack_body(void *baton,
struct pack_baton *pb = baton;
fs_fs_data_t *ffd = pb->fs->fsap_data;
apr_int64_t completed_shards;
- svn_revnum_t youngest;
apr_pool_t *iterpool;
+ svn_boolean_t fully_packed;
- /* If the repository isn't a new enough format, we don't support packing.
- Return a friendly error to that effect. */
- if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT)
- return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
- _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
- ffd->format);
-
- /* If we aren't using sharding, we can't do any packing, so quit. */
- if (!ffd->max_files_per_dir)
- return SVN_NO_ERROR;
-
- SVN_ERR(svn_fs_fs__read_min_unpacked_rev(&ffd->min_unpacked_rev, pb->fs,
- pool));
-
- SVN_ERR(svn_fs_fs__youngest_rev(&youngest, pb->fs, pool));
- completed_shards = (youngest + 1) / ffd->max_files_per_dir;
+ /* Since another process might have already packed the repo,
+ we need to re-read the pack status. */
+ SVN_ERR(get_pack_status(&fully_packed, pb->fs, pool));
+ if (fully_packed)
+ {
+ if (pb->notify_func)
+ (*pb->notify_func)(pb->notify_baton,
+ ffd->min_unpacked_rev / ffd->max_files_per_dir,
+ svn_fs_pack_notify_noop, pool);
- /* See if we've already completed all possible shards thus far. */
- if (ffd->min_unpacked_rev == (completed_shards * ffd->max_files_per_dir))
- return SVN_NO_ERROR;
+ return SVN_NO_ERROR;
+ }
+ completed_shards = (ffd->youngest_rev_cache + 1) / ffd->max_files_per_dir;
pb->revs_dir = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
pb->revsprops_dir = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
@@ -2032,7 +2109,37 @@ svn_fs_fs__pack(svn_fs_t *fs,
struct pack_baton pb = { 0 };
fs_fs_data_t *ffd = fs->fsap_data;
svn_error_t *err;
+ svn_boolean_t fully_packed;
+
+ /* If the repository isn't a new enough format, we don't support packing.
+ Return a friendly error to that effect. */
+ if (ffd->format < SVN_FS_FS__MIN_PACKED_FORMAT)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("FSFS format (%d) too old to pack; please upgrade the filesystem."),
+ ffd->format);
+
+ /* If we aren't using sharding, we can't do any packing, so quit. */
+ if (!ffd->max_files_per_dir)
+ {
+ if (notify_func)
+ (*notify_func)(notify_baton, -1, svn_fs_pack_notify_noop, pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Is there we even anything to do?. */
+ SVN_ERR(get_pack_status(&fully_packed, fs, pool));
+ if (fully_packed)
+ {
+ if (notify_func)
+ (*notify_func)(notify_baton,
+ ffd->min_unpacked_rev / ffd->max_files_per_dir,
+ svn_fs_pack_notify_noop, pool);
+
+ return SVN_NO_ERROR;
+ }
+ /* Lock the repo and start the pack process. */
pb.fs = fs;
pb.notify_func = notify_func;
pb.notify_baton = notify_baton;
diff --git a/subversion/libsvn_fs_fs/recovery.c b/subversion/libsvn_fs_fs/recovery.c
index 125d47a1f3707..eef06f65942ae 100644
--- a/subversion/libsvn_fs_fs/recovery.c
+++ b/subversion/libsvn_fs_fs/recovery.c
@@ -197,9 +197,7 @@ recover_find_max_ids(svn_fs_t *fs,
stored in the representation. Note that this is a directory, i.e.
represented using the hash format on disk and can never have 0 length. */
baton.pool = pool;
- baton.remaining = noderev->data_rep->expanded_size
- ? noderev->data_rep->expanded_size
- : noderev->data_rep->size;
+ baton.remaining = noderev->data_rep->expanded_size;
stream = svn_stream_create(&baton, pool);
svn_stream_set_read2(stream, NULL /* only full read support */,
read_handler_recover);
@@ -211,7 +209,7 @@ recover_find_max_ids(svn_fs_t *fs,
{
svn_string_t *id_str = svn_fs_fs__id_unparse(noderev->id, pool);
- svn_error_clear(svn_stream_close(stream));
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_quick_wrapf(err,
_("malformed representation for node-revision '%s'"),
id_str->data);
diff --git a/subversion/libsvn_fs_fs/rep-cache-db.h b/subversion/libsvn_fs_fs/rep-cache-db.h
index 0f2cc89aa7ff0..e66253810c9de 100644
--- a/subversion/libsvn_fs_fs/rep-cache-db.h
+++ b/subversion/libsvn_fs_fs/rep-cache-db.h
@@ -1,8 +1,8 @@
-/* This file is automatically generated from rep-cache-db.sql and .dist_sandbox/subversion-1.9.7/subversion/libsvn_fs_fs/token-map.h.
+/* This file is automatically generated from rep-cache-db.sql and subversion/libsvn_fs_fs/token-map.h.
* Do not edit this file -- edit the source and rerun gen-make.py */
-#define STMT_CREATE_SCHEMA 0
-#define STMT_0_INFO {"STMT_CREATE_SCHEMA", NULL}
+#define STMT_CREATE_SCHEMA_V1 0
+#define STMT_0_INFO {"STMT_CREATE_SCHEMA_V1", NULL}
#define STMT_0 \
"CREATE TABLE rep_cache ( " \
" hash TEXT NOT NULL PRIMARY KEY, " \
@@ -14,53 +14,66 @@
"PRAGMA USER_VERSION = 1; " \
""
-#define STMT_GET_REP 1
-#define STMT_1_INFO {"STMT_GET_REP", NULL}
+#define STMT_CREATE_SCHEMA_V2 1
+#define STMT_1_INFO {"STMT_CREATE_SCHEMA_V2", NULL}
#define STMT_1 \
+ "CREATE TABLE rep_cache ( " \
+ " hash TEXT NOT NULL PRIMARY KEY, " \
+ " revision INTEGER NOT NULL, " \
+ " offset INTEGER NOT NULL, " \
+ " size INTEGER NOT NULL, " \
+ " expanded_size INTEGER NOT NULL " \
+ " ) WITHOUT ROWID; " \
+ "PRAGMA USER_VERSION = 2; " \
+ ""
+
+#define STMT_GET_REP 2
+#define STMT_2_INFO {"STMT_GET_REP", NULL}
+#define STMT_2 \
"SELECT revision, offset, size, expanded_size " \
"FROM rep_cache " \
"WHERE hash = ?1 " \
""
-#define STMT_SET_REP 2
-#define STMT_2_INFO {"STMT_SET_REP", NULL}
-#define STMT_2 \
+#define STMT_SET_REP 3
+#define STMT_3_INFO {"STMT_SET_REP", NULL}
+#define STMT_3 \
"INSERT OR FAIL INTO rep_cache (hash, revision, offset, size, expanded_size) " \
"VALUES (?1, ?2, ?3, ?4, ?5) " \
""
-#define STMT_GET_REPS_FOR_RANGE 3
-#define STMT_3_INFO {"STMT_GET_REPS_FOR_RANGE", NULL}
-#define STMT_3 \
+#define STMT_GET_REPS_FOR_RANGE 4
+#define STMT_4_INFO {"STMT_GET_REPS_FOR_RANGE", NULL}
+#define STMT_4 \
"SELECT hash, revision, offset, size, expanded_size " \
"FROM rep_cache " \
"WHERE revision >= ?1 AND revision <= ?2 " \
""
-#define STMT_GET_MAX_REV 4
-#define STMT_4_INFO {"STMT_GET_MAX_REV", NULL}
-#define STMT_4 \
+#define STMT_GET_MAX_REV 5
+#define STMT_5_INFO {"STMT_GET_MAX_REV", NULL}
+#define STMT_5 \
"SELECT MAX(revision) " \
"FROM rep_cache " \
""
-#define STMT_DEL_REPS_YOUNGER_THAN_REV 5
-#define STMT_5_INFO {"STMT_DEL_REPS_YOUNGER_THAN_REV", NULL}
-#define STMT_5 \
+#define STMT_DEL_REPS_YOUNGER_THAN_REV 6
+#define STMT_6_INFO {"STMT_DEL_REPS_YOUNGER_THAN_REV", NULL}
+#define STMT_6 \
"DELETE FROM rep_cache " \
"WHERE revision > ?1 " \
""
-#define STMT_LOCK_REP 6
-#define STMT_6_INFO {"STMT_LOCK_REP", NULL}
-#define STMT_6 \
+#define STMT_LOCK_REP 7
+#define STMT_7_INFO {"STMT_LOCK_REP", NULL}
+#define STMT_7 \
"BEGIN TRANSACTION; " \
"INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0) " \
""
-#define STMT_UNLOCK_REP 7
-#define STMT_7_INFO {"STMT_UNLOCK_REP", NULL}
-#define STMT_7 \
+#define STMT_UNLOCK_REP 8
+#define STMT_8_INFO {"STMT_UNLOCK_REP", NULL}
+#define STMT_8 \
"ROLLBACK TRANSACTION; " \
""
@@ -74,6 +87,7 @@
STMT_5, \
STMT_6, \
STMT_7, \
+ STMT_8, \
NULL \
}
@@ -87,5 +101,6 @@
STMT_5_INFO, \
STMT_6_INFO, \
STMT_7_INFO, \
+ STMT_8_INFO, \
{NULL, NULL} \
}
diff --git a/subversion/libsvn_fs_fs/rep-cache-db.sql b/subversion/libsvn_fs_fs/rep-cache-db.sql
index caaac334c68b0..cd89f35c63ee3 100644
--- a/subversion/libsvn_fs_fs/rep-cache-db.sql
+++ b/subversion/libsvn_fs_fs/rep-cache-db.sql
@@ -21,7 +21,7 @@
* ====================================================================
*/
--- STMT_CREATE_SCHEMA
+-- STMT_CREATE_SCHEMA_V1
/* A table mapping representation hashes to locations in a rev file. */
CREATE TABLE rep_cache (
hash TEXT NOT NULL PRIMARY KEY,
@@ -33,36 +33,63 @@ CREATE TABLE rep_cache (
PRAGMA USER_VERSION = 1;
+-- STMT_CREATE_SCHEMA_V2
+/* A table mapping representation hashes to locations in a rev file.
+ Same as in V1 schema, except that it uses the `WITHOUT ROWID` optimization:
+ https://sqlite.org/withoutrowid.html
+
+ Note that this optimization is only supported starting from SQLite version
+ 3.8.2 (2013-12-06). To keep compatibility with existing binaries, it is
+ only used for newer filesystem formats that were released together with
+ bumping the minimum required SQLite version.
+ */
+CREATE TABLE rep_cache (
+ hash TEXT NOT NULL PRIMARY KEY,
+ revision INTEGER NOT NULL,
+ offset INTEGER NOT NULL,
+ size INTEGER NOT NULL,
+ expanded_size INTEGER NOT NULL
+ ) WITHOUT ROWID;
+
+PRAGMA USER_VERSION = 2;
-- STMT_GET_REP
+/* Works for both V1 and V2 schemas. */
SELECT revision, offset, size, expanded_size
FROM rep_cache
WHERE hash = ?1
-- STMT_SET_REP
+/* Works for both V1 and V2 schemas. */
INSERT OR FAIL INTO rep_cache (hash, revision, offset, size, expanded_size)
VALUES (?1, ?2, ?3, ?4, ?5)
-- STMT_GET_REPS_FOR_RANGE
+/* Works for both V1 and V2 schemas. */
SELECT hash, revision, offset, size, expanded_size
FROM rep_cache
WHERE revision >= ?1 AND revision <= ?2
-- STMT_GET_MAX_REV
+/* Works for both V1 and V2 schemas. */
SELECT MAX(revision)
FROM rep_cache
-- STMT_DEL_REPS_YOUNGER_THAN_REV
+/* Works for both V1 and V2 schemas. */
DELETE FROM rep_cache
WHERE revision > ?1
/* An INSERT takes an SQLite reserved lock that prevents other writes
but doesn't block reads. The incomplete transaction means that no
permanent change is made to the database and the transaction is
- removed when the database is closed. */
+ removed when the database is closed.
+
+ Works for both V1 and V2 schemas. */
-- STMT_LOCK_REP
BEGIN TRANSACTION;
INSERT INTO rep_cache VALUES ('dummy', 0, 0, 0, 0)
-- STMT_UNLOCK_REP
+/* Works for both V1 and V2 schemas. */
ROLLBACK TRANSACTION;
diff --git a/subversion/libsvn_fs_fs/rep-cache.c b/subversion/libsvn_fs_fs/rep-cache.c
index 437d60381ddb7..b0b81fad9ac38 100644
--- a/subversion/libsvn_fs_fs/rep-cache.c
+++ b/subversion/libsvn_fs_fs/rep-cache.c
@@ -24,6 +24,7 @@
#include "svn_private_config.h"
+#include "cached_data.h"
#include "fs_fs.h"
#include "fs.h"
#include "rep-cache.h"
@@ -35,9 +36,6 @@
#include "rep-cache-db.h"
-/* A few magic values */
-#define REP_CACHE_SCHEMA_FORMAT 1
-
REP_CACHE_DB_SQL_DECLARE_STATEMENTS(statements);
@@ -50,13 +48,6 @@ path_rep_cache_db(const char *fs_path,
return svn_dirent_join(fs_path, REP_CACHE_DB_NAME, result_pool);
}
-#define SVN_ERR_CLOSE(x, db) do \
-{ \
- svn_error_t *svn__err = (x); \
- if (svn__err) \
- return svn_error_compose_create(svn__err, svn_sqlite__close(db)); \
-} while (0)
-
/** Library-private API's. **/
@@ -106,12 +97,19 @@ open_rep_cache(void *baton,
0, NULL, 0,
fs->pool, pool));
- SVN_ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool), sdb);
- if (version < REP_CACHE_SCHEMA_FORMAT)
+ SVN_SQLITE__ERR_CLOSE(svn_sqlite__read_schema_version(&version, sdb, pool),
+ sdb);
+ /* If we have an uninitialized database, go ahead and create the schema. */
+ if (version <= 0)
{
- /* Must be 0 -- an uninitialized (no schema) database. Create
- the schema. Results in schema version of 1. */
- SVN_ERR_CLOSE(svn_sqlite__exec_statements(sdb, STMT_CREATE_SCHEMA), sdb);
+ int stmt;
+
+ if (ffd->format >= SVN_FS_FS__MIN_REP_CACHE_SCHEMA_V2_FORMAT)
+ stmt = STMT_CREATE_SCHEMA_V2;
+ else
+ stmt = STMT_CREATE_SCHEMA_V1;
+
+ SVN_SQLITE__ERR_CLOSE(svn_sqlite__exec_statements(sdb, stmt), sdb);
}
/* This is used as a flag that the database is available so don't
@@ -263,7 +261,7 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
If you extend this function, check the callsite to see if you have
to make it not-ignore additional error codes. */
svn_error_t *
-svn_fs_fs__get_rep_reference(representation_t **rep,
+svn_fs_fs__get_rep_reference(representation_t **rep_p,
svn_fs_t *fs,
svn_checksum_t *checksum,
apr_pool_t *pool)
@@ -271,6 +269,7 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
fs_fs_data_t *ffd = fs->fsap_data;
svn_sqlite__stmt_t *stmt;
svn_boolean_t have_row;
+ representation_t *rep;
SVN_ERR_ASSERT(ffd->rep_sharing_allowed);
if (! ffd->rep_cache_db)
@@ -289,26 +288,28 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
SVN_ERR(svn_sqlite__step(&have_row, stmt));
if (have_row)
{
- *rep = apr_pcalloc(pool, sizeof(**rep));
- svn_fs_fs__id_txn_reset(&(*rep)->txn_id);
- memcpy((*rep)->sha1_digest, checksum->digest,
- sizeof((*rep)->sha1_digest));
- (*rep)->has_sha1 = TRUE;
- (*rep)->revision = svn_sqlite__column_revnum(stmt, 0);
- (*rep)->item_index = svn_sqlite__column_int64(stmt, 1);
- (*rep)->size = svn_sqlite__column_int64(stmt, 2);
- (*rep)->expanded_size = svn_sqlite__column_int64(stmt, 3);
+ rep = apr_pcalloc(pool, sizeof(*rep));
+ svn_fs_fs__id_txn_reset(&(rep->txn_id));
+ memcpy(rep->sha1_digest, checksum->digest, sizeof(rep->sha1_digest));
+ rep->has_sha1 = TRUE;
+ rep->revision = svn_sqlite__column_revnum(stmt, 0);
+ rep->item_index = svn_sqlite__column_int64(stmt, 1);
+ rep->size = svn_sqlite__column_int64(stmt, 2);
+ rep->expanded_size = svn_sqlite__column_int64(stmt, 3);
}
else
- *rep = NULL;
+ rep = NULL;
SVN_ERR(svn_sqlite__reset(stmt));
- if (*rep)
+ if (rep)
{
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_fs__fixup_expanded_size(fs, rep, pool));
+
/* Check that REP refers to a revision that exists in FS. */
- svn_error_t *err = svn_fs_fs__ensure_revision_exists((*rep)->revision,
- fs, pool);
+ err = svn_fs_fs__ensure_revision_exists(rep->revision, fs, pool);
if (err)
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
"Checksum '%s' in rep-cache is beyond HEAD",
@@ -316,6 +317,7 @@ svn_fs_fs__get_rep_reference(representation_t **rep,
pool));
}
+ *rep_p = rep;
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/rep-cache.h b/subversion/libsvn_fs_fs/rep-cache.h
index 75072a03eb596..30377786c5c17 100644
--- a/subversion/libsvn_fs_fs/rep-cache.h
+++ b/subversion/libsvn_fs_fs/rep-cache.h
@@ -64,11 +64,11 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
apr_pool_t *pool);
/* Return the representation REP in FS which has fulltext CHECKSUM.
- REP is allocated in POOL. If the rep cache database has not been
- opened, just set *REP to NULL. Returns SVN_ERR_FS_CORRUPT if
+ *REP_P is allocated in POOL. If the rep cache database has not been
+ opened, just set *REP_P to NULL. Returns SVN_ERR_FS_CORRUPT if
a reference beyond HEAD is detected. */
svn_error_t *
-svn_fs_fs__get_rep_reference(representation_t **rep,
+svn_fs_fs__get_rep_reference(representation_t **rep_p,
svn_fs_t *fs,
svn_checksum_t *checksum,
apr_pool_t *pool);
diff --git a/subversion/libsvn_fs_fs/rev_file.c b/subversion/libsvn_fs_fs/rev_file.c
index 7c18ac8510020..7ebee63225ab4 100644
--- a/subversion/libsvn_fs_fs/rev_file.c
+++ b/subversion/libsvn_fs_fs/rev_file.c
@@ -259,6 +259,7 @@ svn_fs_fs__auto_read_footer(svn_fs_fs__revision_file_t *file)
SVN_ERR(svn_fs_fs__parse_footer(&file->l2p_offset, &file->l2p_checksum,
&file->p2l_offset, &file->p2l_checksum,
footer, file->start_revision,
+ filesize - footer_length - 1,
file->pool));
file->footer_offset = filesize - footer_length - 1;
}
diff --git a/subversion/libsvn_fs_fs/revprops.c b/subversion/libsvn_fs_fs/revprops.c
index dbb185beca796..6d41fd882d7ef 100644
--- a/subversion/libsvn_fs_fs/revprops.c
+++ b/subversion/libsvn_fs_fs/revprops.c
@@ -25,9 +25,11 @@
#include "svn_pools.h"
#include "svn_hash.h"
#include "svn_dirent_uri.h"
+#include "svn_sorts.h"
#include "fs_fs.h"
#include "revprops.h"
+#include "temp_serializer.h"
#include "util.h"
#include "private/svn_subr_private.h"
@@ -36,11 +38,6 @@
#include "svn_private_config.h"
-/* Give writing processes 10 seconds to replace an existing revprop
- file with a new one. After that time, we assume that the writing
- process got aborted and that we have re-read revprops. */
-#define REVPROP_CHANGE_TIMEOUT (10 * 1000000)
-
svn_error_t *
svn_fs_fs__upgrade_pack_revprops(svn_fs_t *fs,
svn_fs_upgrade_notify_t notify_func,
@@ -82,6 +79,7 @@ svn_fs_fs__upgrade_pack_revprops(svn_fs_t *fs,
shard, ffd->max_files_per_dir,
(int)(0.9 * ffd->revprop_pack_size),
compression_level,
+ ffd->flush_to_disk,
cancel_func, cancel_baton,
iterpool));
if (notify_func)
@@ -144,9 +142,6 @@ typedef struct packed_revprops_t
/* revision number to read (not necessarily the first in the pack) */
svn_revnum_t revision;
- /* current revprop generation. Used when populating the revprop cache */
- apr_int64_t generation;
-
/* the actual revision properties */
apr_hash_t *properties;
@@ -189,35 +184,84 @@ typedef struct packed_revprops_t
/* Parse the serialized revprops in CONTENT and return them in *PROPERTIES.
* Also, put them into the revprop cache, if activated, for future use.
- * Three more parameters are being used to update the revprop cache: FS is
- * our file system, the revprops belong to REVISION and the global revprop
- * GENERATION is used as well.
*
- * The returned hash will be allocated in POOL, SCRATCH_POOL is being used
- * for temporary allocations.
+ * The returned hash will be allocated in RESULT_POOL, SCRATCH_POOL is being
+ * used for temporary allocations.
*/
static svn_error_t *
parse_revprop(apr_hash_t **properties,
svn_fs_t *fs,
svn_revnum_t revision,
- apr_int64_t generation,
svn_string_t *content,
- apr_pool_t *pool,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_stream_t *stream = svn_stream_from_string(content, scratch_pool);
- *properties = apr_hash_make(pool);
+ *properties = apr_hash_make(result_pool);
- SVN_ERR_W(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR, pool),
+ SVN_ERR_W(svn_hash_read2(*properties, stream, SVN_HASH_TERMINATOR,
+ result_pool),
apr_psprintf(scratch_pool, "Failed to parse revprops for r%ld.",
revision));
return SVN_NO_ERROR;
}
+void
+svn_fs_fs__reset_revprop_cache(svn_fs_t *fs)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ ffd->revprop_prefix = 0;
+}
+
+/* If FS has not a revprop cache prefix set, generate one.
+ * Always call this before accessing the revprop cache.
+ */
+static svn_error_t *
+prepare_revprop_cache(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ if (!ffd->revprop_prefix)
+ SVN_ERR(svn_atomic__unique_counter(&ffd->revprop_prefix));
+
+ return SVN_NO_ERROR;
+}
+
+/* Store the unparsed revprop hash CONTENT for REVISION in FS's revprop
+ * cache. If CACHED is not NULL, set *CACHED if there already is such
+ * an entry and skip the cache write in that case. Use SCRATCH_POOL for
+ * temporary allocations. */
+static svn_error_t *
+cache_revprops(svn_boolean_t *is_cached,
+ svn_fs_t *fs,
+ svn_revnum_t revision,
+ svn_string_t *content,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ pair_cache_key_t key;
+
+ /* Make sure prepare_revprop_cache() has been called. */
+ SVN_ERR_ASSERT(ffd->revprop_prefix);
+ key.revision = revision;
+ key.second = ffd->revprop_prefix;
+
+ if (is_cached)
+ {
+ SVN_ERR(svn_cache__has_key(is_cached, ffd->revprop_cache, &key,
+ scratch_pool));
+ if (*is_cached)
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(svn_cache__set(ffd->revprop_cache, &key, content, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
/* Read the non-packed revprops for revision REV in FS, put them into the
- * revprop cache if activated and return them in *PROPERTIES. GENERATION
- * is the current revprop generation.
+ * revprop cache if PROPULATE_CACHE is set and return them in *PROPERTIES.
*
* If the data could not be read due to an otherwise recoverable error,
* leave *PROPERTIES unchanged. No error will be returned in that case.
@@ -228,7 +272,7 @@ static svn_error_t *
read_non_packed_revprop(apr_hash_t **properties,
svn_fs_t *fs,
svn_revnum_t rev,
- apr_int64_t generation,
+ svn_boolean_t populate_cache,
apr_pool_t *pool)
{
svn_stringbuf_t *content = NULL;
@@ -249,9 +293,13 @@ read_non_packed_revprop(apr_hash_t **properties,
}
if (content)
- SVN_ERR(parse_revprop(properties, fs, rev, generation,
- svn_stringbuf__morph_into_string(content),
- pool, iterpool));
+ {
+ svn_string_t *as_string = svn_stringbuf__morph_into_string(content);
+ SVN_ERR(parse_revprop(properties, fs, rev, as_string, pool, iterpool));
+
+ if (populate_cache)
+ SVN_ERR(cache_revprops(NULL, fs, rev, as_string, iterpool));
+ }
svn_pool_clear(iterpool);
@@ -272,12 +320,13 @@ get_min_filename_len(packed_revprops_t *revprops)
}
/* Given FS and REVPROPS->REVISION, fill the FILENAME, FOLDER and MANIFEST
- * members. Use POOL for allocating results and SCRATCH_POOL for temporaries.
+ * members. Use RESULT_POOL for allocating results and SCRATCH_POOL for
+ * temporaries.
*/
static svn_error_t *
get_revprop_packname(svn_fs_t *fs,
packed_revprops_t *revprops,
- apr_pool_t *pool,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
@@ -298,18 +347,20 @@ get_revprop_packname(svn_fs_t *fs,
--rev_count;
}
- revprops->manifest = apr_array_make(pool, rev_count, sizeof(const char*));
+ revprops->manifest = apr_array_make(result_pool, rev_count,
+ sizeof(const char*));
/* No line in the file can be less than this number of chars long. */
min_filename_len = get_min_filename_len(revprops);
/* Read the content of the manifest file */
revprops->folder
- = svn_fs_fs__path_revprops_pack_shard(fs, revprops->revision, pool);
+ = svn_fs_fs__path_revprops_pack_shard(fs, revprops->revision,
+ result_pool);
manifest_file_path
- = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
+ = svn_dirent_join(revprops->folder, PATH_MANIFEST, result_pool);
- SVN_ERR(svn_fs_fs__read_content(&content, manifest_file_path, pool));
+ SVN_ERR(svn_fs_fs__read_content(&content, manifest_file_path, result_pool));
/* There CONTENT must have a certain minimal size and there no
* unterminated lines at the end of the file. Both guarantees also
@@ -392,7 +443,8 @@ same_shard(svn_fs_t *fs,
/* Given FS and the full packed file content in REVPROPS->PACKED_REVPROPS,
* fill the START_REVISION member, and make PACKED_REVPROPS point to the
* first serialized revprop. If READ_ALL is set, initialize the SIZES
- * and OFFSETS members as well.
+ * and OFFSETS members as well. If POPULATE_CACHE is set, cache all
+ * revprops found in this pack.
*
* Parse the revprops for REVPROPS->REVISION and set the PROPERTIES as
* well as the SERIALIZED_SIZE member. If revprop caching has been
@@ -402,20 +454,25 @@ static svn_error_t *
parse_packed_revprops(svn_fs_t *fs,
packed_revprops_t *revprops,
svn_boolean_t read_all,
- apr_pool_t *pool,
+ svn_boolean_t populate_cache,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_stream_t *stream;
apr_int64_t first_rev, count, i;
- apr_off_t offset;
+ apr_size_t offset;
const char *header_end;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ /* Initial value for the "Leaking bucket" pattern. */
+ int bucket = 4;
+
/* decompress (even if the data is only "stored", there is still a
* length header to remove) */
svn_stringbuf_t *compressed = revprops->packed_revprops;
- svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(pool);
- SVN_ERR(svn__decompress(compressed, uncompressed, APR_SIZE_MAX));
+ svn_stringbuf_t *uncompressed = svn_stringbuf_create_empty(result_pool);
+ SVN_ERR(svn__decompress_zlib(compressed->data, compressed->len,
+ uncompressed, APR_SIZE_MAX));
/* read first revision number and number of revisions in the pack */
stream = svn_stream_from_stringbuf(uncompressed, scratch_pool);
@@ -453,18 +510,21 @@ parse_packed_revprops(svn_fs_t *fs,
offset = header_end - uncompressed->data + 2;
- revprops->packed_revprops = svn_stringbuf_create_empty(pool);
+ revprops->packed_revprops = svn_stringbuf_create_empty(result_pool);
revprops->packed_revprops->data = uncompressed->data + offset;
revprops->packed_revprops->len = (apr_size_t)(uncompressed->len - offset);
- revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize - offset);
+ revprops->packed_revprops->blocksize = (apr_size_t)(uncompressed->blocksize
+ - offset);
/* STREAM still points to the first entry in the sizes list. */
revprops->start_revision = (svn_revnum_t)first_rev;
if (read_all)
{
/* Init / construct REVPROPS members. */
- revprops->sizes = apr_array_make(pool, (int)count, sizeof(offset));
- revprops->offsets = apr_array_make(pool, (int)count, sizeof(offset));
+ revprops->sizes = apr_array_make(result_pool, (int)count,
+ sizeof(offset));
+ revprops->offsets = apr_array_make(result_pool, (int)count,
+ sizeof(offset));
}
/* Now parse, revision by revision, the size and content of each
@@ -479,7 +539,7 @@ parse_packed_revprops(svn_fs_t *fs,
/* read & check the serialized size */
SVN_ERR(svn_fs_fs__read_number_from_stream(&size, NULL, stream,
iterpool));
- if (size + offset > (apr_int64_t)revprops->packed_revprops->len)
+ if (size > (apr_int64_t)revprops->packed_revprops->len - offset)
return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
_("Packed revprop size exceeds pack file size"));
@@ -489,21 +549,47 @@ parse_packed_revprops(svn_fs_t *fs,
if (revision == revprops->revision)
{
+ /* Parse (and possibly cache) the one revprop list we care about. */
SVN_ERR(parse_revprop(&revprops->properties, fs, revision,
- revprops->generation, &serialized,
- pool, iterpool));
+ &serialized, result_pool, iterpool));
revprops->serialized_size = serialized.len;
/* If we only wanted the revprops for REVISION then we are done. */
- if (!read_all)
+ if (!read_all && !populate_cache)
break;
}
+ if (populate_cache)
+ {
+ /* Adding all those revprops is expensive, in particular in a
+ * multi-threaded environment. There are situations where hit
+ * rates are low and revprops get evicted before re-using them.
+ *
+ * We try to detect thosse cases here.
+ * Only keep going while most (at least 2/3) aren't cached, yet. */
+ svn_boolean_t already_cached;
+ SVN_ERR(cache_revprops(&already_cached, fs, revision, &serialized,
+ iterpool));
+
+ /* Stop populating the cache once we encountered too many entries
+ * already present relative to the numbers being added. */
+ if (!already_cached)
+ {
+ ++bucket;
+ }
+ else
+ {
+ bucket -= 2;
+ if (bucket < 0)
+ populate_cache = FALSE;
+ }
+ }
+
if (read_all)
{
/* fill REVPROPS data structures */
- APR_ARRAY_PUSH(revprops->sizes, apr_off_t) = serialized.len;
- APR_ARRAY_PUSH(revprops->offsets, apr_off_t) = offset;
+ APR_ARRAY_PUSH(revprops->sizes, apr_size_t) = serialized.len;
+ APR_ARRAY_PUSH(revprops->offsets, apr_size_t) = offset;
}
revprops->total_size += serialized.len;
@@ -514,7 +600,7 @@ parse_packed_revprops(svn_fs_t *fs,
}
/* In filesystem FS, read the packed revprops for revision REV into
- * *REVPROPS. Use GENERATION to populate the revprop cache, if enabled.
+ * *REVPROPS. Populate the revprop cache, if POPULATE_CACHE is set.
* If you want to modify revprop contents / update REVPROPS, READ_ALL
* must be set. Otherwise, only the properties of REV are being provided.
* Allocate data in POOL.
@@ -523,8 +609,8 @@ static svn_error_t *
read_pack_revprop(packed_revprops_t **revprops,
svn_fs_t *fs,
svn_revnum_t rev,
- apr_int64_t generation,
svn_boolean_t read_all,
+ svn_boolean_t populate_cache,
apr_pool_t *pool)
{
apr_pool_t *iterpool = svn_pool_create(pool);
@@ -544,7 +630,6 @@ read_pack_revprop(packed_revprops_t **revprops,
/* initialize the result data structure */
result = apr_pcalloc(pool, sizeof(*result));
result->revision = rev;
- result->generation = generation;
/* try to read the packed revprops. This may require retries if we have
* concurrent writers. */
@@ -575,7 +660,8 @@ read_pack_revprop(packed_revprops_t **revprops,
_("Failed to read revprop pack file for r%ld"), rev);
/* parse it. RESULT will be complete afterwards. */
- err = parse_packed_revprops(fs, result, read_all, pool, iterpool);
+ err = parse_packed_revprops(fs, result, read_all, populate_cache, pool,
+ iterpool);
svn_pool_destroy(iterpool);
if (err)
return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
@@ -594,16 +680,48 @@ svn_error_t *
svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
svn_fs_t *fs,
svn_revnum_t rev,
- apr_pool_t *pool)
+ svn_boolean_t refresh,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
- apr_int64_t generation = 0;
+
+ /* Only populate the cache if we did not just cross a sync barrier.
+ * This is to eliminate overhead from code that always sets REFRESH.
+ * For callers that want caching, the caching kicks in on read "later". */
+ svn_boolean_t populate_cache = !refresh;
/* not found, yet */
*proplist_p = NULL;
/* should they be available at all? */
- SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, pool));
+ SVN_ERR(svn_fs_fs__ensure_revision_exists(rev, fs, scratch_pool));
+
+ if (refresh)
+ {
+ /* Previous cache contents is invalid now. */
+ svn_fs_fs__reset_revprop_cache(fs);
+ }
+ else
+ {
+ /* Try cache lookup first. */
+ svn_boolean_t is_cached;
+ pair_cache_key_t key;
+
+ /* Auto-alloc prefix and construct the key. */
+ SVN_ERR(prepare_revprop_cache(fs, scratch_pool));
+ key.revision = rev;
+ key.second = ffd->revprop_prefix;
+
+ /* The only way that this might error out is due to parser error. */
+ SVN_ERR_W(svn_cache__get((void **) proplist_p, &is_cached,
+ ffd->revprop_cache, &key, result_pool),
+ apr_psprintf(scratch_pool,
+ "Failed to parse revprops for r%ld.",
+ rev));
+ if (is_cached)
+ return SVN_NO_ERROR;
+ }
/* if REV had not been packed when we began, try reading it from the
* non-packed shard. If that fails, we will fall through to packed
@@ -611,7 +729,7 @@ svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
if (!svn_fs_fs__is_packed_revprop(fs, rev))
{
svn_error_t *err = read_non_packed_revprop(proplist_p, fs, rev,
- generation, pool);
+ populate_cache, result_pool);
if (err)
{
if (!APR_STATUS_IS_ENOENT(err->apr_err)
@@ -629,7 +747,8 @@ svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT && !*proplist_p)
{
packed_revprops_t *revprops;
- SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, FALSE, pool));
+ SVN_ERR(read_pack_revprop(&revprops, fs, rev, FALSE, populate_cache,
+ result_pool));
*proplist_p = revprops->properties;
}
@@ -657,6 +776,7 @@ write_non_packed_revprop(const char **final_path,
apr_hash_t *proplist,
apr_pool_t *pool)
{
+ fs_fs_data_t *ffd = fs->fsap_data;
apr_file_t *file;
svn_stream_t *stream;
*final_path = svn_fs_fs__path_revprops(fs, rev, pool);
@@ -671,7 +791,8 @@ write_non_packed_revprop(const char **final_path,
SVN_ERR(svn_stream_close(stream));
/* Flush temporary file to disk and close it. */
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ if (ffd->flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
@@ -694,8 +815,10 @@ switch_to_new_revprop(svn_fs_t *fs,
apr_array_header_t *files_to_delete,
apr_pool_t *pool)
{
+ fs_fs_data_t *ffd = fs->fsap_data;
+
SVN_ERR(svn_fs_fs__move_into_place(tmp_path, final_path, perms_reference,
- pool));
+ ffd->flush_to_disk, pool));
/* Clean up temporary files, if necessary. */
if (files_to_delete)
@@ -744,13 +867,13 @@ serialize_revprops_header(svn_stream_t *stream,
* We only allocate a few bytes each iteration -- even with a
* million iterations we would still be in good shape memory-wise.
*/
- apr_off_t size = APR_ARRAY_IDX(sizes, i, apr_off_t);
- SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_OFF_T_FMT "\n",
+ apr_size_t size = APR_ARRAY_IDX(sizes, i, apr_size_t);
+ SVN_ERR(svn_stream_printf(stream, iterpool, "%" APR_SIZE_T_FMT "\n",
size));
}
/* the double newline char indicates the end of the header */
- SVN_ERR(svn_stream_printf(stream, iterpool, "\n"));
+ SVN_ERR(svn_stream_puts(stream, "\n"));
svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
@@ -773,7 +896,7 @@ repack_revprops(svn_fs_t *fs,
int end,
int changed_index,
svn_stringbuf_t *new_serialized,
- apr_off_t new_total_size,
+ apr_size_t new_total_size,
apr_file_t *file,
apr_pool_t *pool)
{
@@ -802,10 +925,8 @@ repack_revprops(svn_fs_t *fs,
}
else
{
- apr_size_t size
- = (apr_size_t)APR_ARRAY_IDX(revprops->sizes, i, apr_off_t);
- apr_size_t offset
- = (apr_size_t)APR_ARRAY_IDX(revprops->offsets, i, apr_off_t);
+ apr_size_t size = APR_ARRAY_IDX(revprops->sizes, i, apr_size_t);
+ apr_size_t offset = APR_ARRAY_IDX(revprops->offsets, i, apr_size_t);
SVN_ERR(svn_stream_write(stream,
revprops->packed_revprops->data + offset,
@@ -816,16 +937,17 @@ repack_revprops(svn_fs_t *fs,
SVN_ERR(svn_stream_close(stream));
/* compress / store the data */
- SVN_ERR(svn__compress(uncompressed,
- compressed,
- ffd->compress_packed_revprops
- ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
- : SVN_DELTA_COMPRESSION_LEVEL_NONE));
+ SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len,
+ compressed,
+ ffd->compress_packed_revprops
+ ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+ : SVN_DELTA_COMPRESSION_LEVEL_NONE));
/* finally, write the content to the target file, flush and close it */
SVN_ERR(svn_io_file_write_full(file, compressed->data, compressed->len,
NULL, pool));
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ if (ffd->flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
@@ -848,7 +970,7 @@ repack_file_open(apr_file_t **file,
{
apr_int64_t tag;
const char *tag_string;
- svn_string_t *new_filename;
+ const char *new_filename;
int i;
int manifest_offset
= (int)(revprops->start_revision - revprops->manifest_start);
@@ -872,18 +994,18 @@ repack_file_open(apr_file_t **file,
old_filename);
SVN_ERR(svn_cstring_atoi64(&tag, tag_string + 1));
- new_filename = svn_string_createf(pool, "%ld.%" APR_INT64_T_FMT,
- revprops->start_revision + start,
- ++tag);
+ new_filename = apr_psprintf(pool, "%ld.%" APR_INT64_T_FMT,
+ revprops->start_revision + start,
+ ++tag);
/* update the manifest to point to the new file */
for (i = start; i < end; ++i)
APR_ARRAY_IDX(revprops->manifest, i + manifest_offset, const char*)
- = new_filename->data;
+ = new_filename;
/* open the file */
SVN_ERR(svn_io_file_open(file, svn_dirent_join(revprops->folder,
- new_filename->data,
+ new_filename,
pool),
APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
@@ -907,15 +1029,14 @@ write_packed_revprop(const char **final_path,
{
fs_fs_data_t *ffd = fs->fsap_data;
packed_revprops_t *revprops;
- apr_int64_t generation = 0;
svn_stream_t *stream;
apr_file_t *file;
svn_stringbuf_t *serialized;
- apr_off_t new_total_size;
+ apr_size_t new_total_size;
int changed_index;
/* read contents of the current pack file */
- SVN_ERR(read_pack_revprop(&revprops, fs, rev, generation, TRUE, pool));
+ SVN_ERR(read_pack_revprop(&revprops, fs, rev, TRUE, FALSE, pool));
/* serialize the new revprops */
serialized = svn_stringbuf_create_empty(pool);
@@ -929,7 +1050,7 @@ write_packed_revprop(const char **final_path,
+ serialized->len
+ (revprops->offsets->nelts + 2) * SVN_INT64_BUFFER_SIZE;
- APR_ARRAY_IDX(revprops->sizes, changed_index, apr_off_t) = serialized->len;
+ APR_ARRAY_IDX(revprops->sizes, changed_index, apr_size_t) = serialized->len;
/* can we put the new data into the same pack as the before? */
if ( new_total_size < ffd->revprop_pack_size
@@ -953,23 +1074,23 @@ write_packed_revprop(const char **final_path,
int left = 0;
int right = revprops->sizes->nelts - 1;
- apr_off_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
- apr_off_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
+ apr_size_t left_size = 2 * SVN_INT64_BUFFER_SIZE;
+ apr_size_t right_size = 2 * SVN_INT64_BUFFER_SIZE;
/* let left and right side grow such that their size difference
* is minimal after each step. */
while (left <= right)
- if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
- < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_off_t))
+ if ( left_size + APR_ARRAY_IDX(revprops->sizes, left, apr_size_t)
+ < right_size + APR_ARRAY_IDX(revprops->sizes, right, apr_size_t))
{
- left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_off_t)
+ left_size += APR_ARRAY_IDX(revprops->sizes, left, apr_size_t)
+ SVN_INT64_BUFFER_SIZE;
++left;
}
else
{
- right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_off_t)
- + SVN_INT64_BUFFER_SIZE;
+ right_size += APR_ARRAY_IDX(revprops->sizes, right, apr_size_t)
+ + SVN_INT64_BUFFER_SIZE;
--right;
}
@@ -1025,17 +1146,16 @@ write_packed_revprop(const char **final_path,
*final_path = svn_dirent_join(revprops->folder, PATH_MANIFEST, pool);
SVN_ERR(svn_io_open_unique_file3(&file, tmp_path, revprops->folder,
svn_io_file_del_none, pool, pool));
-
+ stream = svn_stream_from_aprfile2(file, TRUE, pool);
for (i = 0; i < revprops->manifest->nelts; ++i)
{
const char *filename = APR_ARRAY_IDX(revprops->manifest, i,
const char*);
- SVN_ERR(svn_io_file_write_full(file, filename, strlen(filename),
- NULL, pool));
- SVN_ERR(svn_io_file_putc('\n', file, pool));
+ SVN_ERR(svn_stream_printf(stream, pool, "%s\n", filename));
}
-
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_stream_close(stream));
+ if (ffd->flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
SVN_ERR(svn_io_file_close(file, pool));
}
@@ -1069,6 +1189,9 @@ svn_fs_fs__set_revision_proplist(svn_fs_t *fs,
SVN_ERR(write_non_packed_revprop(&final_path, &tmp_path,
fs, rev, proplist, pool));
+ /* Previous cache contents is invalid now. */
+ svn_fs_fs__reset_revprop_cache(fs);
+
/* We use the rev file of this revision as the perms reference,
* because when setting revprops for the first time, the revprop
* file won't exist and therefore can't serve as its own reference.
@@ -1167,6 +1290,7 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
apr_array_header_t *sizes,
apr_size_t total_size,
int compression_level,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
@@ -1199,6 +1323,7 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
{
const char *path;
svn_stream_t *stream;
+ apr_file_t *file;
svn_pool_clear(iterpool);
@@ -1207,8 +1332,11 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
iterpool);
/* Copy all the bits from the non-packed revprop file to the end of
- * the pack file. */
- SVN_ERR(svn_stream_open_readonly(&stream, path, iterpool, iterpool));
+ * the pack file. Use unbuffered apr_file_t since we're going to
+ * write using 16kb chunks. */
+ SVN_ERR(svn_io_file_open(&file, path, APR_READ, APR_OS_DEFAULT,
+ iterpool));
+ stream = svn_stream_from_aprfile2(file, FALSE, iterpool);
SVN_ERR(svn_stream_copy3(stream, pack_stream,
cancel_func, cancel_baton, iterpool));
}
@@ -1217,12 +1345,14 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
SVN_ERR(svn_stream_close(pack_stream));
/* compress the content (or just store it for COMPRESSION_LEVEL 0) */
- SVN_ERR(svn__compress(uncompressed, compressed, compression_level));
+ SVN_ERR(svn__compress_zlib(uncompressed->data, uncompressed->len,
+ compressed, compression_level));
/* write the pack file content to disk */
SVN_ERR(svn_io_file_write_full(pack_file, compressed->data, compressed->len,
NULL, scratch_pool));
- SVN_ERR(svn_io_file_flush_to_disk(pack_file, scratch_pool));
+ if (flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(pack_file, scratch_pool));
SVN_ERR(svn_io_file_close(pack_file, scratch_pool));
svn_pool_destroy(iterpool);
@@ -1235,8 +1365,9 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
const char *shard_path,
apr_int64_t shard,
int max_files_per_dir,
- apr_off_t max_pack_size,
+ apr_int64_t max_pack_size,
int compression_level,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
@@ -1245,10 +1376,14 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
apr_file_t *manifest_file;
svn_stream_t *manifest_stream;
svn_revnum_t start_rev, end_rev, rev;
- apr_off_t total_size;
+ apr_size_t total_size;
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
apr_array_header_t *sizes;
+ /* Sanitize config file values. */
+ apr_size_t max_size = (apr_size_t)MIN(MAX(max_pack_size, 1),
+ SVN_MAX_OBJECT_SIZE);
+
/* Some useful paths. */
manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST,
scratch_pool);
@@ -1276,7 +1411,7 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
works. */
/* initialize the revprop size info */
- sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_off_t));
+ sizes = apr_array_make(scratch_pool, max_files_per_dir, sizeof(apr_size_t));
total_size = 2 * SVN_INT64_BUFFER_SIZE;
/* Iterate over the revisions in this shard, determine their size and
@@ -1293,16 +1428,20 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
iterpool);
SVN_ERR(svn_io_stat(&finfo, path, APR_FINFO_SIZE, iterpool));
- /* if we already have started a pack file and this revprop cannot be
- * appended to it, write the previous pack file. */
- if (sizes->nelts != 0 &&
- total_size + SVN_INT64_BUFFER_SIZE + finfo.size > max_pack_size)
+ /* If we already have started a pack file and this revprop cannot be
+ * appended to it, write the previous pack file. Note this overflow
+ * check works because we enforced MAX_SIZE <= SVN_MAX_OBJECT_SIZE. */
+ if (sizes->nelts != 0
+ && ( finfo.size > max_size
+ || total_size > max_size
+ || SVN_INT64_BUFFER_SIZE + finfo.size > max_size - total_size))
{
SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
shard_path, start_rev, rev-1,
- sizes, (apr_size_t)total_size,
- compression_level, cancel_func,
- cancel_baton, iterpool));
+ sizes, total_size,
+ compression_level, flush_to_disk,
+ cancel_func, cancel_baton,
+ iterpool));
/* next pack file starts empty again */
apr_array_clear(sizes);
@@ -1319,7 +1458,7 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
pack_filename));
/* add to list of files to put into the current pack file */
- APR_ARRAY_PUSH(sizes, apr_off_t) = finfo.size;
+ APR_ARRAY_PUSH(sizes, apr_size_t) = finfo.size;
total_size += SVN_INT64_BUFFER_SIZE + finfo.size;
}
@@ -1328,12 +1467,13 @@ svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
SVN_ERR(svn_fs_fs__copy_revprops(pack_file_dir, pack_filename,
shard_path, start_rev, rev-1,
sizes, (apr_size_t)total_size,
- compression_level, cancel_func,
- cancel_baton, iterpool));
+ compression_level, flush_to_disk,
+ cancel_func, cancel_baton, iterpool));
/* flush the manifest file to disk and update permissions */
SVN_ERR(svn_stream_close(manifest_stream));
- SVN_ERR(svn_io_file_flush_to_disk(manifest_file, iterpool));
+ if (flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(manifest_file, iterpool));
SVN_ERR(svn_io_file_close(manifest_file, iterpool));
SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
@@ -1365,7 +1505,7 @@ svn_fs_fs__delete_revprops_shard(const char *shard_path,
apr_psprintf(iterpool, "%d", i),
iterpool);
if (cancel_func)
- SVN_ERR((*cancel_func)(cancel_baton));
+ SVN_ERR(cancel_func(cancel_baton));
SVN_ERR(svn_io_remove_file2(path, TRUE, iterpool));
}
diff --git a/subversion/libsvn_fs_fs/revprops.h b/subversion/libsvn_fs_fs/revprops.h
index 66c137c33bf55..37063f96e9ae6 100644
--- a/subversion/libsvn_fs_fs/revprops.h
+++ b/subversion/libsvn_fs_fs/revprops.h
@@ -58,15 +58,23 @@ svn_fs_fs__upgrade_cleanup_pack_revprops(svn_fs_t *fs,
void *cancel_baton,
apr_pool_t *scratch_pool);
+/* Invalidate the revprop cache in FS. */
+void
+svn_fs_fs__reset_revprop_cache(svn_fs_t *fs);
+
/* Read the revprops for revision REV in FS and return them in *PROPERTIES_P.
+ * If REFRESH is set, clear the revprop cache before accessing the data.
*
- * Allocations will be done in POOL.
+ * The result will be allocated in RESULT_POOL; SCRATCH_POOL is used for
+ * temporaries.
*/
svn_error_t *
svn_fs_fs__get_revision_proplist(apr_hash_t **proplist_p,
svn_fs_t *fs,
svn_revnum_t rev,
- apr_pool_t *pool);
+ svn_boolean_t refresh,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
/* Set the revision property list of revision REV in filesystem FS to
PROPLIST. Use POOL for temporary allocations. */
@@ -103,8 +111,9 @@ svn_fs_fs__packed_revprop_available(svn_boolean_t *missing,
* a hint on which initial buffer size we should use to hold the pack file
* content.
*
- * CANCEL_FUNC and CANCEL_BATON are used as usual. Temporary allocations
- * are done in SCRATCH_POOL.
+ * If FLUSH_TO_DISK is non-zero, do not return until the data has actually
+ * been written on the disk. CANCEL_FUNC and CANCEL_BATON are used as usual.
+ * Temporary allocations are done in SCRATCH_POOL.
*/
svn_error_t *
svn_fs_fs__copy_revprops(const char *pack_file_dir,
@@ -115,6 +124,7 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
apr_array_header_t *sizes,
apr_size_t total_size,
int compression_level,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool);
@@ -126,16 +136,18 @@ svn_fs_fs__copy_revprops(const char *pack_file_dir,
* have no unpacked data anymore. Call upgrade_cleanup_pack_revprops after
* the bump.
*
- * NOTIFY_FUNC and NOTIFY_BATON as well as CANCEL_FUNC and CANCEL_BATON are
- * used in the usual way. Temporary allocations are done in SCRATCH_POOL.
+ * If FLUSH_TO_DISK is non-zero, do not return until the data has actually
+ * been written on the disk. CANCEL_FUNC and CANCEL_BATON areused in the
+ * usual way. Temporary allocations are done in SCRATCH_POOL.
*/
svn_error_t *
svn_fs_fs__pack_revprops_shard(const char *pack_file_dir,
const char *shard_path,
apr_int64_t shard,
int max_files_per_dir,
- apr_off_t max_pack_size,
+ apr_int64_t max_pack_size,
int compression_level,
+ svn_boolean_t flush_to_disk,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool);
diff --git a/subversion/libsvn_fs_fs/stats.c b/subversion/libsvn_fs_fs/stats.c
index 97a2ed7736f36..36992dbaeb66e 100644
--- a/subversion/libsvn_fs_fs/stats.c
+++ b/subversion/libsvn_fs_fs/stats.c
@@ -70,8 +70,9 @@ typedef enum rep_kind_t
*/
typedef struct rep_stats_t
{
- /* absolute offset in the file */
- apr_off_t offset;
+ /* offset in the revision file (phys. addressing) /
+ * item index within REVISION (log. addressing) */
+ apr_uint64_t item_index;
/* item length in bytes */
apr_uint64_t size;
@@ -92,8 +93,36 @@ typedef struct rep_stats_t
/* classification of the representation. values of rep_kind_t */
char kind;
+ /* length of the delta chain, including this representation,
+ * saturated to 255 - if need be */
+ apr_byte_t chain_length;
} rep_stats_t;
+/* Represents a link in the rep delta chain. REVISION + ITEM_INDEX points
+ * to BASE_REVISION + BASE_ITEM_INDEX. We collect this info while scanning
+ * a f7 repo in a single pass and resolve it afterwards. */
+typedef struct rep_ref_t
+{
+ /* Revision that contains this representation. */
+ svn_revnum_t revision;
+
+ /* Item index of this rep within REVISION. */
+ apr_uint64_t item_index;
+
+ /* Revision of the representation we deltified against.
+ * -1 if this representation is either PLAIN or a self-delta. */
+ svn_revnum_t base_revision;
+
+ /* Item index of that rep within BASE_REVISION. */
+ apr_uint64_t base_item_index;
+
+ /* Length of the PLAIN / DELTA line in the source file in bytes.
+ * We use this to update the info in the rep stats after scanning the
+ * whole file. */
+ apr_uint16_t header_size;
+
+} rep_ref_t;
+
/* Represents a single revision.
* There will be only one instance per revision. */
typedef struct revision_info_t
@@ -176,23 +205,6 @@ typedef struct query_t
void *cancel_baton;
} query_t;
-/* Return the length of REV_FILE in *FILE_SIZE.
- * Use SCRATCH_POOL for temporary allocations.
- */
-static svn_error_t *
-get_file_size(apr_off_t *file_size,
- svn_fs_fs__revision_file_t *rev_file,
- apr_pool_t *scratch_pool)
-{
- apr_finfo_t finfo;
-
- SVN_ERR(svn_io_file_info_get(&finfo, APR_FINFO_SIZE, rev_file->file,
- scratch_pool));
-
- *file_size = finfo.size;
- return SVN_NO_ERROR;
-}
-
/* Initialize the LARGEST_CHANGES member in STATS with a capacity of COUNT
* entries. Allocate the result in RESULT_POOL.
*/
@@ -345,13 +357,13 @@ add_change(svn_fs_fs__stats_t *stats,
/* Comparator used for binary search comparing the absolute file offset
* of a representation to some other offset. DATA is a *rep_stats_t,
- * KEY is a pointer to an apr_off_t.
+ * KEY is a pointer to an apr_uint64_t.
*/
static int
-compare_representation_offsets(const void *data, const void *key)
+compare_representation_item_index(const void *data, const void *key)
{
- apr_off_t lhs = (*(const rep_stats_t *const *)data)->offset;
- apr_off_t rhs = *(const apr_off_t *)key;
+ apr_uint64_t lhs = (*(const rep_stats_t *const *)data)->item_index;
+ apr_uint64_t rhs = *(const apr_uint64_t *)key;
if (lhs < rhs)
return -1;
@@ -362,7 +374,7 @@ compare_representation_offsets(const void *data, const void *key)
* return it in *REVISION_INFO. For performance reasons, we skip the
* lookup if the info is already provided.
*
- * In that revision, look for the rep_stats_t object for offset OFFSET.
+ * In that revision, look for the rep_stats_t object for item ITEM_INDEX.
* If it already exists, set *IDX to its index in *REVISION_INFO's
* representations list and return the representation object. Otherwise,
* set the index to where it must be inserted and return NULL.
@@ -372,7 +384,7 @@ find_representation(int *idx,
query_t *query,
revision_info_t **revision_info,
svn_revnum_t revision,
- apr_off_t offset)
+ apr_uint64_t item_index)
{
revision_info_t *info;
*idx = -1;
@@ -392,14 +404,14 @@ find_representation(int *idx,
/* look for the representation */
*idx = svn_sort__bsearch_lower_bound(info->representations,
- &offset,
- compare_representation_offsets);
+ &item_index,
+ compare_representation_item_index);
if (*idx < info->representations->nelts)
{
/* return the representation, if this is the one we were looking for */
rep_stats_t *result
= APR_ARRAY_IDX(info->representations, *idx, rep_stats_t *);
- if (result->offset == offset)
+ if (result->item_index == item_index)
return result;
}
@@ -428,7 +440,7 @@ parse_representation(rep_stats_t **representation,
/* look it up */
result = find_representation(&idx, query, &revision_info, rep->revision,
- (apr_off_t)rep->item_index);
+ rep->item_index);
if (!result)
{
/* not parsed, yet (probably a rep in the same revision).
@@ -436,9 +448,8 @@ parse_representation(rep_stats_t **representation,
*/
result = apr_pcalloc(result_pool, sizeof(*result));
result->revision = rep->revision;
- result->expanded_size = (rep->expanded_size ? rep->expanded_size
- : rep->size);
- result->offset = (apr_off_t)rep->item_index;
+ result->expanded_size = rep->expanded_size;
+ result->item_index = rep->item_index;
result->size = rep->size;
/* In phys. addressing mode, follow link to the actual representation.
@@ -447,7 +458,8 @@ parse_representation(rep_stats_t **representation,
if (!svn_fs_fs__use_log_addressing(query->fs))
{
svn_fs_fs__rep_header_t *header;
- apr_off_t offset = revision_info->offset + result->offset;
+ apr_off_t offset = revision_info->offset
+ + (apr_off_t)rep->item_index;
SVN_ERR_ASSERT(revision_info->rev_file);
SVN_ERR(svn_io_file_seek(revision_info->rev_file->file, APR_SET,
@@ -457,6 +469,23 @@ parse_representation(rep_stats_t **representation,
scratch_pool, scratch_pool));
result->header_size = header->header_size;
+
+ /* Determine length of the delta chain. */
+ if (header->type == svn_fs_fs__rep_delta)
+ {
+ int base_idx;
+ rep_stats_t *base_rep
+ = find_representation(&base_idx, query, NULL,
+ header->base_revision,
+ header->base_item_index);
+
+ result->chain_length = 1 + MIN(base_rep->chain_length,
+ (apr_byte_t)0xfe);
+ }
+ else
+ {
+ result->chain_length = 1;
+ }
}
svn_sort__array_insert(revision_info->representations, &result, idx);
@@ -588,6 +617,10 @@ read_noderev(query_t *query,
svn_stream_t *stream = svn_stream_from_stringbuf(noderev_str, scratch_pool);
SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, scratch_pool,
scratch_pool));
+ SVN_ERR(svn_fs_fs__fixup_expanded_size(query->fs, noderev->data_rep,
+ scratch_pool));
+ SVN_ERR(svn_fs_fs__fixup_expanded_size(query->fs, noderev->prop_rep,
+ scratch_pool));
if (noderev->data_rep)
{
@@ -652,19 +685,25 @@ get_phys_change_count(query_t *query,
revision_info_t *revision_info,
apr_pool_t *scratch_pool)
{
- /* We are going to use our own sub-pool here because the changes object
- * may well be >100MB and SCRATCH_POOL may not get cleared until all other
- * info has been read by read_phys_revision(). Therefore, tidy up early.
- */
- apr_pool_t *subpool = svn_pool_create(scratch_pool);
- apr_array_header_t *changes;
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ svn_fs_fs__changes_context_t *context;
- SVN_ERR(svn_fs_fs__get_changes(&changes, query->fs,
- revision_info->revision, subpool));
- revision_info->change_count = changes->nelts;
+ /* Fetch the first block of data. */
+ SVN_ERR(svn_fs_fs__create_changes_context(&context, query->fs,
+ revision_info->revision,
+ scratch_pool));
- /* Release potentially tons of memory. */
- svn_pool_destroy(subpool);
+ revision_info->change_count = 0;
+ while (!context->eol)
+ {
+ apr_array_header_t *changes;
+
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_fs__get_changes(&changes, context, iterpool, iterpool));
+ revision_info->change_count = changes->nelts;
+ }
+
+ svn_pool_destroy(iterpool);
return SVN_NO_ERROR;
}
@@ -729,12 +768,12 @@ read_phys_pack_file(query_t *query,
{
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
- apr_off_t file_size = 0;
+ svn_filesize_t file_size = 0;
svn_fs_fs__revision_file_t *rev_file;
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, query->fs, base,
scratch_pool, scratch_pool));
- SVN_ERR(get_file_size(&file_size, rev_file, scratch_pool));
+ SVN_ERR(svn_io_file_size_get(&file_size, rev_file->file, scratch_pool));
/* process each revision in the pack file */
for (i = 0; i < query->shard_size; ++i)
@@ -798,7 +837,7 @@ read_phys_revision_file(query_t *query,
apr_pool_t *scratch_pool)
{
revision_info_t *info = apr_pcalloc(result_pool, sizeof(*info));
- apr_off_t file_size = 0;
+ svn_filesize_t file_size = 0;
svn_fs_fs__revision_file_t *rev_file;
/* cancellation support */
@@ -808,7 +847,7 @@ read_phys_revision_file(query_t *query,
/* read the whole pack file into memory */
SVN_ERR(svn_fs_fs__open_pack_or_rev_file(&rev_file, query->fs, revision,
scratch_pool, scratch_pool));
- SVN_ERR(get_file_size(&file_size, rev_file, scratch_pool));
+ SVN_ERR(svn_io_file_size_get(&file_size, rev_file->file, scratch_pool));
/* create the revision info for the current rev */
info->representations = apr_array_make(result_pool, 4, sizeof(rep_stats_t*));
@@ -885,6 +924,70 @@ read_item(svn_stringbuf_t **contents,
return SVN_NO_ERROR;
}
+/* Predicate comparing the two rep_ref_t** LHS and RHS by the respective
+ * representation's revision.
+ */
+static int
+compare_representation_refs(const void *lhs, const void *rhs)
+{
+ svn_revnum_t lhs_rev = (*(const rep_ref_t *const *)lhs)->revision;
+ svn_revnum_t rhs_rev = (*(const rep_ref_t *const *)rhs)->revision;
+
+ if (lhs_rev < rhs_rev)
+ return -1;
+ return (lhs_rev > rhs_rev ? 1 : 0);
+}
+
+/* Given all the presentations found in a single rev / pack file as
+ * rep_ref_t * in REP_REFS, update the delta chain lengths in QUERY.
+ * REP_REFS and its contents can then be discarded.
+ */
+static svn_error_t *
+resolve_representation_refs(query_t *query,
+ apr_array_header_t *rep_refs)
+{
+ int i;
+
+ /* Because delta chains can only point to previous revs, after sorting
+ * REP_REFS, all base refs have already been updated. */
+ svn_sort__array(rep_refs, compare_representation_refs);
+
+ /* Build up the CHAIN_LENGTH values. */
+ for (i = 0; i < rep_refs->nelts; ++i)
+ {
+ int idx;
+ rep_ref_t *ref = APR_ARRAY_IDX(rep_refs, i, rep_ref_t *);
+ rep_stats_t *rep = find_representation(&idx, query, NULL,
+ ref->revision, ref->item_index);
+
+ /* No dangling pointers and all base reps have been processed. */
+ SVN_ERR_ASSERT(rep);
+ SVN_ERR_ASSERT(!rep->chain_length);
+
+ /* Set the HEADER_SIZE as we found it during the scan. */
+ rep->header_size = ref->header_size;
+
+ /* The delta chain got 1 element longer. */
+ if (ref->base_revision == SVN_INVALID_REVNUM)
+ {
+ rep->chain_length = 1;
+ }
+ else
+ {
+ rep_stats_t *base;
+
+ base = find_representation(&idx, query, NULL, ref->base_revision,
+ ref->base_item_index);
+ SVN_ERR_ASSERT(base);
+ SVN_ERR_ASSERT(base->chain_length);
+
+ rep->chain_length = 1 + MIN(base->chain_length, (apr_byte_t)0xfe);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
/* Process the logically addressed revision contents of revisions BASE to
* BASE + COUNT - 1 in QUERY.
*
@@ -905,6 +1008,12 @@ read_log_rev_or_packfile(query_t *query,
int i;
svn_fs_fs__revision_file_t *rev_file;
+ /* We collect the delta chain links as we scan the file. Afterwards,
+ * we determine the lengths of those delta chains and throw this
+ * temporary container away. */
+ apr_array_header_t *rep_refs = apr_array_make(scratch_pool, 64,
+ sizeof(rep_ref_t *));
+
/* we will process every revision in the rev / pack file */
for (i = 0; i < count; ++i)
{
@@ -947,6 +1056,8 @@ read_log_rev_or_packfile(query_t *query,
/* process all entries (and later continue with the next block) */
for (i = 0; i < entries->nelts; ++i)
{
+ svn_stringbuf_t *item;
+ revision_info_t *info;
svn_fs_fs__p2l_entry_t *entry
= &APR_ARRAY_IDX(entries, i, svn_fs_fs__p2l_entry_t);
@@ -959,32 +1070,64 @@ read_log_rev_or_packfile(query_t *query,
continue;
/* read and process interesting items */
+ info = APR_ARRAY_IDX(query->revisions, entry->item.revision,
+ revision_info_t*);
+
if (entry->type == SVN_FS_FS__ITEM_TYPE_NODEREV)
{
- svn_stringbuf_t *item;
- revision_info_t *info = APR_ARRAY_IDX(query->revisions,
- entry->item.revision,
- revision_info_t*);
SVN_ERR(read_item(&item, rev_file, entry, iterpool, iterpool));
SVN_ERR(read_noderev(query, item, info, result_pool, iterpool));
}
else if (entry->type == SVN_FS_FS__ITEM_TYPE_CHANGES)
{
- svn_stringbuf_t *item;
- revision_info_t *info = APR_ARRAY_IDX(query->revisions,
- entry->item.revision,
- revision_info_t*);
SVN_ERR(read_item(&item, rev_file, entry, iterpool, iterpool));
info->change_count
= get_log_change_count(item->data + 0, item->len);
info->changes_len += entry->size;
}
+ else if ( (entry->type == SVN_FS_FS__ITEM_TYPE_FILE_REP)
+ || (entry->type == SVN_FS_FS__ITEM_TYPE_DIR_REP)
+ || (entry->type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
+ || (entry->type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS))
+ {
+ /* Collect the delta chain link. */
+ svn_fs_fs__rep_header_t *header;
+ rep_ref_t *ref = apr_pcalloc(scratch_pool, sizeof(*ref));
+
+ SVN_ERR(svn_io_file_aligned_seek(rev_file->file,
+ rev_file->block_size,
+ NULL, entry->offset,
+ iterpool));
+ SVN_ERR(svn_fs_fs__read_rep_header(&header,
+ rev_file->stream,
+ iterpool, iterpool));
+
+ ref->header_size = header->header_size;
+ ref->revision = entry->item.revision;
+ ref->item_index = entry->item.number;
+
+ if (header->type == svn_fs_fs__rep_delta)
+ {
+ ref->base_item_index = header->base_item_index;
+ ref->base_revision = header->base_revision;
+ }
+ else
+ {
+ ref->base_item_index = SVN_FS_FS__ITEM_INDEX_UNUSED;
+ ref->base_revision = SVN_INVALID_REVNUM;
+ }
+
+ APR_ARRAY_PUSH(rep_refs, rep_ref_t *) = ref;
+ }
/* advance offset */
offset += entry->size;
}
}
+ /* Resolve the delta chain links. */
+ SVN_ERR(resolve_representation_refs(query, rep_refs));
+
/* clean up and close file handles */
svn_pool_destroy(iterpool);
@@ -1111,6 +1254,7 @@ add_rep_stats(svn_fs_fs__representation_stats_t *stats,
stats->references += rep->ref_count;
stats->expanded_size += rep->ref_count * rep->expanded_size;
+ stats->chain_len += rep->chain_length;
}
/* Aggregate the info the in revision_info_t * array REVISIONS into the
diff --git a/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure
index 7b5129f17cee9..f624d616c7b52 100644
--- a/subversion/libsvn_fs_fs/structure
+++ b/subversion/libsvn_fs_fs/structure
@@ -150,12 +150,14 @@ The formats are:
Format 5, understood by Subversion 1.7-dev, never released
Format 6, understood by Subversion 1.8
Format 7, understood by Subversion 1.9
+ Format 8, understood by Subversion 1.10
The differences between the formats are:
Delta representation in revision files
- Format 1: svndiff0 only
- Formats 2+: svndiff0 or svndiff1
+ Format 1: svndiff0 only
+ Formats 2-7: svndiff0 or svndiff1
+ Formats 8: svndiff0, svndiff1 or svndiff2
Format options
Formats 1-2: none permitted
@@ -198,9 +200,9 @@ Shard packing:
(i.e. same min packed revision)
Addressing:
- Format 1-6: Physical addressing; uses fixed positions within a rev file
+ Format 1+: Physical addressing; uses fixed positions within a rev file
Format 7+: Logical addressing; uses item index that will be translated
- on-the-fly to the actual rev / pack file location
+ on-the-fly to the actual rev / pack file location (default for 7+ created)
Repository IDs:
Format 1+: The first line of db/uuid contains the repository UUID
@@ -525,6 +527,7 @@ A revision file contains a concatenation of various kinds of data:
* Text and property representations
* Node-revisions
* The changed-path data
+ * Two offsets at the very end (physical addressing only)
* Index data (logical addressing only)
* Revision / pack file footer (logical addressing only)
@@ -565,6 +568,9 @@ defined:
### in formats >=4, also present:
<sha1-digest> gives hex SHA1 digest of expanded rep
<uniquifier> see representation_t->uniquifier in fs.h
+ ### Starting from format 8, a special notation "-"
+ can be used for optional values that are not present
+ (<sha1-digest> and <uniquifier>).
cpath FS pathname node was created at
copyfrom "<rev> <path>" of copyfrom data
copyroot "<rev> <created-path>" of the root of this copy
@@ -757,6 +763,9 @@ Format 7 introduces logical addressing that requires item indexes
to be translated / mapped to physical rev / pack file offsets.
These indexes are appended to the respective rev / pack file.
+The indexes map (revision number, item-index) pairs to absolute file offsets
+and absolute file offsets to (revision number, item-index, item metadata).
+
Details of the binary format used by these index files can be
found in structure-indexes.
diff --git a/subversion/libsvn_fs_fs/structure-indexes b/subversion/libsvn_fs_fs/structure-indexes
index 25490c7ca8f09..545f2ffcbe8c1 100644
--- a/subversion/libsvn_fs_fs/structure-indexes
+++ b/subversion/libsvn_fs_fs/structure-indexes
@@ -13,10 +13,10 @@ to read and cache any data without traversing DAGs.
Rev and pack files are immutable, so the same is true for index data.
During a transaction or while packing a file, a proto index file gets
-written (actually, one log-to-phys and one phys-to-log). Its format is
-a simple concatenation of runtime structs and as such, an implementation
-detail subject to change. A proto index basically aggregates all the
-information that must later be transformed into the final index.
+written (actually, one log-to-phys and one phys-to-log). They use a
+simpler, less compact format with fixed record lengths. A proto index
+basically aggregates all the information that must later be transformed
+into the final index.
General design concerns
@@ -192,11 +192,11 @@ at the beginning of the file is optional and will be ignored.
<bof> /* begin of proto index file for revision r and following */
(0, 0) /* mark start of revision r, optional for first rev */
- (off, item)* /* zero to many mappings in random order */
+ (off, item)* /* zero or more mappings in random order */
(0, 0) /* mark start of revision r + 1 */
- (off, item)* /* zero to many mappings in random order */
+ (off, item)* /* zero or more mappings in random order */
(0, 0) /* mark start of revision r + 2 */
- (off, item)* /* zero to many mappings in random order */
+ (off, item)* /* zero or more mappings in random order */
...
<eof> /* end of file. */
@@ -343,10 +343,12 @@ For performance reasons we use a modified version:
h0 = fnv_1a([b0 b4 b8 ..]), ..., h3 = fnv_1a([b3 b7 b11 ..])
-* combine the big endian representation of these checksums plus the
- remnant of the original stream into a 12 to 15 byte long intermediate
+* concatenate the big endian representation of these checksums (4 bytes
+ each) plus the remnant of the original stream into a 16 to 19 byte long
+ intermediate:
- [i0 .. iK], 12 <= K+1 <= 15
+ [i0 .. iK] = [big-endian(h0) ... big-endian(h3) remnant ], 16 <= K+1 <= 19
-* FNV checksum = fnv_1a([i0 .. iK]) in big endian representation
+* fold the variable-length intermediate into a compact 32 bit checksum:
+ FNV checksum = fnv_1a([i0 .. iK])
diff --git a/subversion/libsvn_fs_fs/temp_serializer.c b/subversion/libsvn_fs_fs/temp_serializer.c
index 4e7ae2d1abb25..f6e9e3a0971c4 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.c
+++ b/subversion/libsvn_fs_fs/temp_serializer.c
@@ -103,9 +103,7 @@ serialize_svn_string(svn_temp_serializer__context_t *context,
if (string == NULL)
return;
- svn_temp_serializer__push(context,
- (const void * const *)s,
- sizeof(*string));
+ svn_temp_serializer__push(context, (const void * const *)s, sizeof(**s));
/* the "string" content may actually be arbitrary binary data.
* Thus, we cannot use svn_temp_serializer__add_string. */
@@ -143,7 +141,7 @@ serialize_representation(svn_temp_serializer__context_t *context,
/* serialize the representation struct itself */
svn_temp_serializer__add_leaf(context,
(const void * const *)representation,
- sizeof(*rep));
+ sizeof(**representation));
}
/* auxiliary structure representing the content of a directory array */
@@ -153,6 +151,10 @@ typedef struct dir_data_t
* (it's int because the directory is an APR array) */
int count;
+ /** Current length of the in-txn in-disk representation of the directory.
+ * SVN_INVALID_FILESIZE if unknown (i.e. committed data). */
+ svn_filesize_t txn_filesize;
+
/* number of unused dir entry buckets in the index */
apr_size_t over_provision;
@@ -187,7 +189,7 @@ serialize_dir_entry(svn_temp_serializer__context_t *context,
svn_temp_serializer__push(context,
(const void * const *)entry_p,
- sizeof(svn_fs_dirent_t));
+ sizeof(**entry_p));
svn_fs_fs__id_serialize(context, &entry->id);
svn_temp_serializer__add_string(context, &entry->name);
@@ -198,24 +200,27 @@ serialize_dir_entry(svn_temp_serializer__context_t *context,
svn_temp_serializer__pop(context);
}
-/* Utility function to serialize the ENTRIES into a new serialization
+/* Utility function to serialize the DIR into a new serialization
* context to be returned. Allocation will be made form POOL.
*/
static svn_temp_serializer__context_t *
-serialize_dir(apr_array_header_t *entries, apr_pool_t *pool)
+serialize_dir(svn_fs_fs__dir_data_t *dir, apr_pool_t *pool)
{
dir_data_t dir_data;
int i = 0;
svn_temp_serializer__context_t *context;
+ apr_array_header_t *entries = dir->entries;
/* calculate sizes */
int count = entries->nelts;
apr_size_t over_provision = 2 + count / 4;
- apr_size_t entries_len = (count + over_provision) * sizeof(svn_fs_dirent_t*);
- apr_size_t lengths_len = (count + over_provision) * sizeof(apr_uint32_t);
+ apr_size_t total_count = count + over_provision;
+ apr_size_t entries_len = total_count * sizeof(*dir_data.entries);
+ apr_size_t lengths_len = total_count * sizeof(*dir_data.lengths);
/* copy the hash entries to an auxiliary struct of known layout */
dir_data.count = count;
+ dir_data.txn_filesize = dir->txn_filesize;
dir_data.over_provision = over_provision;
dir_data.operations = 0;
dir_data.entries = apr_palloc(pool, entries_len);
@@ -252,24 +257,29 @@ serialize_dir(apr_array_header_t *entries, apr_pool_t *pool)
return context;
}
-/* Utility function to reconstruct a dir entries array from serialized data
+/* Utility function to reconstruct a dir entries struct from serialized data
* in BUFFER and DIR_DATA. Allocation will be made form POOL.
*/
-static apr_array_header_t *
+static svn_fs_fs__dir_data_t *
deserialize_dir(void *buffer, dir_data_t *dir_data, apr_pool_t *pool)
{
- apr_array_header_t *result
- = apr_array_make(pool, dir_data->count, sizeof(svn_fs_dirent_t *));
+ svn_fs_fs__dir_data_t *result;
apr_size_t i;
apr_size_t count;
svn_fs_dirent_t *entry;
svn_fs_dirent_t **entries;
+ /* Construct empty directory object. */
+ result = apr_pcalloc(pool, sizeof(*result));
+ result->entries
+ = apr_array_make(pool, dir_data->count, sizeof(svn_fs_dirent_t *));
+ result->txn_filesize = dir_data->txn_filesize;
+
/* resolve the reference to the entries array */
svn_temp_deserializer__resolve(buffer, (void **)&dir_data->entries);
entries = dir_data->entries;
- /* fixup the references within each entry and add it to the hash */
+ /* fixup the references within each entry and add it to the RESULT */
for (i = 0, count = dir_data->count; i < count; ++i)
{
svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
@@ -280,7 +290,7 @@ deserialize_dir(void *buffer, dir_data_t *dir_data, apr_pool_t *pool)
svn_fs_fs__id_deserialize(entry, (svn_fs_id_t **)&entry->id);
/* add the entry to the hash */
- APR_ARRAY_PUSH(result, svn_fs_dirent_t *) = entry;
+ APR_ARRAY_PUSH(result->entries, svn_fs_dirent_t *) = entry;
}
/* return the now complete hash */
@@ -405,7 +415,7 @@ serialize_txdelta_ops(svn_temp_serializer__context_t *context,
/* the ops form a contiguous chunk of memory with no further references */
svn_temp_serializer__add_leaf(context,
(const void * const *)ops,
- count * sizeof(svn_txdelta_op_t));
+ count * sizeof(**ops));
}
/* Utility function to serialize W in the given serialization CONTEXT.
@@ -417,9 +427,7 @@ serialize_txdeltawindow(svn_temp_serializer__context_t *context,
svn_txdelta_window_t *window = *w;
/* serialize the window struct itself */
- svn_temp_serializer__push(context,
- (const void * const *)w,
- sizeof(svn_txdelta_window_t));
+ svn_temp_serializer__push(context, (const void * const *)w, sizeof(**w));
/* serialize its sub-structures */
serialize_txdelta_ops(context, &window->ops, window->num_ops);
@@ -496,8 +504,7 @@ svn_fs_fs__serialize_manifest(void **data,
apr_array_header_t *manifest = in;
*data_len = sizeof(apr_off_t) *manifest->nelts;
- *data = apr_palloc(pool, *data_len);
- memcpy(*data, manifest->elts, *data_len);
+ *data = apr_pmemdup(pool, manifest->elts, *data_len);
return SVN_NO_ERROR;
}
@@ -592,7 +599,7 @@ svn_fs_fs__serialize_properties(void **data,
/* create our auxiliary data structure */
properties.count = apr_hash_count(hash);
properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
- properties.values = apr_palloc(pool, sizeof(const char*) * properties.count);
+ properties.values = apr_palloc(pool, sizeof(const svn_string_t *) * properties.count);
/* populate it with the hash entries */
for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
@@ -656,6 +663,44 @@ svn_fs_fs__deserialize_properties(void **out,
}
svn_error_t *
+svn_fs_fs__serialize_revprops(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ svn_string_t *buffer = in;
+
+ *data = (void *)buffer->data;
+ *data_len = buffer->len;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_revprops(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool)
+{
+ apr_hash_t *properties;
+ svn_stream_t *stream;
+
+ svn_string_t buffer;
+ buffer.data = data;
+ buffer.len = data_len;
+
+ stream = svn_stream_from_string(&buffer, pool);
+ properties = svn_hash__make(pool);
+
+ SVN_ERR(svn_hash_read2(properties, stream, SVN_HASH_TERMINATOR, pool));
+
+ /* done */
+ *out = properties;
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
svn_fs_fs__serialize_id(void **data,
apr_size_t *data_len,
void *in,
@@ -743,16 +788,18 @@ svn_fs_fs__deserialize_node_revision(void **item,
}
/* Utility function that returns the directory serialized inside CONTEXT
- * to DATA and DATA_LEN. */
+ * to DATA and DATA_LEN. If OVERPROVISION is set, allocate some extra
+ * room for future in-place changes by svn_fs_fs__replace_dir_entry. */
static svn_error_t *
return_serialized_dir_context(svn_temp_serializer__context_t *context,
void **data,
- apr_size_t *data_len)
+ apr_size_t *data_len,
+ svn_boolean_t overprovision)
{
svn_stringbuf_t *serialized = svn_temp_serializer__get(context);
*data = serialized->data;
- *data_len = serialized->blocksize;
+ *data_len = overprovision ? serialized->blocksize : serialized->len;
((dir_data_t *)serialized->data)->len = serialized->len;
return SVN_NO_ERROR;
@@ -764,13 +811,30 @@ svn_fs_fs__serialize_dir_entries(void **data,
void *in,
apr_pool_t *pool)
{
- apr_array_header_t *dir = in;
+ svn_fs_fs__dir_data_t *dir = in;
+
+ /* serialize the dir content into a new serialization context
+ * and return the serialized data */
+ return return_serialized_dir_context(serialize_dir(dir, pool),
+ data,
+ data_len,
+ FALSE);
+}
+
+svn_error_t *
+svn_fs_fs__serialize_txndir_entries(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool)
+{
+ svn_fs_fs__dir_data_t *dir = in;
/* serialize the dir content into a new serialization context
* and return the serialized data */
return return_serialized_dir_context(serialize_dir(dir, pool),
data,
- data_len);
+ data_len,
+ TRUE);
}
svn_error_t *
@@ -803,6 +867,20 @@ svn_fs_fs__get_sharded_offset(void **out,
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_fs_fs__extract_dir_filesize(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ const dir_data_t *dir_data = data;
+
+ *(svn_filesize_t *)out = dir_data->txn_filesize;
+
+ return SVN_NO_ERROR;
+}
+
/* Utility function that returns the lowest index of the first entry in
* *ENTRIES that points to a dir entry with a name equal or larger than NAME.
* If an exact match has been found, *FOUND will be set to TRUE. COUNT is
@@ -857,7 +935,7 @@ svn_fs_fs__extract_dir_entry(void **out,
apr_pool_t *pool)
{
const dir_data_t *dir_data = data;
- const char* name = baton;
+ extract_dir_entry_baton_t *entry_baton = baton;
svn_boolean_t found;
/* resolve the reference to the entries array */
@@ -870,13 +948,17 @@ svn_fs_fs__extract_dir_entry(void **out,
/* binary search for the desired entry by name */
apr_size_t pos = find_entry((svn_fs_dirent_t **)entries,
- name,
+ entry_baton->name,
dir_data->count,
&found);
- /* de-serialize that entry or return NULL, if no match has been found */
+ /* de-serialize that entry or return NULL, if no match has been found.
+ * Be sure to check that the directory contents is still up-to-date. */
+ entry_baton->out_of_date
+ = dir_data->txn_filesize != entry_baton->txn_filesize;
+
*out = NULL;
- if (found)
+ if (found && !entry_baton->out_of_date)
{
const svn_fs_dirent_t *source =
svn_temp_deserializer__ptr(entries, (const void *const *)&entries[pos]);
@@ -889,8 +971,7 @@ svn_fs_fs__extract_dir_entry(void **out,
apr_size_t size = lengths[pos];
/* copy & deserialize the entry */
- svn_fs_dirent_t *new_entry = apr_palloc(pool, size);
- memcpy(new_entry, source, size);
+ svn_fs_dirent_t *new_entry = apr_pmemdup(pool, source, size);
svn_temp_deserializer__resolve(new_entry, (void **)&new_entry->name);
svn_fs_fs__id_deserialize(new_entry, (svn_fs_id_t **)&new_entry->id);
@@ -911,31 +992,34 @@ slowly_replace_dir_entry(void **data,
{
replace_baton_t *replace_baton = (replace_baton_t *)baton;
dir_data_t *dir_data = (dir_data_t *)*data;
- apr_array_header_t *dir;
+ svn_fs_fs__dir_data_t *dir;
int idx = -1;
svn_fs_dirent_t *entry;
+ apr_array_header_t *entries;
SVN_ERR(svn_fs_fs__deserialize_dir_entries((void **)&dir,
*data,
dir_data->len,
pool));
- entry = svn_fs_fs__find_dir_entry(dir, replace_baton->name, &idx);
+ entries = dir->entries;
+ entry = svn_fs_fs__find_dir_entry(entries, replace_baton->name, &idx);
/* Replacement or removal? */
if (replace_baton->new_entry)
{
/* Replace ENTRY with / insert the NEW_ENTRY */
if (entry)
- APR_ARRAY_IDX(dir, idx, svn_fs_dirent_t *) = replace_baton->new_entry;
+ APR_ARRAY_IDX(entries, idx, svn_fs_dirent_t *)
+ = replace_baton->new_entry;
else
- svn_sort__array_insert(dir, &replace_baton->new_entry, idx);
+ svn_sort__array_insert(entries, &replace_baton->new_entry, idx);
}
else
{
/* Remove the old ENTRY. */
if (entry)
- svn_sort__array_delete(dir, idx, 1);
+ svn_sort__array_delete(entries, idx, 1);
}
return svn_fs_fs__serialize_dir_entries(data, data_len, dir, pool);
@@ -957,6 +1041,12 @@ svn_fs_fs__replace_dir_entry(void **data,
svn_temp_serializer__context_t *context;
+ /* update the cached file length info.
+ * Because we are writing to the cache, it is fair to assume that the
+ * caller made sure that the current contents is consistent with the
+ * previous state of the directory file. */
+ dir_data->txn_filesize = replace_baton->txn_filesize;
+
/* after quite a number of operations, let's re-pack everything.
* This is to limit the number of wasted space as we cannot overwrite
* existing data but must always append. */
@@ -1029,9 +1119,7 @@ svn_fs_fs__replace_dir_entry(void **data,
serialize_dir_entry(context, &entries[pos], &length);
/* return the updated serialized data */
- SVN_ERR (return_serialized_dir_context(context,
- data,
- data_len));
+ SVN_ERR(return_serialized_dir_context(context, data, data_len, TRUE));
/* since the previous call may have re-allocated the buffer, the lengths
* pointer may no longer point to the entry in that buffer. Therefore,
@@ -1046,6 +1134,18 @@ svn_fs_fs__replace_dir_entry(void **data,
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_fs_fs__reset_txn_filesize(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *pool)
+{
+ dir_data_t *dir_data = (dir_data_t *)*data;
+ dir_data->txn_filesize = SVN_INVALID_FILESIZE;
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_fs_fs__serialize_rep_header(void **data,
apr_size_t *data_len,
@@ -1055,7 +1155,7 @@ svn_fs_fs__serialize_rep_header(void **data,
svn_fs_fs__rep_header_t *copy = apr_palloc(pool, sizeof(*copy));
*copy = *(svn_fs_fs__rep_header_t *)in;
- *data_len = sizeof(svn_fs_fs__rep_header_t);
+ *data_len = sizeof(*copy);
*data = copy;
return SVN_NO_ERROR;
@@ -1124,47 +1224,29 @@ deserialize_change(void *buffer, change_t **change_p)
svn_temp_deserializer__resolve(change, (void **)&change->info.copyfrom_path);
}
-/* Auxiliary structure representing the content of a change_t array.
- This structure is much easier to (de-)serialize than an APR array.
- */
-typedef struct changes_data_t
-{
- /* number of entries in the array */
- int count;
-
- /* reference to the changes */
- change_t **changes;
-} changes_data_t;
-
svn_error_t *
svn_fs_fs__serialize_changes(void **data,
apr_size_t *data_len,
void *in,
apr_pool_t *pool)
{
- apr_array_header_t *array = in;
- changes_data_t changes;
+ svn_fs_fs__changes_list_t *changes = in;
svn_temp_serializer__context_t *context;
svn_stringbuf_t *serialized;
int i;
- /* initialize our auxiliary data structure and link it to the
- * array elements */
- changes.count = array->nelts;
- changes.changes = (change_t **)array->elts;
-
/* serialize it and all its elements */
- context = svn_temp_serializer__init(&changes,
- sizeof(changes),
- changes.count * 250,
+ context = svn_temp_serializer__init(changes,
+ sizeof(*changes),
+ changes->count * 250,
pool);
svn_temp_serializer__push(context,
- (const void * const *)&changes.changes,
- changes.count * sizeof(change_t*));
+ (const void * const *)&changes->changes,
+ changes->count * sizeof(*changes->changes));
- for (i = 0; i < changes.count; ++i)
- serialize_change(context, &changes.changes[i]);
+ for (i = 0; i < changes->count; ++i)
+ serialize_change(context, &changes->changes[i]);
svn_temp_serializer__pop(context);
@@ -1184,8 +1266,7 @@ svn_fs_fs__deserialize_changes(void **out,
apr_pool_t *pool)
{
int i;
- changes_data_t *changes = (changes_data_t *)data;
- apr_array_header_t *array = apr_array_make(pool, 0, sizeof(change_t *));
+ svn_fs_fs__changes_list_t *changes = (svn_fs_fs__changes_list_t *)data;
/* de-serialize our auxiliary data structure */
svn_temp_deserializer__resolve(changes, (void**)&changes->changes);
@@ -1195,14 +1276,8 @@ svn_fs_fs__deserialize_changes(void **out,
deserialize_change(changes->changes,
(change_t **)&changes->changes[i]);
- /* Use the changes buffer as the array's data buffer
- * (DATA remains valid for at least as long as POOL). */
- array->elts = (char *)changes->changes;
- array->nelts = changes->count;
- array->nalloc = changes->count;
-
/* done */
- *out = array;
+ *out = changes;
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/temp_serializer.h b/subversion/libsvn_fs_fs/temp_serializer.h
index 4d14b01fe63d2..187c8d000206d 100644
--- a/subversion/libsvn_fs_fs/temp_serializer.h
+++ b/subversion/libsvn_fs_fs/temp_serializer.h
@@ -51,7 +51,7 @@ svn_fs_fs__noderev_deserialize(void *buffer,
/**
- * Adds position information to the the raw window data in WINDOW.
+ * Adds position information to the raw window data in WINDOW.
*/
typedef struct
{
@@ -60,6 +60,9 @@ typedef struct
/* the offset within the representation right after reading the window */
apr_off_t end_offset;
+
+ /* svndiff version */
+ int ver;
} svn_fs_fs__raw_cached_window_t;
/**
@@ -156,6 +159,26 @@ svn_fs_fs__deserialize_properties(void **out,
apr_pool_t *pool);
/**
+ * Implements #svn_cache__serialize_func_t for a properties hash
+ * (@a in is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__serialize_revprops(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a properties hash
+ * (@a *out is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__deserialize_revprops(void **out,
+ void *data,
+ apr_size_t data_len,
+ apr_pool_t *pool);
+
+/**
* Implements #svn_cache__serialize_func_t for #svn_fs_id_t
*/
svn_error_t *
@@ -192,7 +215,7 @@ svn_fs_fs__deserialize_node_revision(void **item,
apr_pool_t *pool);
/**
- * Implements #svn_cache__serialize_func_t for a directory contents array
+ * Implements #svn_cache__serialize_func_t for a #svn_fs_fs__dir_data_t
*/
svn_error_t *
svn_fs_fs__serialize_dir_entries(void **data,
@@ -201,7 +224,17 @@ svn_fs_fs__serialize_dir_entries(void **data,
apr_pool_t *pool);
/**
- * Implements #svn_cache__deserialize_func_t for a directory contents array
+ * Same as svn_fs_fs__serialize_dir_entries but allocates extra room for
+ * in-place modification.
+ */
+svn_error_t *
+svn_fs_fs__serialize_txndir_entries(void **data,
+ apr_size_t *data_len,
+ void *in,
+ apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a #svn_fs_fs__dir_data_t
*/
svn_error_t *
svn_fs_fs__deserialize_dir_entries(void **out,
@@ -221,9 +254,44 @@ svn_fs_fs__get_sharded_offset(void **out,
apr_pool_t *pool);
/**
+ * Implements #svn_cache__partial_getter_func_t.
+ * Set (svn_filesize_t) @a *out to the filesize info stored with the
+ * serialized directory in @a data of @a data_len. @a baton is unused.
+ */
+svn_error_t *
+svn_fs_fs__extract_dir_filesize(void **out,
+ const void *data,
+ apr_size_t data_len,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
+ * Describes the entry to be found in a directory: Identifies the entry
+ * by @a name and requires the directory file size to be @a filesize.
+ */
+typedef struct extract_dir_entry_baton_t
+{
+ /** name of the directory entry to return */
+ const char *name;
+
+ /** Current length of the in-txn in-disk representation of the directory.
+ * SVN_INVALID_FILESIZE if unknown. */
+ svn_filesize_t txn_filesize;
+
+ /** Will be set by the callback. If FALSE, the cached data is out of date.
+ * We need this indicator because the svn_cache__t interface will always
+ * report the lookup as a success (FOUND==TRUE) if the generic lookup was
+ * successful -- regardless of what the entry extraction callback does. */
+ svn_boolean_t out_of_date;
+} extract_dir_entry_baton_t;
+
+
+/**
* Implements #svn_cache__partial_getter_func_t for a single
* #svn_fs_dirent_t within a serialized directory contents hash,
- * identified by its name (const char @a *baton).
+ * identified by its name (in (extract_dir_entry_baton_t *) @a *baton).
+ * If the filesize specified in the baton does not match the cached
+ * value for this directory, @a *out will be NULL as well.
*/
svn_error_t *
svn_fs_fs__extract_dir_entry(void **out,
@@ -236,7 +304,10 @@ svn_fs_fs__extract_dir_entry(void **out,
* Describes the change to be done to a directory: Set the entry
* identify by @a name to the value @a new_entry. If the latter is
* @c NULL, the entry shall be removed if it exists. Otherwise it
- * will be replaced or automatically added, respectively.
+ * will be replaced or automatically added, respectively. The
+ * @a filesize allows readers to identify stale cache data (e.g.
+ * due to concurrent access to txns); writers use it to update the
+ * cached file size info.
*/
typedef struct replace_baton_t
{
@@ -245,6 +316,10 @@ typedef struct replace_baton_t
/** directory entry to insert instead */
svn_fs_dirent_t *new_entry;
+
+ /** Current length of the in-txn in-disk representation of the directory.
+ * SVN_INVALID_FILESIZE if unknown. */
+ svn_filesize_t txn_filesize;
} replace_baton_t;
/**
@@ -259,6 +334,17 @@ svn_fs_fs__replace_dir_entry(void **data,
apr_pool_t *pool);
/**
+ * Implements #svn_cache__partial_setter_func_t for a #svn_fs_fs__dir_data_t
+ * at @a *data, resetting its txn_filesize field to SVN_INVALID_FILESIZE.
+ * &a baton should be NULL.
+ */
+svn_error_t *
+svn_fs_fs__reset_txn_filesize(void **data,
+ apr_size_t *data_len,
+ void *baton,
+ apr_pool_t *pool);
+
+/**
* Implements #svn_cache__serialize_func_t for a #svn_fs_fs__rep_header_t.
*/
svn_error_t *
@@ -276,9 +362,34 @@ svn_fs_fs__deserialize_rep_header(void **out,
apr_size_t data_len,
apr_pool_t *pool);
+/*** Block of changes in a changed paths list. */
+typedef struct svn_fs_fs__changes_list_t
+{
+ /* Offset of the first element in CHANGES within the changed paths list
+ on disk. */
+ apr_off_t start_offset;
+
+ /* Offset of the first element behind CHANGES within the changed paths
+ list on disk. */
+ apr_off_t end_offset;
+
+ /* End of list reached? This may have false negatives in case the number
+ of elements in the list is a multiple of our block / range size. */
+ svn_boolean_t eol;
+
+ /* Array of #svn_fs_x__change_t * representing a consecutive sub-range of
+ elements in a changed paths list. */
+
+ /* number of entries in the array */
+ int count;
+
+ /* reference to the changes */
+ change_t **changes;
+
+} svn_fs_fs__changes_list_t;
+
/**
- * Implements #svn_cache__serialize_func_t for an #apr_array_header_t of
- * #change_t *.
+ * Implements #svn_cache__serialize_func_t for a #svn_fs_fs__changes_list_t.
*/
svn_error_t *
svn_fs_fs__serialize_changes(void **data,
@@ -287,8 +398,7 @@ svn_fs_fs__serialize_changes(void **data,
apr_pool_t *pool);
/**
- * Implements #svn_cache__deserialize_func_t for an #apr_array_header_t of
- * #change_t *.
+ * Implements #svn_cache__deserialize_func_t for a #svn_fs_fs__changes_list_t.
*/
svn_error_t *
svn_fs_fs__deserialize_changes(void **out,
diff --git a/subversion/libsvn_fs_fs/transaction.c b/subversion/libsvn_fs_fs/transaction.c
index bc93a5c27b824..eb6fefaeae229 100644
--- a/subversion/libsvn_fs_fs/transaction.c
+++ b/subversion/libsvn_fs_fs/transaction.c
@@ -25,6 +25,7 @@
#include <assert.h>
#include <apr_sha1.h>
+#include "svn_error_codes.h"
#include "svn_hash.h"
#include "svn_props.h"
#include "svn_sorts.h"
@@ -88,15 +89,6 @@ path_txn_props(svn_fs_t *fs,
}
static APR_INLINE const char *
-path_txn_props_final(svn_fs_t *fs,
- const svn_fs_fs__id_part_t *txn_id,
- apr_pool_t *pool)
-{
- return svn_dirent_join(svn_fs_fs__path_txn_dir(fs, txn_id, pool),
- PATH_TXN_PROPS_FINAL, pool);
-}
-
-static APR_INLINE const char *
path_txn_next_ids(svn_fs_t *fs,
const svn_fs_fs__id_part_t *txn_id,
apr_pool_t *pool)
@@ -584,16 +576,62 @@ unparse_dir_entry(svn_fs_dirent_t *dirent,
svn_stream_t *stream,
apr_pool_t *pool)
{
- const char *val
- = apr_psprintf(pool, "%s %s",
- (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
- : SVN_FS_FS__KIND_DIR,
- svn_fs_fs__id_unparse(dirent->id, pool)->data);
+ apr_size_t to_write;
+ svn_string_t *id_str = svn_fs_fs__id_unparse(dirent->id, pool);
+ apr_size_t name_len = strlen(dirent->name);
+
+ /* Note that sizeof == len + 1, i.e. accounts for the space between
+ * type and ID. */
+ apr_size_t type_len = (dirent->kind == svn_node_file)
+ ? sizeof(SVN_FS_FS__KIND_FILE)
+ : sizeof(SVN_FS_FS__KIND_DIR);
+ apr_size_t value_len = type_len + id_str->len;
+
+ /* A buffer with sufficient space for
+ * - both string lines
+ * - 4 newlines
+ * - 2 lines K/V lines containing a number each
+ */
+ char *buffer = apr_palloc(pool, name_len + value_len
+ + 4
+ + 2 * (2 + SVN_INT64_BUFFER_SIZE));
+
+ /* Now construct the value. */
+ char *p = buffer;
+
+ /* The "K length(name)\n" line. */
+ p[0] = 'K';
+ p[1] = ' ';
+ p += 2;
+ p += svn__i64toa(p, name_len);
+ *(p++) = '\n';
+
+ /* The line with the key, i.e. dir entry name. */
+ memcpy(p, dirent->name, name_len);
+ p += name_len;
+ *(p++) = '\n';
+
+ /* The "V length(type+id)\n" line. */
+ p[0] = 'V';
+ p[1] = ' ';
+ p += 2;
+ p += svn__i64toa(p, value_len);
+ *(p++) = '\n';
+
+ /* The line with the type and ID. */
+ memcpy(p,
+ (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
+ : SVN_FS_FS__KIND_DIR,
+ type_len - 1);
+ p += type_len - 1;
+ *(p++) = ' ';
+ memcpy(p, id_str->data, id_str->len);
+ p+=id_str->len;
+ *(p++) = '\n';
- SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
- "V %" APR_SIZE_T_FMT "\n%s\n",
- strlen(dirent->name), dirent->name,
- strlen(val), val));
+ /* Add the entry to the output stream. */
+ to_write = p - buffer;
+ SVN_ERR(svn_stream_write(stream, buffer, &to_write));
return SVN_NO_ERROR;
}
@@ -894,20 +932,36 @@ svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
svn_revnum_t rev,
apr_pool_t *pool)
{
- apr_hash_t *changed_paths;
- apr_array_header_t *changes;
- int i;
+ apr_hash_t *changed_paths = svn_hash__make(pool);
+ svn_fs_fs__changes_context_t *context;
- SVN_ERR(svn_fs_fs__get_changes(&changes, fs, rev, pool));
+ apr_pool_t *iterpool = svn_pool_create(pool);
- changed_paths = svn_hash__make(pool);
- for (i = 0; i < changes->nelts; ++i)
+ /* Fetch all data block-by-block. */
+ SVN_ERR(svn_fs_fs__create_changes_context(&context, fs, rev, pool));
+ while (!context->eol)
{
- change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
- apr_hash_set(changed_paths, change->path.data, change->path.len,
- &change->info);
+ apr_array_header_t *changes;
+ int i;
+
+ svn_pool_clear(iterpool);
+
+ /* Be sure to allocate the changes in the result POOL, even though
+ we don't need the array itself afterwards. Copying the entries
+ from a temp pool to the result POOL would be expensive and saves
+ use less then 10% memory. */
+ SVN_ERR(svn_fs_fs__get_changes(&changes, context, pool, iterpool));
+
+ for (i = 0; i < changes->nelts; ++i)
+ {
+ change_t *change = APR_ARRAY_IDX(changes, i, change_t *);
+ apr_hash_set(changed_paths, change->path.data, change->path.len,
+ &change->info);
+ }
}
+ svn_pool_destroy(iterpool);
+
*changed_paths_p = changed_paths;
return SVN_NO_ERROR;
@@ -959,6 +1013,7 @@ static svn_error_t *
get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
{
struct get_and_increment_txn_key_baton *cb = baton;
+ fs_fs_data_t *ffd = cb->fs->fsap_data;
const char *txn_current_filename
= svn_fs_fs__path_txn_current(cb->fs, pool);
char new_id_str[SVN_INT64_BUFFER_SIZE + 1]; /* add space for a newline */
@@ -976,10 +1031,10 @@ get_and_increment_txn_key_body(void *baton, apr_pool_t *pool)
/* Increment the key and add a trailing \n to the string so the
txn-current file has a newline in it. */
- SVN_ERR(svn_io_write_atomic(txn_current_filename, new_id_str,
- line_length + 1,
- txn_current_filename /* copy_perms path */,
- pool));
+ SVN_ERR(svn_io_write_atomic2(txn_current_filename, new_id_str,
+ line_length + 1,
+ txn_current_filename /* copy_perms path */,
+ ffd->flush_to_disk, pool));
return SVN_NO_ERROR;
}
@@ -1144,7 +1199,7 @@ get_txn_proplist(apr_hash_t *proplist,
err = svn_hash_read2(proplist, stream, SVN_HASH_TERMINATOR, pool);
if (err)
{
- svn_error_clear(svn_stream_close(stream));
+ err = svn_error_compose_create(err, svn_stream_close(stream));
return svn_error_quick_wrapf(err,
_("malformed property list in transaction '%s'"),
path_txn_props(fs, txn_id, pool));
@@ -1159,24 +1214,24 @@ static svn_error_t *
set_txn_proplist(svn_fs_t *fs,
const svn_fs_fs__id_part_t *txn_id,
apr_hash_t *props,
- svn_boolean_t final,
apr_pool_t *pool)
{
- svn_stringbuf_t *buf;
- svn_stream_t *stream;
+ svn_stream_t *tmp_stream;
+ const char *tmp_path;
+ const char *final_path = path_txn_props(fs, txn_id, pool);
- /* Write out the new file contents to BUF. */
- buf = svn_stringbuf_create_ensure(1024, pool);
- stream = svn_stream_from_stringbuf(buf, pool);
- SVN_ERR(svn_hash_write2(props, stream, SVN_HASH_TERMINATOR, pool));
- SVN_ERR(svn_stream_close(stream));
+ /* Write the new contents into a temporary file. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_path,
+ svn_dirent_dirname(final_path, pool),
+ svn_io_file_del_none,
+ pool, pool));
+
+ /* Replace the old file with the new one. */
+ SVN_ERR(svn_hash_write2(props, tmp_stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(tmp_stream));
+
+ SVN_ERR(svn_io_file_rename2(tmp_path, final_path, FALSE, pool));
- /* Open the transaction properties file and write new contents to it. */
- SVN_ERR(svn_io_write_atomic((final
- ? path_txn_props_final(fs, txn_id, pool)
- : path_txn_props(fs, txn_id, pool)),
- buf->data, buf->len,
- NULL /* copy_perms_path */, pool));
return SVN_NO_ERROR;
}
@@ -1231,7 +1286,7 @@ svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
/* Create a new version of the file and write out the new props. */
/* Open the transaction properties file. */
- SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, FALSE, pool));
+ SVN_ERR(set_txn_proplist(txn->fs, &ftd->txn_id, txn_prop, pool));
return SVN_NO_ERROR;
}
@@ -1490,6 +1545,7 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
= svn_fs_fs__path_txn_node_children(fs, parent_noderev->id, pool);
apr_file_t *file;
svn_stream_t *out;
+ svn_filesize_t filesize;
fs_fs_data_t *ffd = fs->fsap_data;
apr_pool_t *subpool = svn_pool_create(pool);
@@ -1507,8 +1563,6 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
out = svn_stream_from_aprfile2(file, TRUE, pool);
SVN_ERR(unparse_dir_entries(entries, out, subpool));
- svn_pool_clear(subpool);
-
/* Mark the node-rev's data rep as mutable. */
rep = apr_pcalloc(pool, sizeof(*rep));
rep->revision = SVN_INVALID_REVNUM;
@@ -1517,25 +1571,105 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
parent_noderev->data_rep = rep;
SVN_ERR(svn_fs_fs__put_node_revision(fs, parent_noderev->id,
parent_noderev, FALSE, pool));
+
+ /* Immediately populate the txn dir cache to avoid re-reading
+ * the file we just wrote. */
+ if (ffd->txn_dir_cache)
+ {
+ const char *key
+ = svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
+ svn_fs_fs__dir_data_t dir_data;
+
+ /* Flush APR buffers. */
+ SVN_ERR(svn_io_file_flush(file, subpool));
+
+ /* Obtain final file size to update txn_dir_cache. */
+ SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+ /* Store in the cache. */
+ dir_data.entries = entries;
+ dir_data.txn_filesize = filesize;
+ SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, &dir_data,
+ subpool));
+ }
+
+ svn_pool_clear(subpool);
}
else
{
/* The directory rep is already mutable, so just open it for append. */
SVN_ERR(svn_io_file_open(&file, filename, APR_WRITE | APR_APPEND,
- APR_OS_DEFAULT, pool));
- out = svn_stream_from_aprfile2(file, TRUE, pool);
+ APR_OS_DEFAULT, subpool));
+ out = svn_stream_from_aprfile2(file, TRUE, subpool);
+
+ /* If the cache contents is stale, drop it.
+ *
+ * Note that the directory file is append-only, i.e. if the size
+ * did not change, the contents didn't either. */
+ if (ffd->txn_dir_cache)
+ {
+ const char *key
+ = svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
+ svn_boolean_t found;
+ svn_filesize_t cached_filesize;
+
+ /* Get the file size that corresponds to the cached contents
+ * (if any). */
+ SVN_ERR(svn_cache__get_partial((void **)&cached_filesize, &found,
+ ffd->txn_dir_cache, key,
+ svn_fs_fs__extract_dir_filesize,
+ NULL, subpool));
+
+ /* File size info still matches?
+ * If not, we need to drop the cache entry. */
+ if (found)
+ {
+ SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+ if (cached_filesize != filesize)
+ SVN_ERR(svn_cache__set(ffd->txn_dir_cache, key, NULL,
+ subpool));
+ }
+ }
}
+ /* Append an incremental hash entry for the entry change. */
+ if (id)
+ {
+ svn_fs_dirent_t entry;
+ entry.name = name;
+ entry.id = id;
+ entry.kind = kind;
+
+ SVN_ERR(unparse_dir_entry(&entry, out, subpool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
+ strlen(name), name));
+ }
+
+ /* Flush APR buffers. */
+ SVN_ERR(svn_io_file_flush(file, subpool));
+
+ /* Obtain final file size to update txn_dir_cache. */
+ SVN_ERR(svn_io_file_size_get(&filesize, file, subpool));
+
+ /* Close file. */
+ SVN_ERR(svn_io_file_close(file, subpool));
+ svn_pool_clear(subpool);
+
/* if we have a directory cache for this transaction, update it */
if (ffd->txn_dir_cache)
{
- /* build parameters: (name, new entry) pair */
+ /* build parameters: name, new entry, new file size */
const char *key =
svn_fs_fs__id_unparse(parent_noderev->id, subpool)->data;
replace_baton_t baton;
baton.name = name;
baton.new_entry = NULL;
+ baton.txn_filesize = filesize;
if (id)
{
@@ -1550,25 +1684,7 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
svn_fs_fs__replace_dir_entry, &baton,
subpool));
}
- svn_pool_clear(subpool);
- /* Append an incremental hash entry for the entry change. */
- if (id)
- {
- svn_fs_dirent_t entry;
- entry.name = name;
- entry.id = id;
- entry.kind = kind;
-
- SVN_ERR(unparse_dir_entry(&entry, out, subpool));
- }
- else
- {
- SVN_ERR(svn_stream_printf(out, subpool, "D %" APR_SIZE_T_FMT "\n%s\n",
- strlen(name), name));
- }
-
- SVN_ERR(svn_io_file_close(file, subpool));
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}
@@ -1615,9 +1731,11 @@ svn_fs_fs__add_change(svn_fs_t *fs,
return svn_io_file_close(file, pool);
}
-/* If the transaction TXN_ID in FS uses logical addressing, store the
- * (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto index file.
+/* Store the (ITEM_INDEX, OFFSET) pair in the txn's log-to-phys proto
+ * index file.
* Use POOL for allocations.
+ * This function assumes that transaction TXN_ID in FS uses logical
+ * addressing.
*/
static svn_error_t *
store_l2p_index_entry(svn_fs_t *fs,
@@ -1626,37 +1744,40 @@ store_l2p_index_entry(svn_fs_t *fs,
apr_uint64_t item_index,
apr_pool_t *pool)
{
- if (svn_fs_fs__use_log_addressing(fs))
- {
- const char *path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
- apr_file_t *file;
- SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
- SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
- item_index, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
+ const char *path;
+ apr_file_t *file;
+
+ SVN_ERR_ASSERT(svn_fs_fs__use_log_addressing(fs));
+
+ path = svn_fs_fs__path_l2p_proto_index(fs, txn_id, pool);
+ SVN_ERR(svn_fs_fs__l2p_proto_index_open(&file, path, pool));
+ SVN_ERR(svn_fs_fs__l2p_proto_index_add_entry(file, offset,
+ item_index, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
}
-/* If the transaction TXN_ID in FS uses logical addressing, store ENTRY
- * in the phys-to-log proto index file of transaction TXN_ID.
+/* Store ENTRY in the phys-to-log proto index file of transaction TXN_ID.
* Use POOL for allocations.
+ * This function assumes that transaction TXN_ID in FS uses logical
+ * addressing.
*/
static svn_error_t *
store_p2l_index_entry(svn_fs_t *fs,
const svn_fs_fs__id_part_t *txn_id,
- svn_fs_fs__p2l_entry_t *entry,
+ const svn_fs_fs__p2l_entry_t *entry,
apr_pool_t *pool)
{
- if (svn_fs_fs__use_log_addressing(fs))
- {
- const char *path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
- apr_file_t *file;
- SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
- SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
+ const char *path;
+ apr_file_t *file;
+
+ SVN_ERR_ASSERT(svn_fs_fs__use_log_addressing(fs));
+
+ path = svn_fs_fs__path_p2l_proto_index(fs, txn_id, pool);
+ SVN_ERR(svn_fs_fs__p2l_proto_index_open(&file, path, pool));
+ SVN_ERR(svn_fs_fs__p2l_proto_index_add_entry(file, entry, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
return SVN_NO_ERROR;
}
@@ -1680,17 +1801,23 @@ allocate_item_index(apr_uint64_t *item_index,
char buffer[SVN_INT64_BUFFER_SIZE] = { 0 };
svn_boolean_t eof = FALSE;
apr_size_t to_write;
- apr_size_t read;
+ apr_size_t bytes_read;
apr_off_t offset = 0;
/* read number, increment it and write it back to disk */
SVN_ERR(svn_io_file_open(&file,
svn_fs_fs__path_txn_item_index(fs, txn_id, pool),
- APR_READ | APR_WRITE | APR_CREATE | APR_BUFFERED,
+ APR_READ | APR_WRITE | APR_CREATE,
APR_OS_DEFAULT, pool));
SVN_ERR(svn_io_file_read_full2(file, buffer, sizeof(buffer)-1,
- &read, &eof, pool));
- if (read)
+ &bytes_read, &eof, pool));
+
+ /* Item index file should be shorter than SVN_INT64_BUFFER_SIZE,
+ otherwise we truncate data. */
+ if (!eof)
+ return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+ _("Unexpected itemidx file length"));
+ else if (bytes_read)
SVN_ERR(svn_cstring_atoui64(item_index, buffer));
else
*item_index = SVN_FS_FS__ITEM_INDEX_FIRST_USER;
@@ -1968,9 +2095,7 @@ choose_delta_base(representation_t **rep,
/* Very short rep bases are simply not worth it as we are unlikely
* to re-coup the deltification space overhead of 20+ bytes. */
- svn_filesize_t rep_size = (*rep)->expanded_size
- ? (*rep)->expanded_size
- : (*rep)->size;
+ svn_filesize_t rep_size = (*rep)->expanded_size;
if (rep_size < 64)
{
*rep = NULL;
@@ -2034,6 +2159,35 @@ rep_write_cleanup(void *data)
return APR_SUCCESS;
}
+static void
+txdelta_to_svndiff(svn_txdelta_window_handler_t *handler,
+ void **handler_baton,
+ svn_stream_t *output,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ int svndiff_version;
+
+ if (ffd->delta_compression_type == compression_type_lz4)
+ {
+ SVN_ERR_ASSERT_NO_RETURN(ffd->format >= SVN_FS_FS__MIN_SVNDIFF2_FORMAT);
+ svndiff_version = 2;
+ }
+ else if (ffd->delta_compression_type == compression_type_zlib)
+ {
+ SVN_ERR_ASSERT_NO_RETURN(ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT);
+ svndiff_version = 1;
+ }
+ else
+ {
+ svndiff_version = 0;
+ }
+
+ svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version,
+ ffd->delta_compression_level, pool);
+}
+
/* Get a rep_write_baton and store it in *WB_P for the representation
indicated by NODEREV in filesystem FS. Perform allocations in
POOL. Only appropriate for file contents, not for props or
@@ -2050,8 +2204,6 @@ rep_write_get_baton(struct rep_write_baton **wb_p,
svn_stream_t *source;
svn_txdelta_window_handler_t wh;
void *whb;
- fs_fs_data_t *ffd = fs->fsap_data;
- int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
svn_fs_fs__rep_header_t header = { 0 };
b = apr_pcalloc(pool, sizeof(*b));
@@ -2071,12 +2223,12 @@ rep_write_get_baton(struct rep_write_baton **wb_p,
b->scratch_pool));
b->file = file;
- b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx,
- svn_stream_from_aprfile2(file, TRUE,
- b->scratch_pool),
- b->scratch_pool);
+ b->rep_stream = svn_stream_from_aprfile2(file, TRUE, b->scratch_pool);
+ if (svn_fs_fs__use_log_addressing(fs))
+ b->rep_stream = fnv1a_wrap_stream(&b->fnv1a_checksum_ctx, b->rep_stream,
+ b->scratch_pool);
- SVN_ERR(svn_fs_fs__get_file_offset(&b->rep_offset, file, b->scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&b->rep_offset, file, b->scratch_pool));
/* Get the base for this delta. */
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, FALSE, b->scratch_pool));
@@ -2099,20 +2251,15 @@ rep_write_get_baton(struct rep_write_baton **wb_p,
b->scratch_pool));
/* Now determine the offset of the actual svndiff data. */
- SVN_ERR(svn_fs_fs__get_file_offset(&b->delta_start, file,
- b->scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&b->delta_start, file,
+ b->scratch_pool));
/* Cleanup in case something goes wrong. */
apr_pool_cleanup_register(b->scratch_pool, b, rep_write_cleanup,
apr_pool_cleanup_null);
/* Prepare to write the svndiff data. */
- svn_txdelta_to_svndiff3(&wh,
- &whb,
- b->rep_stream,
- diff_version,
- ffd->delta_compression_level,
- pool);
+ txdelta_to_svndiff(&wh, &whb, b->rep_stream, fs, pool);
b->delta_stream = svn_txdelta_target_push(wh, whb, source,
b->scratch_pool);
@@ -2123,7 +2270,7 @@ rep_write_get_baton(struct rep_write_baton **wb_p,
}
/* For REP->SHA1_CHECKSUM, try to find an already existing representation
- in FS and return it in *OUT_REP. If no such representation exists or
+ in FS and return it in *OLD_REP. If no such representation exists or
if rep sharing has been disabled for FS, NULL will be returned. Since
there may be new duplicate representations within the same uncommitted
revision, those can be passed in REPS_HASH (maps a sha1 digest onto
@@ -2158,9 +2305,13 @@ get_shared_rep(representation_t **old_rep,
if (!ffd->rep_sharing_allowed)
return SVN_NO_ERROR;
+ /* Can't look up if we don't know the key (happens for directories). */
+ if (!rep->has_sha1)
+ return SVN_NO_ERROR;
+
/* Check and see if we already have a representation somewhere that's
identical to the one we just wrote out. Start with the hash lookup
- because it is cheepest. */
+ because it is cheapest. */
if (reps_hash)
*old_rep = apr_hash_get(reps_hash,
rep->sha1_digest,
@@ -2226,13 +2377,33 @@ get_shared_rep(representation_t **old_rep,
if (!*old_rep)
return SVN_NO_ERROR;
- /* We don't want 0-length PLAIN representations to replace non-0-length
- ones (see issue #4554). Take into account that EXPANDED_SIZE may be
- 0 in which case we have to check the on-disk SIZE. Also, this doubles
- as a simple guard against general rep-cache induced corruption. */
- if ( ((*old_rep)->expanded_size != rep->expanded_size)
- || ((rep->expanded_size == 0) && ((*old_rep)->size != rep->size)))
+ /* A simple guard against general rep-cache induced corruption. */
+ if ((*old_rep)->expanded_size != rep->expanded_size)
{
+ /* Make the problem show up in the server log.
+
+ Because not sharing reps is always a safe option,
+ terminating the request would be inappropriate.
+ */
+ err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ "Rep size %s mismatches rep-cache.db value %s "
+ "for SHA1 %s.\n"
+ "You should delete the rep-cache.db and "
+ "verify the repository. The cached rep will "
+ "not be shared.",
+ apr_psprintf(scratch_pool,
+ "%" SVN_FILESIZE_T_FMT,
+ rep->expanded_size),
+ apr_psprintf(scratch_pool,
+ "%" SVN_FILESIZE_T_FMT,
+ (*old_rep)->expanded_size),
+ svn_checksum_to_cstring_display(&checksum,
+ scratch_pool));
+
+ (fs->warning)(fs->warning_baton, err);
+ svn_error_clear(err);
+
+ /* Ignore the shared rep. */
*old_rep = NULL;
}
else
@@ -2268,7 +2439,7 @@ get_shared_rep(representation_t **old_rep,
old_rep_norm.txn_id = rep->txn_id;
/* Make sure we can later restore FILE's current position. */
- SVN_ERR(svn_fs_fs__get_file_offset(&old_position, file, scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&old_position, file, scratch_pool));
/* Compare the two representations.
* Note that the stream comparison might also produce MD5 checksum
@@ -2298,7 +2469,7 @@ get_shared_rep(representation_t **old_rep,
const char *checksum__str
= svn_checksum_to_cstring_display(&checksum, scratch_pool);
- return svn_error_createf(SVN_ERR_FS_GENERAL,
+ return svn_error_createf(SVN_ERR_FS_AMBIGUOUS_CHECKSUM_REP,
err, "SHA1 of reps '%s' and '%s' "
"matches (%s) but contents differ",
old_rep_str->data, rep_str->data,
@@ -2313,6 +2484,7 @@ get_shared_rep(representation_t **old_rep,
}
/* Copy the hash sum calculation results from MD5_CTX, SHA1_CTX into REP.
+ * SHA1 results are only be set if SHA1_CTX is not NULL.
* Use POOL for allocations.
*/
static svn_error_t *
@@ -2325,10 +2497,12 @@ digests_final(representation_t *rep,
SVN_ERR(svn_checksum_final(&checksum, md5_ctx, pool));
memcpy(rep->md5_digest, checksum->digest, svn_checksum_size(checksum));
- SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
- rep->has_sha1 = checksum != NULL;
+ rep->has_sha1 = sha1_ctx != NULL;
if (rep->has_sha1)
- memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
+ {
+ SVN_ERR(svn_checksum_final(&checksum, sha1_ctx, pool));
+ memcpy(rep->sha1_digest, checksum->digest, svn_checksum_size(checksum));
+ }
return SVN_NO_ERROR;
}
@@ -2352,7 +2526,7 @@ rep_write_contents_close(void *baton)
SVN_ERR(svn_stream_close(b->delta_stream));
/* Determine the length of the svndiff data. */
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, b->file, b->scratch_pool));
rep->size = offset - b->delta_start;
/* Fill in the rest of the representation field. */
@@ -2394,12 +2568,12 @@ rep_write_contents_close(void *baton)
/* Write out the new node-rev information. */
SVN_ERR(svn_fs_fs__put_node_revision(b->fs, b->noderev->id, b->noderev,
FALSE, b->scratch_pool));
- if (!old_rep)
+ if (!old_rep && svn_fs_fs__use_log_addressing(b->fs))
{
svn_fs_fs__p2l_entry_t entry;
entry.offset = b->rep_offset;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, b->file, b->scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, b->file, b->scratch_pool));
entry.size = offset - b->rep_offset;
entry.type = SVN_FS_FS__ITEM_TYPE_FILE_REP;
entry.item.revision = SVN_INVALID_REVNUM;
@@ -2523,6 +2697,8 @@ svn_fs_fs__set_proplist(svn_fs_t *fs,
{
noderev->prop_rep = apr_pcalloc(pool, sizeof(*noderev->prop_rep));
noderev->prop_rep->txn_id = *svn_fs_fs__id_txn_id(noderev->id);
+ SVN_ERR(set_uniquifier(fs, noderev->prop_rep, pool));
+ noderev->prop_rep->revision = SVN_INVALID_REVNUM;
SVN_ERR(svn_fs_fs__put_node_revision(fs, noderev->id, noderev, FALSE,
pool));
}
@@ -2538,6 +2714,8 @@ struct write_container_baton
apr_size_t size;
svn_checksum_ctx_t *md5_ctx;
+
+ /* SHA1 calculation is optional. If not needed, this will be NULL. */
svn_checksum_ctx_t *sha1_ctx;
};
@@ -2552,7 +2730,8 @@ write_container_handler(void *baton,
struct write_container_baton *whb = baton;
SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
- SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
+ if (whb->sha1_ctx)
+ SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
SVN_ERR(svn_stream_write(whb->stream, data, len));
whb->size += *len;
@@ -2618,19 +2797,21 @@ write_container_rep(representation_t *rep,
struct write_container_baton *whb;
svn_checksum_ctx_t *fnv1a_checksum_ctx;
apr_off_t offset = 0;
- svn_fs_fs__p2l_entry_t entry;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
whb = apr_pcalloc(scratch_pool, sizeof(*whb));
- whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
- svn_stream_from_aprfile2(file, TRUE,
- scratch_pool),
- scratch_pool);
+ whb->stream = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
+ if (svn_fs_fs__use_log_addressing(fs))
+ whb->stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, whb->stream,
+ scratch_pool);
+ else
+ fnv1a_checksum_ctx = NULL;
whb->size = 0;
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
- whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
+ if (item_type != SVN_FS_FS__ITEM_TYPE_DIR_REP)
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
stream = svn_stream_create(whb, scratch_pool);
svn_stream_set_write(stream, write_container_handler);
@@ -2671,17 +2852,22 @@ write_container_rep(representation_t *rep,
SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
offset, scratch_pool));
- entry.offset = offset;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
- entry.size = offset - entry.offset;
- entry.type = item_type;
- entry.item.revision = SVN_INVALID_REVNUM;
- entry.item.number = rep->item_index;
- SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
- fnv1a_checksum_ctx,
- scratch_pool));
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ svn_fs_fs__p2l_entry_t entry;
- SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+ entry.offset = offset;
+ SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
+ entry.size = offset - entry.offset;
+ entry.type = item_type;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = rep->item_index;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ scratch_pool));
+
+ SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+ }
return SVN_NO_ERROR;
}
@@ -2723,15 +2909,12 @@ write_container_delta_rep(representation_t *rep,
svn_checksum_ctx_t *fnv1a_checksum_ctx;
svn_stream_t *source;
svn_fs_fs__rep_header_t header = { 0 };
- svn_fs_fs__p2l_entry_t entry;
apr_off_t rep_end = 0;
apr_off_t delta_start = 0;
apr_off_t offset = 0;
struct write_container_baton *whb;
- fs_fs_data_t *ffd = fs->fsap_data;
- int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
|| (item_type == SVN_FS_FS__ITEM_TYPE_DIR_PROPS);
@@ -2739,7 +2922,7 @@ write_container_delta_rep(representation_t *rep,
SVN_ERR(choose_delta_base(&base_rep, fs, noderev, is_props, scratch_pool));
SVN_ERR(svn_fs_fs__get_contents(&source, fs, base_rep, FALSE, scratch_pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
/* Write out the rep header. */
if (base_rep)
@@ -2754,27 +2937,25 @@ write_container_delta_rep(representation_t *rep,
header.type = svn_fs_fs__rep_self_delta;
}
- file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
- svn_stream_from_aprfile2(file, TRUE,
- scratch_pool),
- scratch_pool);
+ file_stream = svn_stream_from_aprfile2(file, TRUE, scratch_pool);
+ if (svn_fs_fs__use_log_addressing(fs))
+ file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, file_stream,
+ scratch_pool);
+ else
+ fnv1a_checksum_ctx = NULL;
SVN_ERR(svn_fs_fs__write_rep_header(&header, file_stream, scratch_pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&delta_start, file, scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&delta_start, file, scratch_pool));
/* Prepare to write the svndiff data. */
- svn_txdelta_to_svndiff3(&diff_wh,
- &diff_whb,
- file_stream,
- diff_version,
- ffd->delta_compression_level,
- scratch_pool);
+ txdelta_to_svndiff(&diff_wh, &diff_whb, file_stream, fs, scratch_pool);
whb = apr_pcalloc(scratch_pool, sizeof(*whb));
whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source,
scratch_pool);
whb->size = 0;
whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, scratch_pool);
- whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
+ if (item_type != SVN_FS_FS__ITEM_TYPE_DIR_REP)
+ whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, scratch_pool);
/* serialize the hash */
stream = svn_stream_create(whb, scratch_pool);
@@ -2787,7 +2968,7 @@ write_container_delta_rep(representation_t *rep,
SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, scratch_pool));
/* Update size info. */
- SVN_ERR(svn_fs_fs__get_file_offset(&rep_end, file, scratch_pool));
+ SVN_ERR(svn_io_file_get_offset(&rep_end, file, scratch_pool));
rep->size = rep_end - delta_start;
rep->expanded_size = whb->size;
@@ -2816,17 +2997,22 @@ write_container_delta_rep(representation_t *rep,
SVN_ERR(allocate_item_index(&rep->item_index, fs, &rep->txn_id,
offset, scratch_pool));
- entry.offset = offset;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, scratch_pool));
- entry.size = offset - entry.offset;
- entry.type = item_type;
- entry.item.revision = SVN_INVALID_REVNUM;
- entry.item.number = rep->item_index;
- SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
- fnv1a_checksum_ctx,
- scratch_pool));
+ if (svn_fs_fs__use_log_addressing(fs))
+ {
+ svn_fs_fs__p2l_entry_t entry;
+
+ entry.offset = offset;
+ SVN_ERR(svn_io_file_get_offset(&offset, file, scratch_pool));
+ entry.size = offset - entry.offset;
+ entry.type = item_type;
+ entry.item.revision = SVN_INVALID_REVNUM;
+ entry.item.number = rep->item_index;
+ SVN_ERR(fnv1a_checksum_finalize(&entry.fnv1_checksum,
+ fnv1a_checksum_ctx,
+ scratch_pool));
- SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+ SVN_ERR(store_p2l_index_entry(fs, &rep->txn_id, &entry, scratch_pool));
+ }
return SVN_NO_ERROR;
}
@@ -2870,13 +3056,12 @@ validate_root_noderev(svn_fs_t *fs,
Normally (rev == root_noderev->predecessor_count), but here we
use a more roundabout check that should only trigger on new instances
- of the corruption, rather then trigger on each and every new commit
+ of the corruption, rather than trigger on each and every new commit
to a repository that has triggered the bug somewhere in its root
noderev's history.
*/
- if (root_noderev->predecessor_count != -1
- && (root_noderev->predecessor_count - head_predecessor_count)
- != (rev - head_revnum))
+ if ( (root_noderev->predecessor_count - head_predecessor_count)
+ != (rev - head_revnum))
{
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
_("predecessor count for "
@@ -2929,6 +3114,9 @@ get_final_id(svn_fs_fs__id_part_t *part,
INITIAL_OFFSET is the offset of the proto-rev-file on entry to
commit_body.
+ Collect the pair_cache_key_t of all directories written to the
+ committed cache in DIRECTORY_IDS.
+
If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
REPS_POOL) of each data rep that is new in this revision.
@@ -2950,6 +3138,7 @@ write_final_rev(const svn_fs_id_t **new_id_p,
apr_uint64_t start_node_id,
apr_uint64_t start_copy_id,
apr_off_t initial_offset,
+ apr_array_header_t *directory_ids,
apr_array_header_t *reps_to_cache,
apr_hash_t *reps_hash,
apr_pool_t *reps_pool,
@@ -2992,14 +3181,17 @@ write_final_rev(const svn_fs_id_t **new_id_p,
svn_pool_clear(subpool);
SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
start_node_id, start_copy_id, initial_offset,
- reps_to_cache, reps_hash, reps_pool, FALSE,
- subpool));
+ directory_ids, reps_to_cache, reps_hash,
+ reps_pool, FALSE, subpool));
if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
dirent->id = svn_fs_fs__id_copy(new_id, pool);
}
if (noderev->data_rep && is_txn_rep(noderev->data_rep))
{
+ pair_cache_key_t *key;
+ svn_fs_fs__dir_data_t dir_data;
+
/* Write out the contents of this directory as a text rep. */
noderev->data_rep->revision = rev;
if (ffd->deltify_directories)
@@ -3016,6 +3208,23 @@ write_final_rev(const svn_fs_id_t **new_id_p,
pool));
reset_txn_in_rep(noderev->data_rep);
+
+ /* Cache the new directory contents. Otherwise, subsequent reads
+ * or commits will likely have to reconstruct, verify and parse
+ * it again. */
+ key = apr_array_push(directory_ids);
+ key->revision = noderev->data_rep->revision;
+ key->second = noderev->data_rep->item_index;
+
+ /* Store directory contents under the new revision number but mark
+ * it as "stale" by setting the file length to 0. Committed dirs
+ * will report -1, in-txn dirs will report > 0, so that this can
+ * never match. We reset that to -1 after the commit is complete.
+ */
+ dir_data.entries = entries;
+ dir_data.txn_filesize = 0;
+
+ SVN_ERR(svn_cache__set(ffd->dir_cache, key, &dir_data, subpool));
}
}
else
@@ -3052,7 +3261,8 @@ write_final_rev(const svn_fs_id_t **new_id_p,
? SVN_FS_FS__ITEM_TYPE_DIR_PROPS
: SVN_FS_FS__ITEM_TYPE_FILE_PROPS;
SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
-
+ noderev->prop_rep->txn_id = *txn_id;
+ SVN_ERR(set_uniquifier(fs, noderev->prop_rep, pool));
noderev->prop_rep->revision = rev;
if (ffd->deltify_properties)
@@ -3077,7 +3287,7 @@ write_final_rev(const svn_fs_id_t **new_id_p,
noderev->copyroot_rev = rev;
/* root nodes have a fixed ID in log addressing mode */
- SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
+ SVN_ERR(svn_io_file_get_offset(&my_offset, file, pool));
if (svn_fs_fs__use_log_addressing(fs) && at_root)
{
/* reference the root noderev from the log-to-phys index */
@@ -3121,13 +3331,28 @@ write_final_rev(const svn_fs_id_t **new_id_p,
}
}
- /* don't serialize SHA1 for dirs to disk (waste of space) */
+ /* don't serialize SHA1 for dir content to disk (waste of space) */
+ /* ### Could clients record bogus last-changed-revisions (issue #4700)? */
if (noderev->data_rep && noderev->kind == svn_node_dir)
noderev->data_rep->has_sha1 = FALSE;
- /* don't serialize SHA1 for props to disk (waste of space) */
- if (noderev->prop_rep)
- noderev->prop_rep->has_sha1 = FALSE;
+ /* Compatibility: while we don't need to serialize SHA1 for props (it is
+ not used), older formats can only have representation strings that either
+ have both the SHA1 value *and* the uniquifier, or don't have them at all.
+ For such formats, both values get written to the disk only if the SHA1
+ is present.
+
+ We cannot omit the uniquifier, as doing so breaks svn_fs_props_changed()
+ for properties with shared representations, see issues #4623 and #4700.
+ Therefore, we skip writing SHA1, but only for the newer formats where
+ this dependency is untied and we can write the uniquifier to the disk
+ without the SHA1.
+ */
+ if (ffd->format >= SVN_FS_FS__MIN_REP_STRING_OPTIONAL_VALUES_FORMAT &&
+ noderev->prop_rep)
+ {
+ noderev->prop_rep->has_sha1 = FALSE;
+ }
/* Workaround issue #4031: is-fresh-txn-root in revision files. */
noderev->is_fresh_txn_root = FALSE;
@@ -3136,9 +3361,12 @@ write_final_rev(const svn_fs_id_t **new_id_p,
if (at_root)
SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
- file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
- svn_stream_from_aprfile2(file, TRUE, pool),
- pool);
+ file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
+ if (svn_fs_fs__use_log_addressing(fs))
+ file_stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, file_stream, pool);
+ else
+ fnv1a_checksum_ctx = NULL;
+
SVN_ERR(svn_fs_fs__write_noderev(file_stream, noderev, ffd->format,
svn_fs_fs__fs_supports_mergeinfo(fs),
pool));
@@ -3150,7 +3378,7 @@ write_final_rev(const svn_fs_id_t **new_id_p,
rev_item.revision = SVN_INVALID_REVNUM;
entry.offset = my_offset;
- SVN_ERR(svn_fs_fs__get_file_offset(&my_offset, file, pool));
+ SVN_ERR(svn_io_file_get_offset(&my_offset, file, pool));
entry.size = my_offset - entry.offset;
entry.type = SVN_FS_FS__ITEM_TYPE_NODEREV;
entry.item = rev_item;
@@ -3183,12 +3411,15 @@ write_final_changed_path_info(apr_off_t *offset_p,
svn_stream_t *stream;
svn_checksum_ctx_t *fnv1a_checksum_ctx;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, file, pool));
+
+ /* write to target file & calculate checksum if needed */
+ stream = svn_stream_from_aprfile2(file, TRUE, pool);
+ if (svn_fs_fs__use_log_addressing(fs))
+ stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx, stream, pool);
+ else
+ fnv1a_checksum_ctx = NULL;
- /* write to target file & calculate checksum */
- stream = fnv1a_wrap_stream(&fnv1a_checksum_ctx,
- svn_stream_from_aprfile2(file, TRUE, pool),
- pool);
SVN_ERR(svn_fs_fs__write_changes(stream, fs, changed_paths, TRUE, pool));
*offset_p = offset;
@@ -3199,7 +3430,7 @@ write_final_changed_path_info(apr_off_t *offset_p,
svn_fs_fs__p2l_entry_t entry;
entry.offset = offset;
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, file, pool));
entry.size = offset - entry.offset;
entry.type = SVN_FS_FS__ITEM_TYPE_CHANGES;
entry.item.revision = SVN_INVALID_REVNUM;
@@ -3223,11 +3454,10 @@ write_final_changed_path_info(apr_off_t *offset_p,
Intended to be called as the very last step in a commit before 'current'
is bumped. This implies that we are holding the write lock. */
static svn_error_t *
-verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
- svn_revnum_t new_rev,
- apr_pool_t *pool)
+verify_before_commit(svn_fs_t *fs,
+ svn_revnum_t new_rev,
+ apr_pool_t *pool)
{
-#ifdef SVN_DEBUG
fs_fs_data_t *ffd = fs->fsap_data;
svn_fs_t *ft; /* fs++ == ft */
svn_fs_root_t *root;
@@ -3257,7 +3487,6 @@ verify_as_revision_before_current_plus_plus(svn_fs_t *fs,
SVN_ERR_ASSERT(root->is_txn_root == FALSE && root->rev == new_rev);
SVN_ERR_ASSERT(ft_ffd->youngest_rev_cache == new_rev);
SVN_ERR(svn_fs_fs__verify_root(root, pool));
-#endif /* SVN_DEBUG */
return SVN_NO_ERROR;
}
@@ -3363,41 +3592,32 @@ verify_locks(svn_fs_t *fs,
return SVN_NO_ERROR;
}
-/* Return in *PATH the path to a file containing the properties that
- make up the final revision properties file. This involves setting
- svn:date and removing any temporary properties associated with the
- commit flags. */
+/* Writes final revision properties to file PATH applying permissions
+ from file PERMS_REFERENCE. This involves setting svn:date and
+ removing any temporary properties associated with the commit flags. */
static svn_error_t *
-write_final_revprop(const char **path,
+write_final_revprop(const char *path,
+ const char *perms_reference,
svn_fs_txn_t *txn,
- const svn_fs_fs__id_part_t *txn_id,
+ svn_boolean_t flush_to_disk,
apr_pool_t *pool)
{
apr_hash_t *txnprops;
- svn_boolean_t final_mods = FALSE;
svn_string_t date;
svn_string_t *client_date;
+ apr_file_t *revprop_file;
+ svn_stream_t *stream;
SVN_ERR(svn_fs_fs__txn_proplist(&txnprops, txn, pool));
/* Remove any temporary txn props representing 'flags'. */
- if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
- {
- svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
- final_mods = TRUE;
- }
-
- if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
- {
- svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
- final_mods = TRUE;
- }
+ svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD, NULL);
+ svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL);
client_date = svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE);
if (client_date)
{
svn_hash_sets(txnprops, SVN_FS__PROP_TXN_CLIENT_DATE, NULL);
- final_mods = TRUE;
}
/* Update commit time to ensure that svn:date revprops remain ordered if
@@ -3407,18 +3627,23 @@ write_final_revprop(const char **path,
date.data = svn_time_to_cstring(apr_time_now(), pool);
date.len = strlen(date.data);
svn_hash_sets(txnprops, SVN_PROP_REVISION_DATE, &date);
- final_mods = TRUE;
}
- if (final_mods)
- {
- SVN_ERR(set_txn_proplist(txn->fs, txn_id, txnprops, TRUE, pool));
- *path = path_txn_props_final(txn->fs, txn_id, pool);
- }
- else
- {
- *path = path_txn_props(txn->fs, txn_id, pool);
- }
+ /* Create new revprops file. Tell OS to truncate existing file,
+ since file may already exists from failed transaction. */
+ SVN_ERR(svn_io_file_open(&revprop_file, path,
+ APR_WRITE | APR_CREATE | APR_TRUNCATE
+ | APR_BUFFERED, APR_OS_DEFAULT, pool));
+
+ stream = svn_stream_from_aprfile2(revprop_file, TRUE, pool);
+ SVN_ERR(svn_hash_write2(txnprops, stream, SVN_HASH_TERMINATOR, pool));
+ SVN_ERR(svn_stream_close(stream));
+
+ if (flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(revprop_file, pool));
+ SVN_ERR(svn_io_file_close(revprop_file, pool));
+
+ SVN_ERR(svn_io_copy_perms(perms_reference, path, pool));
return SVN_NO_ERROR;
}
@@ -3464,6 +3689,41 @@ svn_fs_fs__add_index_data(svn_fs_t *fs,
return SVN_NO_ERROR;
}
+/* Mark the directories cached in FS with the keys from DIRECTORY_IDS
+ * as "valid" now. Use SCRATCH_POOL for temporaries. */
+static svn_error_t *
+promote_cached_directories(svn_fs_t *fs,
+ apr_array_header_t *directory_ids,
+ apr_pool_t *scratch_pool)
+{
+ fs_fs_data_t *ffd = fs->fsap_data;
+ apr_pool_t *iterpool;
+ int i;
+
+ if (!ffd->dir_cache)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+ for (i = 0; i < directory_ids->nelts; ++i)
+ {
+ const pair_cache_key_t *key
+ = &APR_ARRAY_IDX(directory_ids, i, pair_cache_key_t);
+
+ svn_pool_clear(iterpool);
+
+ /* Currently, the entry for KEY - if it still exists - is marked
+ * as "stale" and would not be used. Mark it as current for in-
+ * revison data. */
+ SVN_ERR(svn_cache__set_partial(ffd->dir_cache, key,
+ svn_fs_fs__reset_txn_filesize, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
/* Baton used for commit_body below. */
struct commit_baton {
svn_revnum_t *new_rev_p;
@@ -3483,7 +3743,7 @@ commit_body(void *baton, apr_pool_t *pool)
struct commit_baton *cb = baton;
fs_fs_data_t *ffd = cb->fs->fsap_data;
const char *old_rev_filename, *rev_filename, *proto_filename;
- const char *revprop_filename, *final_revprop;
+ const char *revprop_filename;
const svn_fs_id_t *root_id, *new_root_id;
apr_uint64_t start_node_id;
apr_uint64_t start_copy_id;
@@ -3493,6 +3753,8 @@ commit_body(void *baton, apr_pool_t *pool)
apr_off_t initial_offset, changed_path_offset;
const svn_fs_fs__id_part_t *txn_id = svn_fs_fs__txn_get_id(cb->txn);
apr_hash_t *changed_paths;
+ apr_array_header_t *directory_ids = apr_array_make(pool, 4,
+ sizeof(pair_cache_key_t));
/* Re-Read the current repository format. All our repo upgrade and
config evaluation strategies are such that existing information in
@@ -3540,14 +3802,14 @@ commit_body(void *baton, apr_pool_t *pool)
/* Get a write handle on the proto revision file. */
SVN_ERR(get_writable_proto_rev(&proto_file, &proto_file_lockcookie,
cb->fs, txn_id, pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&initial_offset, proto_file, pool));
+ SVN_ERR(svn_io_file_get_offset(&initial_offset, proto_file, pool));
/* Write out all the node-revisions and directory contents. */
root_id = svn_fs_fs__id_txn_create_root(txn_id, pool);
SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
start_node_id, start_copy_id, initial_offset,
- cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
- TRUE, pool));
+ directory_ids, cb->reps_to_cache, cb->reps_hash,
+ cb->reps_pool, TRUE, pool));
/* Write the changed-path information. */
SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
@@ -3575,7 +3837,8 @@ commit_body(void *baton, apr_pool_t *pool)
NULL, pool));
}
- SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
+ if (ffd->flush_to_disk)
+ SVN_ERR(svn_io_file_flush_to_disk(proto_file, pool));
SVN_ERR(svn_io_file_close(proto_file, pool));
/* We don't unlock the prototype revision file immediately to avoid a
@@ -3628,7 +3891,8 @@ commit_body(void *baton, apr_pool_t *pool)
rev_filename = svn_fs_fs__path_rev(cb->fs, new_rev, pool);
proto_filename = svn_fs_fs__path_txn_proto_rev(cb->fs, txn_id, pool);
SVN_ERR(svn_fs_fs__move_into_place(proto_filename, rev_filename,
- old_rev_filename, pool));
+ old_rev_filename, ffd->flush_to_disk,
+ pool));
/* Now that we've moved the prototype revision file out of the way,
we can unlock it (since further attempts to write to the file
@@ -3636,15 +3900,19 @@ commit_body(void *baton, apr_pool_t *pool)
remove the transaction directory later. */
SVN_ERR(unlock_proto_rev(cb->fs, txn_id, proto_file_lockcookie, pool));
- /* Move the revprops file into place. */
+ /* Write final revprops file. */
SVN_ERR_ASSERT(! svn_fs_fs__is_packed_revprop(cb->fs, new_rev));
- SVN_ERR(write_final_revprop(&revprop_filename, cb->txn, txn_id, pool));
- final_revprop = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
- SVN_ERR(svn_fs_fs__move_into_place(revprop_filename, final_revprop,
- old_rev_filename, pool));
+ revprop_filename = svn_fs_fs__path_revprops(cb->fs, new_rev, pool);
+ SVN_ERR(write_final_revprop(revprop_filename, old_rev_filename,
+ cb->txn, ffd->flush_to_disk, pool));
+
+ /* Run paranoia checks. */
+ if (ffd->verify_before_commit)
+ {
+ SVN_ERR(verify_before_commit(cb->fs, new_rev, pool));
+ }
/* Update the 'current' file. */
- SVN_ERR(verify_as_revision_before_current_plus_plus(cb->fs, new_rev, pool));
SVN_ERR(write_final_current(cb->fs, txn_id, new_rev, start_node_id,
start_copy_id, pool));
@@ -3657,6 +3925,10 @@ commit_body(void *baton, apr_pool_t *pool)
ffd->youngest_rev_cache = new_rev;
+ /* Make the directory contents alreday cached for the new revision
+ * visible. */
+ SVN_ERR(promote_cached_directories(cb->fs, directory_ids, pool));
+
/* Remove this transaction directory. */
SVN_ERR(svn_fs_fs__purge_txn(cb->fs, cb->txn->id, pool));
@@ -3731,7 +4003,7 @@ svn_fs_fs__commit(svn_revnum_t *new_rev_p,
err = write_reps_to_cache(fs, cb.reps_to_cache, pool);
err = svn_sqlite__finish_transaction(ffd->rep_cache_db, err);
- if (svn_error_find_cause(err, SVN_SQLITE__ERR_ROLLBACK_FAILED))
+ if (svn_error_find_cause(err, SVN_ERR_SQLITE_ROLLBACK_FAILED))
{
/* Failed rollback means that our db connection is unusable, and
the only thing we can do is close it. The connection will be
@@ -3959,6 +4231,5 @@ svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p,
svn_string_create("0", pool));
ftd = (*txn_p)->fsap_data;
- return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, FALSE,
- pool));
+ return svn_error_trace(set_txn_proplist(fs, &ftd->txn_id, props, pool));
}
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c
index 5893da22b0cbd..76209f368bf94 100644
--- a/subversion/libsvn_fs_fs/tree.c
+++ b/subversion/libsvn_fs_fs/tree.c
@@ -209,17 +209,15 @@ auto_clear_dag_cache(fs_fs_dag_cache_t* cache)
}
}
-/* 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.
+/* Returns a 32 bit hash value for the given REVISION and PATH of exactly
+ * PATH_LEN chars.
*/
-static cache_entry_t *
-cache_lookup( fs_fs_dag_cache_t *cache
- , svn_revnum_t revision
- , const char *path)
+static apr_uint32_t
+hash_func(svn_revnum_t revision,
+ const char *path,
+ apr_size_t path_len)
{
- apr_size_t i, bucket_index;
- apr_size_t path_len = strlen(path);
+ apr_size_t i;
apr_uint32_t hash_value = (apr_uint32_t)revision;
#if SVN_UNALIGNED_ACCESS_IS_OK
@@ -227,20 +225,7 @@ cache_lookup( fs_fs_dag_cache_t *cache
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))
- {
- /* Remember the position of the last node we found in this cache. */
- if (result->node)
- cache->last_non_empty = cache->last_hit;
-
- return result;
- }
-
- /* need to do a full lookup. Calculate the hash value
+ /* Calculate the hash value
(HASH_VALUE has been initialized to REVISION).
Note that the actual hash function is arbitrary as long as its result
@@ -283,6 +268,37 @@ cache_lookup( fs_fs_dag_cache_t *cache
*/
hash_value = hash_value * 32 + (hash_value + (unsigned char)path[i]);
+ return hash_value;
+}
+
+/* For the given REVISION and PATH, return the respective node found in
+ * CACHE. If there is none, return NULL.
+ */
+static dag_node_t *
+cache_lookup( fs_fs_dag_cache_t *cache
+ , svn_revnum_t revision
+ , const char *path)
+{
+ apr_size_t bucket_index;
+ apr_size_t path_len = strlen(path);
+ apr_uint32_t hash_value;
+
+ /* 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))
+ {
+ /* Remember the position of the last node we found in this cache. */
+ if (result->node)
+ cache->last_non_empty = cache->last_hit;
+
+ return result->node;
+ }
+
+ /* need to do a full lookup. */
+ hash_value = hash_func(revision, path, path_len);
+
bucket_index = hash_value + (hash_value >> 16);
bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
@@ -297,16 +313,7 @@ cache_lookup( fs_fs_dag_cache_t *cache
|| (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 NULL;
}
else if (result->node)
{
@@ -315,7 +322,46 @@ cache_lookup( fs_fs_dag_cache_t *cache
cache->last_non_empty = bucket_index;
}
- return result;
+ return result->node;
+}
+
+/* Store a copy of NODE in CACHE, taking REVISION and PATH as key.
+ * This function will clean the cache at regular intervals.
+ */
+static void
+cache_insert(fs_fs_dag_cache_t *cache,
+ svn_revnum_t revision,
+ const char *path,
+ dag_node_t *node)
+{
+ apr_size_t bucket_index;
+ apr_size_t path_len = strlen(path);
+ apr_uint32_t hash_value;
+ cache_entry_t *entry;
+
+ auto_clear_dag_cache(cache);
+
+ /* calculate the bucket index to use */
+ hash_value = hash_func(revision, path, path_len);
+
+ bucket_index = hash_value + (hash_value >> 16);
+ bucket_index = (bucket_index + (bucket_index >> 8)) % BUCKET_COUNT;
+
+ /* access the corresponding bucket and remember its location */
+ entry = &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 */
+ entry->hash_value = hash_value;
+ entry->revision = revision;
+ if (entry->path_len < path_len)
+ entry->path = apr_palloc(cache->pool, path_len + 1);
+ entry->path_len = path_len;
+ memcpy(entry->path, path, path_len + 1);
+
+ entry->node = svn_fs_fs__dag_dup(node, cache->pool);
+ cache->insertions++;
}
/* Optimistic lookup using the last seen non-empty location in CACHE.
@@ -393,11 +439,9 @@ dag_node_cache_get(dag_node_t **node_p,
/* 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)
+ node = cache_lookup(ffd->dag_node_cache, root->rev, path);
+ if (node == NULL)
{
locate_cache(&cache, &key, root, path, pool);
SVN_ERR(svn_cache__get((void **)&node, &found, cache, key, pool));
@@ -408,14 +452,13 @@ dag_node_cache_get(dag_node_t **node_p,
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);
+ cache_insert(ffd->dag_node_cache, root->rev, path, node);
}
}
else
{
/* Copy the node from L1 cache into the passed-in POOL. */
- node = svn_fs_fs__dag_dup(bucket->node, pool);
+ node = svn_fs_fs__dag_dup(node, pool);
}
}
else
@@ -1100,8 +1143,28 @@ 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->data),
- apr_psprintf(iterpool, _("Failure opening '%s'"), path));
+ {
+ const char *msg;
+
+ /* Since this is not a directory and we are looking for some
+ sub-path, that sub-path will not exist. That will be o.k.,
+ if we are just here to check for the path's existence. */
+ if (flags & open_path_allow_null)
+ {
+ parent_path = NULL;
+ break;
+ }
+
+ /* It's really a problem ... */
+ msg = root->is_txn_root
+ ? apr_psprintf(iterpool,
+ _("Failure opening '%s' in transaction '%s'"),
+ path, root->txn)
+ : apr_psprintf(iterpool,
+ _("Failure opening '%s' in revision %ld"),
+ path, root->rev);
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far->data), msg);
+ }
rest = next;
here = child;
@@ -1232,11 +1295,15 @@ get_dag(dag_node_t **dag_node_p,
{
/* Canonicalize the input PATH. As it turns out, >95% of all paths
* seen here during e.g. svnadmin verify are non-canonical, i.e.
- * miss the leading '/'. Unconditional canonicalization has a net
- * performance benefit over previously checking path for being
- * canonical. */
- path = svn_fs__canonicalize_abspath(path, pool);
- SVN_ERR(dag_node_cache_get(&node, root, path, pool));
+ * miss the leading '/'. Check for those quickly.
+ *
+ * For normalized paths, it is much faster to check the path than
+ * to attempt a second cache lookup (which would fail). */
+ if (*path != '/' || !svn_fs__is_canonical_abspath(path))
+ {
+ path = svn_fs__canonicalize_abspath(path, pool);
+ SVN_ERR(dag_node_cache_get(&node, root, path, pool));
+ }
if (! node)
{
@@ -1382,7 +1449,7 @@ fs_node_relation(svn_fs_node_relation_t *relation,
/* Noderevs have the same node-ID now. So, they *seem* to be related.
*
* Special case: Different txns may create the same (txn-local) node ID.
- * Only when they are committed can they actually be related to others. */
+ * These are not related to each other, nor to any other node ID so far. */
if (different_txn && node_id_a.revision == SVN_INVALID_REVNUM)
{
*relation = svn_fs_node_unrelated;
@@ -1427,29 +1494,6 @@ fs_node_created_path(const char **created_path,
return SVN_NO_ERROR;
}
-
-/* Set *KIND_P to the type of node located at PATH under ROOT.
- Perform temporary allocations in POOL. */
-static svn_error_t *
-node_kind(svn_node_kind_t *kind_p,
- svn_fs_root_t *root,
- const char *path,
- apr_pool_t *pool)
-{
- const svn_fs_id_t *node_id;
- dag_node_t *node;
-
- /* Get the node id. */
- SVN_ERR(svn_fs_fs__node_id(&node_id, root, path, pool));
-
- /* Use the node id to get the real kind. */
- SVN_ERR(svn_fs_fs__dag_get_node(&node, root->fs, node_id, pool));
- *kind_p = svn_fs_fs__dag_node_kind(node);
-
- return SVN_NO_ERROR;
-}
-
-
/* Set *KIND_P to the type of node present at PATH under ROOT. If
PATH does not exist under ROOT, set *KIND_P to svn_node_none. Use
POOL for temporary allocation. */
@@ -1459,17 +1503,23 @@ svn_fs_fs__check_path(svn_node_kind_t *kind_p,
const char *path,
apr_pool_t *pool)
{
- svn_error_t *err = node_kind(kind_p, root, path, pool);
+ dag_node_t *node;
+ svn_error_t *err;
+
+ err = get_dag(&node, root, path, pool);
if (err &&
((err->apr_err == SVN_ERR_FS_NOT_FOUND)
|| (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
{
svn_error_clear(err);
- err = SVN_NO_ERROR;
*kind_p = svn_node_none;
+ return SVN_NO_ERROR;
}
+ else if (err)
+ return svn_error_trace(err);
- return svn_error_trace(err);
+ *kind_p = svn_fs_fs__dag_node_kind(node);
+ return SVN_NO_ERROR;
}
/* Set *VALUE_P to the value of the property named PROPNAME of PATH in
@@ -3188,23 +3238,18 @@ fs_contents_changed(svn_boolean_t *changed_p,
(SVN_ERR_FS_GENERAL, NULL,
_("Cannot compare file contents between two different filesystems"));
- /* Check that both paths are files. */
- {
- svn_node_kind_t kind;
-
- SVN_ERR(svn_fs_fs__check_path(&kind, root1, path1, pool));
- if (kind != svn_node_file)
- return svn_error_createf
- (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
-
- SVN_ERR(svn_fs_fs__check_path(&kind, root2, path2, pool));
- if (kind != svn_node_file)
- return svn_error_createf
- (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
- }
-
SVN_ERR(get_dag(&node1, root1, path1, pool));
+ /* Make sure that path is file. */
+ if (svn_fs_fs__dag_node_kind(node1) != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), path1);
+
SVN_ERR(get_dag(&node2, root2, path2, pool));
+ /* Make sure that path is file. */
+ if (svn_fs_fs__dag_node_kind(node2) != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("'%s' is not a file"), path2);
+
return svn_fs_fs__dag_things_different(NULL, changed_p,
node1, node2, strict, pool);
}
@@ -3256,6 +3301,187 @@ fs_paths_changed(apr_hash_t **changed_paths_p,
}
+/* Copy the contents of ENTRY at PATH with LEN to OUTPUT. */
+static void
+convert_path_change(svn_fs_path_change3_t *output,
+ const char *path,
+ size_t path_len,
+ svn_fs_path_change2_t *entry)
+{
+ output->path.data = path;
+ output->path.len = path_len;
+ output->change_kind = entry->change_kind;
+ output->node_kind = entry->node_kind;
+ output->text_mod = entry->text_mod;
+ output->prop_mod = entry->prop_mod;
+ output->mergeinfo_mod = entry->mergeinfo_mod;
+ output->copyfrom_known = entry->copyfrom_known;
+ output->copyfrom_rev = entry->copyfrom_rev;
+ output->copyfrom_path = entry->copyfrom_path;
+}
+
+/* FSAP data structure for in-txn changes list iterators. */
+typedef struct fs_txn_changes_iterator_data_t
+{
+ /* Current iterator position. */
+ apr_hash_index_t *hi;
+
+ /* For efficiency such that we don't need to dynamically allocate
+ yet another copy of that data. */
+ svn_fs_path_change3_t change;
+} fs_txn_changes_iterator_data_t;
+
+/* Implement changes_iterator_vtable_t.get for in-txn change lists. */
+static svn_error_t *
+fs_txn_changes_iterator_get(svn_fs_path_change3_t **change,
+ svn_fs_path_change_iterator_t *iterator)
+{
+ fs_txn_changes_iterator_data_t *data = iterator->fsap_data;
+
+ if (data->hi)
+ {
+ const void *key;
+ apr_ssize_t length;
+ void *value;
+ apr_hash_this(data->hi, &key, &length, &value);
+
+ convert_path_change(&data->change, key, length, value);
+
+ *change = &data->change;
+ data->hi = apr_hash_next(data->hi);
+ }
+ else
+ {
+ *change = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static changes_iterator_vtable_t txn_changes_iterator_vtable =
+{
+ fs_txn_changes_iterator_get
+};
+
+/* FSAP data structure for in-revision changes list iterators. */
+typedef struct fs_revision_changes_iterator_data_t
+{
+ /* Context that tells the lower layers from where to fetch the next
+ block of changes. */
+ svn_fs_fs__changes_context_t *context;
+
+ /* Changes to send. */
+ apr_array_header_t *changes;
+
+ /* Current indexes within CHANGES. */
+ int idx;
+
+ /* For efficiency such that we don't need to dynamically allocate
+ yet another copy of that data. */
+ svn_fs_path_change3_t change;
+
+ /* A cleanable scratch pool in case we need one.
+ No further sub-pool creation necessary. */
+ apr_pool_t *scratch_pool;
+} fs_revision_changes_iterator_data_t;
+
+/* Implement changes_iterator_vtable_t.get for in-revision change lists. */
+static svn_error_t *
+fs_revision_changes_iterator_get(svn_fs_path_change3_t **change,
+ svn_fs_path_change_iterator_t *iterator)
+{
+ fs_revision_changes_iterator_data_t *data = iterator->fsap_data;
+
+ /* If we exhausted our block of changes and did not reach the end of the
+ list, yet, fetch the next block. Note that that block may be empty. */
+ if ((data->idx >= data->changes->nelts) && !data->context->eol)
+ {
+ apr_pool_t *changes_pool = data->changes->pool;
+
+ /* Drop old changes block, read new block. */
+ svn_pool_clear(changes_pool);
+ SVN_ERR(svn_fs_fs__get_changes(&data->changes, data->context,
+ changes_pool, data->scratch_pool));
+ data->idx = 0;
+
+ /* Immediately release any temporary data. */
+ svn_pool_clear(data->scratch_pool);
+ }
+
+ if (data->idx < data->changes->nelts)
+ {
+ change_t *entry = APR_ARRAY_IDX(data->changes, data->idx, change_t *);
+ convert_path_change(&data->change, entry->path.data, entry->path.len,
+ &entry->info);
+
+ *change = &data->change;
+ ++data->idx;
+ }
+ else
+ {
+ *change = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static changes_iterator_vtable_t rev_changes_iterator_vtable =
+{
+ fs_revision_changes_iterator_get
+};
+
+static svn_error_t *
+fs_report_changes(svn_fs_path_change_iterator_t **iterator,
+ svn_fs_root_t *root,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_fs_path_change_iterator_t *result = apr_pcalloc(result_pool,
+ sizeof(*result));
+ if (root->is_txn_root)
+ {
+ fs_txn_changes_iterator_data_t *data = apr_pcalloc(result_pool,
+ sizeof(*data));
+ apr_hash_t *changed_paths;
+ SVN_ERR(svn_fs_fs__txn_changes_fetch(&changed_paths, root->fs,
+ root_txn_id(root), result_pool));
+
+ data->hi = apr_hash_first(result_pool, changed_paths);
+ result->fsap_data = data;
+ result->vtable = &txn_changes_iterator_vtable;
+ }
+ else
+ {
+ /* The block of changes that we retrieve need to live in a separately
+ cleanable pool. */
+ apr_pool_t *changes_pool = svn_pool_create(result_pool);
+
+ /* Our iteration context info. */
+ fs_revision_changes_iterator_data_t *data = apr_pcalloc(result_pool,
+ sizeof(*data));
+
+ /* This pool must remain valid as long as ITERATOR lives but will
+ be used only for temporary allocations and will be cleaned up
+ frequently. So, this must be a sub-pool of RESULT_POOL. */
+ data->scratch_pool = svn_pool_create(result_pool);
+
+ /* Fetch the first block of data. */
+ SVN_ERR(svn_fs_fs__create_changes_context(&data->context,
+ root->fs, root->rev,
+ result_pool));
+ SVN_ERR(svn_fs_fs__get_changes(&data->changes, data->context,
+ changes_pool, scratch_pool));
+
+ /* Return the fully initialized object. */
+ result->fsap_data = data;
+ result->vtable = &rev_changes_iterator_vtable;
+ }
+
+ *iterator = result;
+
+ return SVN_NO_ERROR;
+}
+
/* Our coolio opaque history object. */
typedef struct fs_history_data_t
@@ -3273,6 +3499,14 @@ typedef struct fs_history_data_t
/* FALSE until the first call to svn_fs_history_prev(). */
svn_boolean_t is_interesting;
+
+ /* If not SVN_INVALID_REVISION, we know that the next copy operation
+ is at this revision. */
+ svn_revnum_t next_copy;
+
+ /* If not NULL, this is the noderev ID of PATH@REVISION. */
+ const svn_fs_id_t *current_id;
+
} fs_history_data_t;
static svn_fs_history_t *
@@ -3282,6 +3516,8 @@ assemble_history(svn_fs_t *fs,
svn_boolean_t is_interesting,
const char *path_hint,
svn_revnum_t rev_hint,
+ svn_revnum_t next_copy,
+ const svn_fs_id_t *current_id,
apr_pool_t *pool);
@@ -3308,7 +3544,8 @@ fs_node_history(svn_fs_history_t **history_p,
/* Okay, all seems well. Build our history object and return it. */
*history_p = assemble_history(root->fs, path, root->rev, FALSE, NULL,
- SVN_INVALID_REVNUM, result_pool);
+ SVN_INVALID_REVNUM, SVN_INVALID_REVNUM,
+ NULL, result_pool);
return SVN_NO_ERROR;
}
@@ -3609,10 +3846,50 @@ history_prev(svn_fs_history_t **prev_history,
svn_boolean_t reported = fhd->is_interesting;
svn_revnum_t copyroot_rev;
const char *copyroot_path;
+ const svn_fs_id_t *pred_id = NULL;
/* Initialize our return value. */
*prev_history = NULL;
+ /* When following history, there tend to be long sections of linear
+ history where there are no copies at PATH or its parents. Within
+ these sections, we only need to follow the node history. */
+ if ( SVN_IS_VALID_REVNUM(fhd->next_copy)
+ && revision > fhd->next_copy
+ && fhd->current_id)
+ {
+ /* We know the last reported node (CURRENT_ID) and the NEXT_COPY
+ revision is somewhat further in the past. */
+ node_revision_t *noderev;
+ assert(reported);
+
+ /* Get the previous node change. If there is none, then we already
+ reported the initial addition and this history traversal is done. */
+ SVN_ERR(svn_fs_fs__get_node_revision(&noderev, fs, fhd->current_id,
+ scratch_pool, scratch_pool));
+ if (! noderev->predecessor_id)
+ return SVN_NO_ERROR;
+
+ /* If the previous node change is younger than the next copy, it is
+ part of the linear history section. */
+ commit_rev = svn_fs_fs__id_rev(noderev->predecessor_id);
+ if (commit_rev > fhd->next_copy)
+ {
+ /* Within the linear history, simply report all node changes and
+ continue with the respective predecessor. */
+ *prev_history = assemble_history(fs, noderev->created_path,
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM,
+ fhd->next_copy,
+ noderev->predecessor_id,
+ result_pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* We hit a copy. Fall back to the standard code path. */
+ }
+
/* If our last history report left us hints about where to pickup
the chase, then our last report was on the destination of a
copy. If we are crossing copies, start from those locations,
@@ -3651,7 +3928,9 @@ history_prev(svn_fs_history_t **prev_history,
need now to do so) ... */
*prev_history = assemble_history(fs, commit_path,
commit_rev, TRUE, NULL,
- SVN_INVALID_REVNUM, result_pool);
+ SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, NULL,
+ result_pool);
return SVN_NO_ERROR;
}
else
@@ -3659,8 +3938,6 @@ history_prev(svn_fs_history_t **prev_history,
/* ... or we *have* reported on this revision, and must now
progress toward this node's predecessor (unless there is
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));
if (! pred_id)
return SVN_NO_ERROR;
@@ -3731,12 +4008,18 @@ history_prev(svn_fs_history_t **prev_history,
retry = TRUE;
*prev_history = assemble_history(fs, path, dst_rev, ! retry,
- src_path, src_rev, result_pool);
+ src_path, src_rev,
+ SVN_INVALID_REVNUM, NULL,
+ result_pool);
}
else
{
+ /* We know the next copy revision. If we are not at the copy rev
+ itself, we will also know the predecessor node ID and the next
+ invocation will use the optimized "linear history" code path. */
*prev_history = assemble_history(fs, commit_path, commit_rev, TRUE,
- NULL, SVN_INVALID_REVNUM, result_pool);
+ NULL, SVN_INVALID_REVNUM,
+ copyroot_rev, pred_id, result_pool);
}
return SVN_NO_ERROR;
@@ -3767,10 +4050,12 @@ fs_history_prev(svn_fs_history_t **prev_history_p,
if (! fhd->is_interesting)
prev_history = assemble_history(fs, "/", fhd->revision,
1, NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, NULL,
result_pool);
else if (fhd->revision > 0)
prev_history = assemble_history(fs, "/", fhd->revision - 1,
1, NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, NULL,
result_pool);
}
else
@@ -3830,6 +4115,8 @@ assemble_history(svn_fs_t *fs,
svn_boolean_t is_interesting,
const char *path_hint,
svn_revnum_t rev_hint,
+ svn_revnum_t next_copy,
+ const svn_fs_id_t *current_id,
apr_pool_t *pool)
{
svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history));
@@ -3840,6 +4127,8 @@ assemble_history(svn_fs_t *fs,
fhd->path_hint = path_hint ? svn_fs__canonicalize_abspath(path_hint, pool)
: NULL;
fhd->rev_hint = rev_hint;
+ fhd->next_copy = next_copy;
+ fhd->current_id = current_id ? svn_fs_fs__id_copy(current_id, pool) : NULL;
fhd->fs = fs;
history->vtable = &history_vtable;
@@ -3853,21 +4142,19 @@ assemble_history(svn_fs_t *fs,
/* DIR_DAG is a directory DAG node which has mergeinfo in its
descendants. This function iterates over its children. For each
- child with immediate mergeinfo, it adds its mergeinfo to
- RESULT_CATALOG. appropriate arguments. For each child with
- descendants with mergeinfo, it recurses. Note that it does *not*
- call the action on the path for DIR_DAG itself.
-
- POOL is used for temporary allocations, including the mergeinfo
- hashes passed to actions; RESULT_POOL is used for the mergeinfo added
- to RESULT_CATALOG.
+ child with immediate mergeinfo, call RECEIVER with it and BATON.
+ For each child with descendants with mergeinfo, it recurses. Note
+ that it does *not* call the action on the path for DIR_DAG itself.
+
+ SCRATCH_POOL is used for temporary allocations, including the mergeinfo
+ hashes passed to actions.
*/
static svn_error_t *
crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
const char *this_path,
dag_node_t *dir_dag,
- svn_mergeinfo_catalog_t result_catalog,
- apr_pool_t *result_pool,
+ svn_fs_mergeinfo_receiver_t receiver,
+ void *baton,
apr_pool_t *scratch_pool)
{
apr_array_header_t *entries;
@@ -3914,7 +4201,7 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
error. */
err = svn_mergeinfo_parse(&kid_mergeinfo,
mergeinfo_string->data,
- result_pool);
+ iterpool);
if (err)
{
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
@@ -3924,8 +4211,7 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
}
else
{
- svn_hash_sets(result_catalog, apr_pstrdup(result_pool, kid_path),
- kid_mergeinfo);
+ SVN_ERR(receiver(kid_path, kid_mergeinfo, baton, iterpool));
}
}
@@ -3933,8 +4219,8 @@ crawl_directory_dag_for_mergeinfo(svn_fs_root_t *root,
SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
kid_path,
kid_dag,
- result_catalog,
- result_pool,
+ receiver,
+ baton,
iterpool));
}
@@ -4118,14 +4404,13 @@ get_mergeinfo_for_path(svn_mergeinfo_t *mergeinfo,
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. */
+/* Invoke RECEIVER with BATON for each mergeinfo found on descendants of
+ PATH (but not PATH itself). Use SCRATCH_POOL for temporary values. */
static svn_error_t *
-add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
- svn_fs_root_t *root,
+add_descendant_mergeinfo(svn_fs_root_t *root,
const char *path,
- apr_pool_t *result_pool,
+ svn_fs_mergeinfo_receiver_t receiver,
+ void *baton,
apr_pool_t *scratch_pool)
{
dag_node_t *this_dag;
@@ -4138,27 +4423,28 @@ add_descendant_mergeinfo(svn_mergeinfo_catalog_t result_catalog,
SVN_ERR(crawl_directory_dag_for_mergeinfo(root,
path,
this_dag,
- result_catalog,
- result_pool,
+ receiver,
+ baton,
scratch_pool));
return SVN_NO_ERROR;
}
-/* Get the mergeinfo for a set of paths, returned in
- *MERGEINFO_CATALOG. Returned values are allocated in
- POOL, while temporary values are allocated in a sub-pool. */
+/* Find all the mergeinfo for a set of PATHS under ROOT and report it
+ through RECEIVER with BATON. INHERITED, INCLUDE_DESCENDANTS and
+ ADJUST_INHERITED_MERGEINFO are the same as in the FS API.
+
+ Allocate temporary values are allocated in SCRATCH_POOL. */
static svn_error_t *
get_mergeinfos_for_paths(svn_fs_root_t *root,
- svn_mergeinfo_catalog_t *mergeinfo_catalog,
const apr_array_header_t *paths,
svn_mergeinfo_inheritance_t inherit,
svn_boolean_t include_descendants,
svn_boolean_t adjust_inherited_mergeinfo,
- apr_pool_t *result_pool,
+ svn_fs_mergeinfo_receiver_t receiver,
+ void *baton,
apr_pool_t *scratch_pool)
{
- svn_mergeinfo_catalog_t result_catalog = svn_hash__make(result_pool);
apr_pool_t *iterpool = svn_pool_create(scratch_pool);
int i;
@@ -4172,7 +4458,7 @@ get_mergeinfos_for_paths(svn_fs_root_t *root,
err = get_mergeinfo_for_path(&path_mergeinfo, root, path,
inherit, adjust_inherited_mergeinfo,
- result_pool, iterpool);
+ iterpool, iterpool);
if (err)
{
if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
@@ -4188,27 +4474,26 @@ get_mergeinfos_for_paths(svn_fs_root_t *root,
}
if (path_mergeinfo)
- svn_hash_sets(result_catalog, path, path_mergeinfo);
+ SVN_ERR(receiver(path, path_mergeinfo, baton, iterpool));
if (include_descendants)
- SVN_ERR(add_descendant_mergeinfo(result_catalog, root, path,
- result_pool, scratch_pool));
+ SVN_ERR(add_descendant_mergeinfo(root, path, receiver, baton,
+ iterpool));
}
svn_pool_destroy(iterpool);
- *mergeinfo_catalog = result_catalog;
return SVN_NO_ERROR;
}
/* Implements svn_fs_get_mergeinfo. */
static svn_error_t *
-fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
- svn_fs_root_t *root,
+fs_get_mergeinfo(svn_fs_root_t *root,
const apr_array_header_t *paths,
svn_mergeinfo_inheritance_t inherit,
svn_boolean_t include_descendants,
svn_boolean_t adjust_inherited_mergeinfo,
- apr_pool_t *result_pool,
+ svn_fs_mergeinfo_receiver_t receiver,
+ void *baton,
apr_pool_t *scratch_pool)
{
fs_fs_data_t *ffd = root->fs->fsap_data;
@@ -4226,17 +4511,18 @@ fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
SVN_FS_FS__MIN_MERGEINFO_FORMAT, root->fs->path, ffd->format);
/* Retrieve a path -> mergeinfo hash mapping. */
- return get_mergeinfos_for_paths(root, catalog, paths,
- inherit,
+ return get_mergeinfos_for_paths(root, paths, inherit,
include_descendants,
adjust_inherited_mergeinfo,
- result_pool, scratch_pool);
+ receiver, baton,
+ scratch_pool);
}
/* The vtable associated with root objects. */
static root_vtable_t root_vtable = {
fs_paths_changed,
+ fs_report_changes,
svn_fs_fs__check_path,
fs_node_history,
svn_fs_fs__node_id,
diff --git a/subversion/libsvn_fs_fs/util.c b/subversion/libsvn_fs_fs/util.c
index faa1e3d319b29..e191322480351 100644
--- a/subversion/libsvn_fs_fs/util.c
+++ b/subversion/libsvn_fs_fs/util.c
@@ -428,6 +428,7 @@ svn_fs_fs__write_min_unpacked_rev(svn_fs_t *fs,
svn_revnum_t revnum,
apr_pool_t *scratch_pool)
{
+ fs_fs_data_t *ffd = fs->fsap_data;
const char *final_path;
char buf[SVN_INT64_BUFFER_SIZE];
apr_size_t len = svn__i64toa(buf, revnum);
@@ -435,8 +436,9 @@ svn_fs_fs__write_min_unpacked_rev(svn_fs_t *fs,
final_path = svn_fs_fs__path_min_unpacked_rev(fs, scratch_pool);
- SVN_ERR(svn_io_write_atomic(final_path, buf, len + 1,
- final_path /* copy_perms */, scratch_pool));
+ SVN_ERR(svn_io_write_atomic2(final_path, buf, len + 1,
+ final_path /* copy_perms */,
+ ffd->flush_to_disk, scratch_pool));
return SVN_NO_ERROR;
}
@@ -517,8 +519,9 @@ svn_fs_fs__write_current(svn_fs_t *fs,
}
name = svn_fs_fs__path_current(fs, pool);
- SVN_ERR(svn_io_write_atomic(name, buf, strlen(buf),
- name /* copy_perms_path */, pool));
+ SVN_ERR(svn_io_write_atomic2(name, buf, strlen(buf),
+ name /* copy_perms_path */,
+ ffd->flush_to_disk, pool));
return SVN_NO_ERROR;
}
@@ -565,22 +568,6 @@ svn_fs_fs__try_stringbuf_from_file(svn_stringbuf_t **content,
}
svn_error_t *
-svn_fs_fs__get_file_offset(apr_off_t *offset_p,
- apr_file_t *file,
- apr_pool_t *pool)
-{
- apr_off_t offset;
-
- /* Note that, for buffered files, one (possibly surprising) side-effect
- of this call is to flush any unwritten data to disk. */
- offset = 0;
- SVN_ERR(svn_io_file_seek(file, APR_CUR, &offset, pool));
- *offset_p = offset;
-
- return SVN_NO_ERROR;
-}
-
-svn_error_t *
svn_fs_fs__read_content(svn_stringbuf_t **content,
const char *fname,
apr_pool_t *pool)
@@ -634,54 +621,54 @@ svn_error_t *
svn_fs_fs__move_into_place(const char *old_filename,
const char *new_filename,
const char *perms_reference,
+ svn_boolean_t flush_to_disk,
apr_pool_t *pool)
{
svn_error_t *err;
+ apr_file_t *file;
+ /* Copying permissions is a no-op on WIN32. */
SVN_ERR(svn_io_copy_perms(perms_reference, old_filename, pool));
/* Move the file into place. */
- err = svn_io_file_rename(old_filename, new_filename, pool);
+ err = svn_io_file_rename2(old_filename, new_filename, flush_to_disk, pool);
if (err && APR_STATUS_IS_EXDEV(err->apr_err))
{
- apr_file_t *file;
-
/* Can't rename across devices; fall back to copying. */
svn_error_clear(err);
- err = SVN_NO_ERROR;
SVN_ERR(svn_io_copy_file(old_filename, new_filename, TRUE, pool));
- /* Flush the target of the copy to disk. */
- SVN_ERR(svn_io_file_open(&file, new_filename, APR_READ,
- APR_OS_DEFAULT, pool));
- /* ### BH: Does this really guarantee a flush of the data written
- ### via a completely different handle on all operating systems?
- ###
- ### Maybe we should perform the copy ourselves instead of making
- ### apr do that and flush the real handle? */
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
- if (err)
- return svn_error_trace(err);
+ /* Flush the target of the copy to disk.
+ ### The code below is duplicates svn_io_file_rename2(), because
+ currently we don't have the svn_io_copy_file2() function with
+ a flush_to_disk argument. */
+ if (flush_to_disk)
+ {
+ SVN_ERR(svn_io_file_open(&file, new_filename, APR_WRITE,
+ APR_OS_DEFAULT, pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
-#ifdef __linux__
- {
- /* Linux has the unusual feature that fsync() on a file is not
- enough to ensure that a file's directory entries have been
- flushed to disk; you have to fsync the directory as well.
- On other operating systems, we'd only be asking for trouble
- by trying to open and fsync a directory. */
- const char *dirname;
- apr_file_t *file;
-
- dirname = svn_dirent_dirname(new_filename, pool);
- SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
- pool));
- SVN_ERR(svn_io_file_flush_to_disk(file, pool));
- SVN_ERR(svn_io_file_close(file, pool));
- }
+#ifdef SVN_ON_POSIX
+ if (flush_to_disk)
+ {
+ /* On POSIX, the file name is stored in the file's directory entry.
+ Hence, we need to fsync() that directory as well.
+ On other operating systems, we'd only be asking for trouble
+ by trying to open and fsync a directory. */
+ const char *dirname;
+
+ dirname = svn_dirent_dirname(new_filename, pool);
+ SVN_ERR(svn_io_file_open(&file, dirname, APR_READ, APR_OS_DEFAULT,
+ pool));
+ SVN_ERR(svn_io_file_flush_to_disk(file, pool));
+ SVN_ERR(svn_io_file_close(file, pool));
+ }
#endif
+ }
+ else if (err)
+ return svn_error_trace(err);
return SVN_NO_ERROR;
}
diff --git a/subversion/libsvn_fs_fs/util.h b/subversion/libsvn_fs_fs/util.h
index 328dfbcff3fd2..2d42f97b114e0 100644
--- a/subversion/libsvn_fs_fs/util.h
+++ b/subversion/libsvn_fs_fs/util.h
@@ -363,12 +363,6 @@ svn_fs_fs__try_stringbuf_from_file(svn_stringbuf_t **content,
svn_boolean_t last_attempt,
apr_pool_t *pool);
-/* Fetch the current offset of FILE into *OFFSET_P. */
-svn_error_t *
-svn_fs_fs__get_file_offset(apr_off_t *offset_p,
- apr_file_t *file,
- apr_pool_t *pool);
-
/* Read the file FNAME and store the contents in *BUF.
Allocations are performed in POOL. */
svn_error_t *
@@ -394,11 +388,12 @@ svn_fs_fs__read_number_from_stream(apr_int64_t *result,
PERMS_REFERENCE. Temporary allocations are from POOL.
This function almost duplicates svn_io_file_move(), but it tries to
- guarantee a flush. */
+ guarantee a flush if FLUSH_TO_DISK is non-zero. */
svn_error_t *
svn_fs_fs__move_into_place(const char *old_filename,
const char *new_filename,
const char *perms_reference,
+ svn_boolean_t flush_to_disk,
apr_pool_t *pool);
/* Return TRUE, iff FS uses logical addressing. */
diff --git a/subversion/libsvn_fs_fs/verify.c b/subversion/libsvn_fs_fs/verify.c
index 0fa314bfddf45..7fe5ecbbd4941 100644
--- a/subversion/libsvn_fs_fs/verify.c
+++ b/subversion/libsvn_fs_fs/verify.c
@@ -30,6 +30,7 @@
#include "cached_data.h"
#include "rep-cache.h"
+#include "revprops.h"
#include "util.h"
#include "index.h"
@@ -463,7 +464,8 @@ expect_buffer_nul(apr_file_t *file,
/* read the whole data block; error out on failure */
data.chunks[(size - 1)/ sizeof(apr_uint64_t)] = 0;
- SVN_ERR(svn_io_file_read_full2(file, data.buffer, size, NULL, NULL, pool));
+ SVN_ERR(svn_io_file_read_full2(file, data.buffer, (apr_size_t)size, NULL,
+ NULL, pool));
/* chunky check */
for (i = 0; i < size / sizeof(apr_uint64_t); ++i)
@@ -478,7 +480,7 @@ expect_buffer_nul(apr_file_t *file,
apr_off_t offset;
SVN_ERR(svn_io_file_name_get(&file_name, file, pool));
- SVN_ERR(svn_fs_fs__get_file_offset(&offset, file, pool));
+ SVN_ERR(svn_io_file_get_offset(&offset, file, pool));
offset -= size - i;
return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
@@ -671,15 +673,45 @@ compare_p2l_to_rev(svn_fs_t *fs,
apr_off_t_toa(pool, offset),
apr_off_t_toa(pool, entry->offset));
- /* empty sections must contain NUL bytes only */
+ /* Check type <-> item dependencies. */
+
+ /* Entry types must be within the valid range. */
+ if (entry->type >= SVN_FS_FS__ITEM_TYPE_ANY_REP)
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION,
+ NULL,
+ _("p2l index entry for revision r%ld"
+ " at offset %s contains invalid item"
+ " type %d"),
+ start,
+ apr_off_t_toa(pool, offset),
+ entry->type);
+
+ /* There can be only one changes entry and that has a fixed type
+ * and item number. Its presence and parse-ability will be checked
+ * during later stages of the verification process. */
+ if ( (entry->type == SVN_FS_FS__ITEM_TYPE_CHANGES)
+ != (entry->item.number == SVN_FS_FS__ITEM_INDEX_CHANGES))
+ return svn_error_createf(SVN_ERR_FS_INDEX_CORRUPTION,
+ NULL,
+ _("p2l index entry for changes in"
+ " revision r%ld is item %ld of type"
+ " %d at offset %s"),
+ entry->item.revision,
+ entry->item.number,
+ entry->type,
+ apr_off_t_toa(pool, offset));
+
+ /* Check contents. */
if (entry->type == SVN_FS_FS__ITEM_TYPE_UNUSED)
{
- /* skip filler entry at the end of the p2l index */
+ /* Empty sections must contain NUL bytes only.
+ * Beware of the filler at the end of the p2l index. */
if (entry->offset != max_offset)
SVN_ERR(read_all_nul(rev_file->file, entry->size, pool));
}
else
{
+ /* Generic contents check against checksum. */
if (entry->size < STREAM_THRESHOLD)
SVN_ERR(expected_buffered_checksum(rev_file->file, entry,
pool));
@@ -720,6 +752,10 @@ verify_revprops(svn_fs_t *fs,
svn_revnum_t revision;
apr_pool_t *iterpool = svn_pool_create(pool);
+ /* Invalidate the revprop cache once.
+ * Use the cache inside the loop to speed up packed revprop access. */
+ svn_fs_fs__reset_revprop_cache(fs);
+
for (revision = start; revision < end; ++revision)
{
svn_string_t *date;
@@ -730,7 +766,8 @@ verify_revprops(svn_fs_t *fs,
/* Access the svn:date revprop.
* This implies parsing all revprops for that revision. */
SVN_ERR(svn_fs_fs__revision_prop(&date, fs, revision,
- SVN_PROP_REVISION_DATE, iterpool));
+ SVN_PROP_REVISION_DATE, FALSE,
+ iterpool, iterpool));
/* The time stamp is the only revprop that, if given, needs to
* have a valid content. */
@@ -857,13 +894,15 @@ svn_fs_fs__verify(svn_fs_t *fs,
apr_pool_t *pool)
{
fs_fs_data_t *ffd = fs->fsap_data;
- svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
/* Input validation. */
if (! SVN_IS_VALID_REVNUM(start))
start = 0;
if (! SVN_IS_VALID_REVNUM(end))
- end = youngest;
+ {
+ SVN_ERR(svn_fs_fs__youngest_rev(&end, fs, pool));
+ }
+
SVN_ERR(svn_fs_fs__ensure_revision_exists(start, fs, pool));
SVN_ERR(svn_fs_fs__ensure_revision_exists(end, fs, pool));