subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From stef...@apache.org
Subject svn commit: r1425472 [3/6] - /subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/
Date Sun, 23 Dec 2012 15:17:47 GMT
Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.h?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.h (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/fs_fs.h Sun Dec 23 15:17:47 2012
@@ -46,48 +46,23 @@ svn_error_t *svn_fs_fs__verify(svn_fs_t 
                                svn_revnum_t end,
                                apr_pool_t *pool);
 
-/* 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. Use POOL for temporary allocations. */
-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_cancel_func_t cancel_func,
-                                 void *cancel_baton,
-                                 apr_pool_t *pool);
-
-/* Recover the fsfs associated with filesystem FS.
-   Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
-   Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__recover(svn_fs_t *fs,
-                                svn_cancel_func_t cancel_func,
-                                void *cancel_baton,
-                                apr_pool_t *pool);
-
-/* Set *NODEREV_P to the node-revision for the node ID in FS.  Do any
-   allocations in POOL. */
-svn_error_t *svn_fs_fs__get_node_revision(node_revision_t **noderev_p,
-                                          svn_fs_t *fs,
-                                          const svn_fs_id_t *id,
-                                          apr_pool_t *pool);
-
-/* Store NODEREV as the node-revision for the node whose id is ID in
-   FS, after setting its is_fresh_txn_root to FRESH_TXN_ROOT.  Do any
-   necessary temporary allocation in POOL. */
-svn_error_t *svn_fs_fs__put_node_revision(svn_fs_t *fs,
-                                          const svn_fs_id_t *id,
-                                          node_revision_t *noderev,
-                                          svn_boolean_t fresh_txn_root,
-                                          apr_pool_t *pool);
-
 /* Set *YOUNGEST to the youngest revision in filesystem FS.  Do any
    temporary allocation in POOL. */
 svn_error_t *svn_fs_fs__youngest_rev(svn_revnum_t *youngest,
                                      svn_fs_t *fs,
                                      apr_pool_t *pool);
 
+/* For revision REV in fileysystem FS, open the revision (or packed rev)
+   file and seek to the start of the revision.  Return it in *FILE, and
+   use POOL for allocations. */
+svn_error_t *
+svn_fs_fs__open_pack_or_rev_file(apr_file_t **file,
+                                 svn_fs_t *fs,
+                                 svn_revnum_t rev,
+                                 apr_pool_t *pool);
+
+/* Assert that revision REV exists in fileysystem FS.  If it doesn't, return
+   an error.  Use POOL for allocations. */
 svn_error_t *
 svn_fs_fs__ensure_revision_exists(svn_revnum_t rev,
                                   svn_fs_t *fs,
@@ -99,73 +74,6 @@ svn_fs_fs__revision_exists(svn_revnum_t 
                            svn_fs_t *fs,
                            apr_pool_t *pool);
 
-/* Set *ROOT_ID to the node-id for the root of revision REV in
-   filesystem FS.  Do any allocations in POOL. */
-svn_error_t *svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,
-                                     svn_fs_t *fs,
-                                     svn_revnum_t rev,
-                                     apr_pool_t *pool);
-
-/* Set *ENTRIES to an apr_hash_t of dirent structs that contain the
-   directory entries of node-revision NODEREV in filesystem FS.  The
-   returned table (and its keys and values) is allocated in POOL,
-   which is also used for temporary allocations. */
-svn_error_t *svn_fs_fs__rep_contents_dir(apr_hash_t **entries,
-                                         svn_fs_t *fs,
-                                         node_revision_t *noderev,
-                                         apr_pool_t *pool);
-
-/* Set *DIRENT to the entry identified by NAME in the directory given
-   by NODEREV in filesystem FS.  If no such entry exits, *DIRENT will
-   be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL
-   used for temporary allocations. */
-svn_error_t *
-svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
-                                  svn_fs_t *fs,
-                                  node_revision_t *noderev,
-                                  const char *name,
-                                  apr_pool_t *result_pool,
-                                  apr_pool_t *scratch_pool);
-
-/* Set *CONTENTS to be a readable svn_stream_t that receives the text
-   representation of node-revision NODEREV as seen in filesystem FS.
-   Use POOL for temporary allocations. */
-svn_error_t *svn_fs_fs__get_contents(svn_stream_t **contents,
-                                     svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     apr_pool_t *pool);
-
-/* Attempt to fetch the text representation of node-revision NODEREV as
-   seen in filesystem FS and pass it along with the BATON to the PROCESSOR.
-   Set *SUCCESS only of the data could be provided and the processing
-   had been called.
-   Use POOL for all allocations.
- */
-svn_error_t *
-svn_fs_fs__try_process_file_contents(svn_boolean_t *success,
-                                     svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     svn_fs_process_contents_func_t processor,
-                                     void* baton,
-                                     apr_pool_t *pool);
-
-/* Set *STREAM_P to a delta stream turning the contents of the file SOURCE into
-   the contents of the file TARGET, allocated in POOL.
-   If SOURCE is null, the empty string will be used. */
-svn_error_t *svn_fs_fs__get_file_delta_stream(svn_txdelta_stream_t **stream_p,
-                                              svn_fs_t *fs,
-                                              node_revision_t *source,
-                                              node_revision_t *target,
-                                              apr_pool_t *pool);
-
-/* Set *PROPLIST to be an apr_hash_t containing the property list of
-   node-revision NODEREV as seen in filesystem FS.  Use POOL for
-   temporary allocations. */
-svn_error_t *svn_fs_fs__get_proplist(apr_hash_t **proplist,
-                                     svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     apr_pool_t *pool);
-
 /* Set *PROPLIST to be an apr_hash_t containing the property list of
    revision REV as seen in filesystem FS.  Use POOL for temporary
    allocations. */
@@ -199,153 +107,9 @@ svn_error_t *svn_fs_fs__file_checksum(sv
                                       svn_checksum_kind_t kind,
                                       apr_pool_t *pool);
 
-/* Find the paths which were changed in revision REV of filesystem FS
-   and store them in *CHANGED_PATHS_P.  Cached copyfrom information
-   will be stored in *COPYFROM_CACHE.  Get any temporary allocations
-   from POOL. */
-svn_error_t *svn_fs_fs__paths_changed(apr_hash_t **changed_paths_p,
-                                      svn_fs_t *fs,
-                                      svn_revnum_t rev,
-                                      apr_hash_t *copyfrom_cache,
-                                      apr_pool_t *pool);
-
-/* Create a new transaction in filesystem FS, based on revision REV,
-   and store it in *TXN_P.  Allocate all necessary variables from
-   POOL. */
-svn_error_t *svn_fs_fs__create_txn(svn_fs_txn_t **txn_p,
-                                   svn_fs_t *fs,
-                                   svn_revnum_t rev,
-                                   apr_pool_t *pool);
-
-/* Set the transaction property NAME to the value VALUE in transaction
-   TXN.  Perform temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__change_txn_prop(svn_fs_txn_t *txn,
-                                        const char *name,
-                                        const svn_string_t *value,
-                                        apr_pool_t *pool);
-
-/* Change transaction properties in transaction TXN based on PROPS.
-   Perform temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__change_txn_props(svn_fs_txn_t *txn,
-                                         const apr_array_header_t *props,
-                                         apr_pool_t *pool);
-
 /* Return whether or not the given FS supports mergeinfo metadata. */
 svn_boolean_t svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs);
 
-/* Store a transaction record in *TXN_P for the transaction identified
-   by TXN_ID in filesystem FS.  Allocate everything from POOL. */
-svn_error_t *svn_fs_fs__get_txn(transaction_t **txn_p,
-                                svn_fs_t *fs,
-                                const char *txn_id,
-                                apr_pool_t *pool);
-
-/* Abort the existing transaction TXN, performing any temporary
-   allocations in POOL. */
-svn_error_t *svn_fs_fs__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool);
-
-/* Create an entirely new mutable node in the filesystem FS, whose
-   node-revision is NODEREV.  Set *ID_P to the new node revision's ID.
-   Use POOL for any temporary allocation.  COPY_ID is the copy_id to
-   use in the node revision ID.  TXN_ID is the Subversion transaction
-   under which this occurs. */
-svn_error_t *svn_fs_fs__create_node(const svn_fs_id_t **id_p,
-                                    svn_fs_t *fs,
-                                    node_revision_t *noderev,
-                                    const char *copy_id,
-                                    const char *txn_id,
-                                    apr_pool_t *pool);
-
-/* Remove all references to the transaction TXN_ID from filesystem FS.
-   Temporary allocations are from POOL. */
-svn_error_t *svn_fs_fs__purge_txn(svn_fs_t *fs,
-                                  const char *txn_id,
-                                  apr_pool_t *pool);
-
-/* Add or set in filesystem FS, transaction TXN_ID, in directory
-   PARENT_NODEREV a directory entry for NAME pointing to ID of type
-   KIND.  Allocations are done in POOL. */
-svn_error_t *svn_fs_fs__set_entry(svn_fs_t *fs,
-                                  const char *txn_id,
-                                  node_revision_t *parent_noderev,
-                                  const char *name,
-                                  const svn_fs_id_t *id,
-                                  svn_node_kind_t kind,
-                                  apr_pool_t *pool);
-
-/* Add a change to the changes record for filesystem FS in transaction
-   TXN_ID.  Mark path PATH, having node-id ID, as changed according to
-   the type in CHANGE_KIND.  If the text representation was changed
-   set TEXT_MOD to TRUE, and likewise for PROP_MOD.  If this change
-   was the result of a copy, set COPYFROM_REV and COPYFROM_PATH to the
-   revision and path of the copy source, otherwise they should be set
-   to SVN_INVALID_REVNUM and NULL.  Perform any temporary allocations
-   from POOL. */
-svn_error_t *svn_fs_fs__add_change(svn_fs_t *fs,
-                                   const char *txn_id,
-                                   const char *path,
-                                   const svn_fs_id_t *id,
-                                   svn_fs_path_change_kind_t change_kind,
-                                   svn_boolean_t text_mod,
-                                   svn_boolean_t prop_mod,
-                                   svn_node_kind_t node_kind,
-                                   svn_revnum_t copyfrom_rev,
-                                   const char *copyfrom_path,
-                                   apr_pool_t *pool);
-
-/* Return a writable stream in *STREAM that allows storing the text
-   representation of node-revision NODEREV in filesystem FS.
-   Allocations are from POOL. */
-svn_error_t *svn_fs_fs__set_contents(svn_stream_t **stream,
-                                     svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     apr_pool_t *pool);
-
-/* Create a node revision in FS which is an immediate successor of
-   OLD_ID, whose contents are NEW_NR.  Set *NEW_ID_P to the new node
-   revision's ID.  Use POOL for any temporary allocation.
-
-   COPY_ID, if non-NULL, is a key into the `copies' table, and
-   indicates that this new node is being created as the result of a
-   copy operation, and specifically which operation that was.  If
-   COPY_ID is NULL, then re-use the copy ID from the predecessor node.
-
-   TXN_ID is the Subversion transaction under which this occurs.
-
-   After this call, the deltification code assumes that the new node's
-   contents will change frequently, and will avoid representing other
-   nodes as deltas against this node's contents.  */
-svn_error_t *svn_fs_fs__create_successor(const svn_fs_id_t **new_id_p,
-                                         svn_fs_t *fs,
-                                         const svn_fs_id_t *old_idp,
-                                         node_revision_t *new_noderev,
-                                         const char *copy_id,
-                                         const char *txn_id,
-                                         apr_pool_t *pool);
-
-/* Write a new property list PROPLIST for node-revision NODEREV in
-   filesystem FS.  Perform any temporary allocations in POOL. */
-svn_error_t *svn_fs_fs__set_proplist(svn_fs_t *fs,
-                                     node_revision_t *noderev,
-                                     apr_hash_t *proplist,
-                                     apr_pool_t *pool);
-
-/* Commit the transaction TXN in filesystem FS and return its new
-   revision number in *REV.  If the transaction is out of date, return
-   the error SVN_ERR_FS_TXN_OUT_OF_DATE.  Use POOL for temporary
-   allocations. */
-svn_error_t *svn_fs_fs__commit(svn_revnum_t *new_rev_p,
-                               svn_fs_t *fs,
-                               svn_fs_txn_t *txn,
-                               apr_pool_t *pool);
-
-/* Return the next available copy_id in *COPY_ID for the transaction
-   TXN_ID in filesystem FS.  Allocate space in POOL. */
-svn_error_t *svn_fs_fs__reserve_copy_id(const char **copy_id,
-                                        svn_fs_t *fs,
-                                        const char *txn_id,
-                                        apr_pool_t *pool);
-
 /* Create a fs_fs fileysystem referenced by FS at path PATH.  Get any
    temporary allocations from POOL.
 
@@ -362,44 +126,6 @@ svn_error_t *svn_fs_fs__set_uuid(svn_fs_
                                  const char *uuid,
                                  apr_pool_t *pool);
 
-/* Set *NAMES_P to an array of names which are all the active
-   transactions in filesystem FS.  Allocate the array from POOL. */
-svn_error_t *svn_fs_fs__list_transactions(apr_array_header_t **names_p,
-                                          svn_fs_t *fs,
-                                          apr_pool_t *pool);
-
-/* Open the transaction named NAME in filesystem FS.  Set *TXN_P to
- * the transaction. If there is no such transaction, return
-` * SVN_ERR_FS_NO_SUCH_TRANSACTION.  Allocate the new transaction in
- * POOL. */
-svn_error_t *svn_fs_fs__open_txn(svn_fs_txn_t **txn_p,
-                                 svn_fs_t *fs,
-                                 const char *name,
-                                 apr_pool_t *pool);
-
-/* Return the property list from transaction TXN and store it in
-   *PROPLIST.  Allocate the property list from POOL. */
-svn_error_t *svn_fs_fs__txn_proplist(apr_hash_t **proplist,
-                                     svn_fs_txn_t *txn,
-                                     apr_pool_t *pool);
-
-/* Delete the mutable node-revision referenced by ID, along with any
-   mutable props or directory contents associated with it.  Perform
-   temporary allocations in POOL. */
-svn_error_t *svn_fs_fs__delete_node_revision(svn_fs_t *fs,
-                                             const svn_fs_id_t *id,
-                                             apr_pool_t *pool);
-
-
-/* Find the paths which were changed in transaction TXN_ID of
-   filesystem FS and store them in *CHANGED_PATHS_P.
-   Get any temporary allocations from POOL. */
-svn_error_t *svn_fs_fs__txn_changes_fetch(apr_hash_t **changes,
-                                          svn_fs_t *fs,
-                                          const char *txn_id,
-                                          apr_pool_t *pool);
-
-
 /* Set *PATH to the path of REV in FS, whether in a pack file or not.
    Allocate *PATH in POOL.
 
@@ -419,15 +145,16 @@ svn_fs_fs__path_rev_absolute(const char 
 const char *
 svn_fs_fs__path_current(svn_fs_t *fs, apr_pool_t *pool);
 
-/* Obtain a write lock on the filesystem FS in a subpool of POOL, call
-   BODY with BATON and that subpool, destroy the subpool (releasing the write
-   lock) and return what BODY returned. */
+/* Read the format number and maximum number of files per directory
+   from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
+   respectively.
+
+   *MAX_FILES_PER_DIR is obtained from the 'layout' format option, and
+   will be set to zero if a linear scheme should be used.
+
+   Use POOL for temporary allocation. */
 svn_error_t *
-svn_fs_fs__with_write_lock(svn_fs_t *fs,
-                           svn_error_t *(*body)(void *baton,
-                                                apr_pool_t *pool),
-                           void *baton,
-                           apr_pool_t *pool);
+svn_fs_fs__write_format(svn_fs_t *fs, apr_pool_t *pool);
 
 /* Find the value of the property named PROPNAME in transaction TXN.
    Return the contents in *VALUE_P.  The contents will be allocated
@@ -448,38 +175,6 @@ svn_error_t *svn_fs_fs__change_rev_prop(
                                         const svn_string_t *value,
                                         apr_pool_t *pool);
 
-/* Retrieve information about the Subversion transaction SVN_TXN from
-   the `transactions' table of FS, allocating from POOL.  Set
-   *ROOT_ID_P to the ID of the transaction's root directory.  Set
-   *BASE_ROOT_ID_P to the ID of the root directory of the
-   transaction's base revision.
-
-   If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
-   the error returned.
-
-   Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
-   transaction that has already been committed.
-
-   Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL.  */
-svn_error_t *svn_fs_fs__get_txn_ids(const svn_fs_id_t **root_id_p,
-                                    const svn_fs_id_t **base_root_id_p,
-                                    svn_fs_t *fs,
-                                    const char *txn_name,
-                                    apr_pool_t *pool);
-
-/* Begin a new transaction in filesystem FS, based on existing
-   revision REV.  The new transaction is returned in *TXN_P.  Allocate
-   the new transaction structure from POOL. */
-svn_error_t *svn_fs_fs__begin_txn(svn_fs_txn_t **txn_p, svn_fs_t *fs,
-                                  svn_revnum_t rev, apr_uint32_t flags,
-                                  apr_pool_t *pool);
-
-/* Find the value of the property named PROPNAME in transaction TXN.
-   Return the contents in *VALUE_P.  The contents will be allocated
-   from POOL. */
-svn_error_t *svn_fs_fs__txn_prop(svn_string_t **value_p, svn_fs_txn_t *txn,
-                                 const char *propname, apr_pool_t *pool);
-
 /* If directory PATH does not exist, create it and give it the same
    permissions as FS_PATH.*/
 svn_error_t *svn_fs_fs__ensure_dir_exists(const char *path,
@@ -537,18 +232,4 @@ svn_fs_fs__initialize_txn_caches(svn_fs_
 void
 svn_fs_fs__reset_txn_caches(svn_fs_t *fs);
 
-/* Possibly pack the repository at PATH.  This just take full shards, and
-   combines all the revision files into a single one, with a manifest header.
-   Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
-
-   Existing filesystem references need not change.  */
-svn_error_t *
-svn_fs_fs__pack(svn_fs_t *fs,
-                svn_fs_pack_notify_t notify_func,
-                void *notify_baton,
-                svn_cancel_func_t cancel_func,
-                void *cancel_baton,
-                apr_pool_t *pool);
-
-
 #endif

Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.c?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.c (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.c Sun Dec 23 15:17:47 2012
@@ -0,0 +1,998 @@
+/* hotcopys.c --- FS hotcopy functionality for FSFS
+ *
+ * ====================================================================
+ *    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.
+ * ====================================================================
+ */
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "svn_dirent_uri.h"
+
+#include "fs_fs.h"
+#include "hotcopy.h"
+#include "util.h"
+#include "revprops.h"
+#include "rep-cache.h"
+#include "key-gen.h"
+#include "transaction.h"
+#include "recovery.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+/* Like svn_io_dir_file_copy(), but doesn't copy files that exist at
+ * the destination and do not differ in terms of kind, size, and mtime. */
+static svn_error_t *
+hotcopy_io_dir_file_copy(const char *src_path,
+                         const char *dst_path,
+                         const char *file,
+                         apr_pool_t *scratch_pool)
+{
+  const svn_io_dirent2_t *src_dirent;
+  const svn_io_dirent2_t *dst_dirent;
+  const char *src_target;
+  const char *dst_target;
+
+  /* Does the destination already exist? If not, we must copy it. */
+  dst_target = svn_dirent_join(dst_path, file, scratch_pool);
+  SVN_ERR(svn_io_stat_dirent(&dst_dirent, dst_target, TRUE,
+                             scratch_pool, scratch_pool));
+  if (dst_dirent->kind != svn_node_none)
+    {
+      /* If the destination's stat information indicates that the file
+       * is equal to the source, don't bother copying the file again. */
+      src_target = svn_dirent_join(src_path, file, scratch_pool);
+      SVN_ERR(svn_io_stat_dirent(&src_dirent, src_target, FALSE,
+                                 scratch_pool, scratch_pool));
+      if (src_dirent->kind == dst_dirent->kind &&
+          src_dirent->special == dst_dirent->special &&
+          src_dirent->filesize == dst_dirent->filesize &&
+          src_dirent->mtime <= dst_dirent->mtime)
+        return SVN_NO_ERROR;
+    }
+
+  return svn_error_trace(svn_io_dir_file_copy(src_path, dst_path, file,
+                                              scratch_pool));
+}
+
+/* Set *NAME_P to the UTF-8 representation of directory entry NAME.
+ * NAME is in the internal encoding used by APR; PARENT is in
+ * UTF-8 and in internal (not local) style.
+ *
+ * Use PARENT only for generating an error string if the conversion
+ * fails because NAME could not be represented in UTF-8.  In that
+ * case, return a two-level error in which the outer error's message
+ * mentions PARENT, but the inner error's message does not mention
+ * NAME (except possibly in hex) since NAME may not be printable.
+ * Such a compound error at least allows the user to go looking in the
+ * right directory for the problem.
+ *
+ * If there is any other error, just return that error directly.
+ *
+ * If there is any error, the effect on *NAME_P is undefined.
+ *
+ * *NAME_P and NAME may refer to the same storage.
+ */
+static svn_error_t *
+entry_name_to_utf8(const char **name_p,
+                   const char *name,
+                   const char *parent,
+                   apr_pool_t *pool)
+{
+  svn_error_t *err = svn_path_cstring_to_utf8(name_p, name, pool);
+  if (err && err->apr_err == APR_EINVAL)
+    {
+      return svn_error_createf(err->apr_err, err,
+                               _("Error converting entry "
+                                 "in directory '%s' to UTF-8"),
+                               svn_dirent_local_style(parent, pool));
+    }
+  return err;
+}
+
+/* Like svn_io_copy_dir_recursively() but doesn't copy regular files that
+ * exist in the destination and do not differ from the source in terms of
+ * kind, size, and mtime. */
+static svn_error_t *
+hotcopy_io_copy_dir_recursively(const char *src,
+                                const char *dst_parent,
+                                const char *dst_basename,
+                                svn_boolean_t copy_perms,
+                                svn_cancel_func_t cancel_func,
+                                void *cancel_baton,
+                                apr_pool_t *pool)
+{
+  svn_node_kind_t kind;
+  apr_status_t status;
+  const char *dst_path;
+  apr_dir_t *this_dir;
+  apr_finfo_t this_entry;
+  apr_int32_t flags = APR_FINFO_TYPE | APR_FINFO_NAME;
+
+  /* Make a subpool for recursion */
+  apr_pool_t *subpool = svn_pool_create(pool);
+
+  /* The 'dst_path' is simply dst_parent/dst_basename */
+  dst_path = svn_dirent_join(dst_parent, dst_basename, pool);
+
+  /* Sanity checks:  SRC and DST_PARENT are directories, and
+     DST_BASENAME doesn't already exist in DST_PARENT. */
+  SVN_ERR(svn_io_check_path(src, &kind, subpool));
+  if (kind != svn_node_dir)
+    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+                             _("Source '%s' is not a directory"),
+                             svn_dirent_local_style(src, pool));
+
+  SVN_ERR(svn_io_check_path(dst_parent, &kind, subpool));
+  if (kind != svn_node_dir)
+    return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
+                             _("Destination '%s' is not a directory"),
+                             svn_dirent_local_style(dst_parent, pool));
+
+  SVN_ERR(svn_io_check_path(dst_path, &kind, subpool));
+
+  /* Create the new directory. */
+  /* ### TODO: copy permissions (needs apr_file_attrs_get()) */
+  SVN_ERR(svn_io_make_dir_recursively(dst_path, pool));
+
+  /* Loop over the dirents in SRC.  ('.' and '..' are auto-excluded) */
+  SVN_ERR(svn_io_dir_open(&this_dir, src, subpool));
+
+  for (status = apr_dir_read(&this_entry, flags, this_dir);
+       status == APR_SUCCESS;
+       status = apr_dir_read(&this_entry, flags, this_dir))
+    {
+      if ((this_entry.name[0] == '.')
+          && ((this_entry.name[1] == '\0')
+              || ((this_entry.name[1] == '.')
+                  && (this_entry.name[2] == '\0'))))
+        {
+          continue;
+        }
+      else
+        {
+          const char *entryname_utf8;
+
+          if (cancel_func)
+            SVN_ERR(cancel_func(cancel_baton));
+
+          SVN_ERR(entry_name_to_utf8(&entryname_utf8, this_entry.name,
+                                     src, subpool));
+          if (this_entry.filetype == APR_REG) /* regular file */
+            {
+              SVN_ERR(hotcopy_io_dir_file_copy(src, dst_path, entryname_utf8,
+                                               subpool));
+            }
+          else if (this_entry.filetype == APR_LNK) /* symlink */
+            {
+              const char *src_target = svn_dirent_join(src, entryname_utf8,
+                                                       subpool);
+              const char *dst_target = svn_dirent_join(dst_path,
+                                                       entryname_utf8,
+                                                       subpool);
+              SVN_ERR(svn_io_copy_link(src_target, dst_target,
+                                       subpool));
+            }
+          else if (this_entry.filetype == APR_DIR) /* recurse */
+            {
+              const char *src_target;
+
+              /* Prevent infinite recursion by filtering off our
+                 newly created destination path. */
+              if (strcmp(src, dst_parent) == 0
+                  && strcmp(entryname_utf8, dst_basename) == 0)
+                continue;
+
+              src_target = svn_dirent_join(src, entryname_utf8, subpool);
+              SVN_ERR(hotcopy_io_copy_dir_recursively(src_target,
+                                                      dst_path,
+                                                      entryname_utf8,
+                                                      copy_perms,
+                                                      cancel_func,
+                                                      cancel_baton,
+                                                      subpool));
+            }
+          /* ### support other APR node types someday?? */
+
+        }
+    }
+
+  if (! (APR_STATUS_IS_ENOENT(status)))
+    return svn_error_wrap_apr(status, _("Can't read directory '%s'"),
+                              svn_dirent_local_style(src, pool));
+
+  status = apr_dir_close(this_dir);
+  if (status)
+    return svn_error_wrap_apr(status, _("Error closing directory '%s'"),
+                              svn_dirent_local_style(src, pool));
+
+  /* Free any memory used by recursion */
+  svn_pool_destroy(subpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Copy an un-packed revision or revprop file for revision REV from SRC_SUBDIR
+ * to DST_SUBDIR. Assume a sharding layout based on MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_shard_file(const char *src_subdir,
+                        const char *dst_subdir,
+                        svn_revnum_t rev,
+                        int max_files_per_dir,
+                        apr_pool_t *scratch_pool)
+{
+  const char *src_subdir_shard = src_subdir,
+             *dst_subdir_shard = dst_subdir;
+
+  if (max_files_per_dir)
+    {
+      const char *shard = apr_psprintf(scratch_pool, "%ld",
+                                       rev / max_files_per_dir);
+      src_subdir_shard = svn_dirent_join(src_subdir, shard, scratch_pool);
+      dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+      if (rev % max_files_per_dir == 0)
+        {
+          SVN_ERR(svn_io_make_dir_recursively(dst_subdir_shard, scratch_pool));
+          SVN_ERR(svn_io_copy_perms(dst_subdir, dst_subdir_shard,
+                                    scratch_pool));
+        }
+    }
+
+  SVN_ERR(hotcopy_io_dir_file_copy(src_subdir_shard, dst_subdir_shard,
+                                   apr_psprintf(scratch_pool, "%ld", rev),
+                                   scratch_pool));
+  return SVN_NO_ERROR;
+}
+
+
+/* Copy a packed shard containing revision REV, and which contains
+ * MAX_FILES_PER_DIR revisions, from SRC_FS to DST_FS.
+ * Update *DST_MIN_UNPACKED_REV in case the shard is new in DST_FS.
+ * Do not re-copy data which already exists in DST_FS.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_copy_packed_shard(svn_revnum_t *dst_min_unpacked_rev,
+                          svn_fs_t *src_fs,
+                          svn_fs_t *dst_fs,
+                          svn_revnum_t rev,
+                          int max_files_per_dir,
+                          apr_pool_t *scratch_pool)
+{
+  const char *src_subdir;
+  const char *dst_subdir;
+  const char *packed_shard;
+  const char *src_subdir_packed_shard;
+  svn_revnum_t revprop_rev;
+  apr_pool_t *iterpool;
+  fs_fs_data_t *src_ffd = src_fs->fsap_data;
+
+  /* Copy the packed shard. */
+  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, scratch_pool);
+  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
+  packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+                              rev / max_files_per_dir);
+  src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+                                            scratch_pool);
+  SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
+                                          dst_subdir, packed_shard,
+                                          TRUE /* copy_perms */,
+                                          NULL /* cancel_func */, NULL,
+                                          scratch_pool));
+
+  /* Copy revprops belonging to revisions in this pack. */
+  src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, scratch_pool);
+
+  if (   src_ffd->format < SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT
+      || src_ffd->min_unpacked_rev < rev + max_files_per_dir)
+    {
+      /* copy unpacked revprops rev by rev */
+      iterpool = svn_pool_create(scratch_pool);
+      for (revprop_rev = rev;
+           revprop_rev < rev + max_files_per_dir;
+           revprop_rev++)
+        {
+          svn_pool_clear(iterpool);
+
+          SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+                                          revprop_rev, max_files_per_dir,
+                                          iterpool));
+        }
+      svn_pool_destroy(iterpool);
+    }
+  else
+    {
+      /* revprop for revision 0 will never be packed */
+      if (rev == 0)
+        SVN_ERR(hotcopy_copy_shard_file(src_subdir, dst_subdir,
+                                        0, max_files_per_dir,
+                                        scratch_pool));
+
+      /* packed revprops folder */
+      packed_shard = apr_psprintf(scratch_pool, "%ld" PATH_EXT_PACKED_SHARD,
+                                  rev / max_files_per_dir);
+      src_subdir_packed_shard = svn_dirent_join(src_subdir, packed_shard,
+                                                scratch_pool);
+      SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir_packed_shard,
+                                              dst_subdir, packed_shard,
+                                              TRUE /* copy_perms */,
+                                              NULL /* cancel_func */, NULL,
+                                              scratch_pool));
+    }
+
+  /* If necessary, update the min-unpacked rev file in the hotcopy. */
+  if (*dst_min_unpacked_rev < rev + max_files_per_dir)
+    {
+      *dst_min_unpacked_rev = rev + max_files_per_dir;
+      SVN_ERR(write_revnum_file(dst_fs,
+                                *dst_min_unpacked_rev,
+                                scratch_pool));
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* If NEW_YOUNGEST is younger than *DST_YOUNGEST, update the 'current'
+ * file in DST_FS and set *DST_YOUNGEST to NEW_YOUNGEST.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_update_current(svn_revnum_t *dst_youngest,
+                       svn_fs_t *dst_fs,
+                       svn_revnum_t new_youngest,
+                       apr_pool_t *scratch_pool)
+{
+  char next_node_id[MAX_KEY_SIZE] = "0";
+  char next_copy_id[MAX_KEY_SIZE] = "0";
+  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+  if (*dst_youngest >= new_youngest)
+    return SVN_NO_ERROR;
+
+  /* If necessary, get new current next_node and next_copy IDs. */
+  if (dst_ffd->format < SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT)
+    SVN_ERR(svn_fs_fs__find_max_ids(dst_fs, new_youngest,
+                                    next_node_id, next_copy_id,
+                                    scratch_pool));
+
+  /* Update 'current'. */
+  SVN_ERR(write_current(dst_fs, new_youngest, next_node_id, next_copy_id,
+                        scratch_pool));
+
+  *dst_youngest = new_youngest;
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Remove revisions between START_REV (inclusive) and END_REV (non-inclusive)
+ * from DST_FS. Assume sharding as per MAX_FILES_PER_DIR.
+ * Use SCRATCH_POOL for temporary allocations. */
+static svn_error_t *
+hotcopy_remove_rev_files(svn_fs_t *dst_fs,
+                         svn_revnum_t start_rev,
+                         svn_revnum_t end_rev,
+                         int max_files_per_dir,
+                         apr_pool_t *scratch_pool)
+{
+  const char *dst_subdir;
+  const char *shard;
+  const char *dst_subdir_shard;
+  svn_revnum_t rev;
+  apr_pool_t *iterpool;
+
+  SVN_ERR_ASSERT(start_rev <= end_rev);
+
+  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, scratch_pool);
+
+  /* Pre-compute paths for initial shard. */
+  shard = apr_psprintf(scratch_pool, "%ld", start_rev / max_files_per_dir);
+  dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+
+  iterpool = svn_pool_create(scratch_pool);
+  for (rev = start_rev; rev < end_rev; rev++)
+    {
+      const char *rev_path;
+
+      svn_pool_clear(iterpool);
+
+      /* If necessary, update paths for shard. */
+      if (rev != start_rev && rev % max_files_per_dir == 0)
+        {
+          shard = apr_psprintf(iterpool, "%ld", rev / max_files_per_dir);
+          dst_subdir_shard = svn_dirent_join(dst_subdir, shard, scratch_pool);
+        }
+
+      rev_path = svn_dirent_join(dst_subdir_shard,
+                                 apr_psprintf(iterpool, "%ld", rev),
+                                 iterpool);
+
+      /* Make the rev file writable and remove it. */
+      SVN_ERR(svn_io_set_file_read_write(rev_path, TRUE, iterpool));
+      SVN_ERR(svn_io_remove_file2(rev_path, TRUE, iterpool));
+    }
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Verify that DST_FS is a suitable destination for an incremental
+ * hotcopy from SRC_FS. */
+static svn_error_t *
+hotcopy_incremental_check_preconditions(svn_fs_t *src_fs,
+                                        svn_fs_t *dst_fs,
+                                        apr_pool_t *pool)
+{
+  fs_fs_data_t *src_ffd = src_fs->fsap_data;
+  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+  /* We only support incremental hotcopy between the same format. */
+  if (src_ffd->format != dst_ffd->format)
+    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+      _("The FSFS format (%d) of the hotcopy source does not match the "
+        "FSFS format (%d) of the hotcopy destination; please upgrade "
+        "both repositories to the same format"),
+      src_ffd->format, dst_ffd->format);
+
+  /* Make sure the UUID of source and destination match up.
+   * We don't want to copy over a different repository. */
+  if (strcmp(src_fs->uuid, dst_fs->uuid) != 0)
+    return svn_error_create(SVN_ERR_RA_UUID_MISMATCH, NULL,
+                            _("The UUID of the hotcopy source does "
+                              "not match the UUID of the hotcopy "
+                              "destination"));
+
+  /* Also require same shard size. */
+  if (src_ffd->max_files_per_dir != dst_ffd->max_files_per_dir)
+    return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+                            _("The sharding layout configuration "
+                              "of the hotcopy source does not match "
+                              "the sharding layout configuration of "
+                              "the hotcopy destination"));
+  return SVN_NO_ERROR;
+}
+
+
+/* Baton for hotcopy_body(). */
+struct hotcopy_body_baton {
+  svn_fs_t *src_fs;
+  svn_fs_t *dst_fs;
+  svn_boolean_t incremental;
+  svn_cancel_func_t cancel_func;
+  void *cancel_baton;
+} hotcopy_body_baton;
+
+/* Perform a hotcopy, either normal or incremental.
+ *
+ * Normal hotcopy assumes that the destination exists as an empty
+ * directory. It behaves like an incremental hotcopy except that
+ * none of the copied files already exist in the destination.
+ *
+ * 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
+ * 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.
+ *
+ * Writers are blocked out completely during the entire incremental hotcopy
+ * process to ensure consistency. This function assumes that the repository
+ * write-lock is held.
+ */
+static svn_error_t *
+hotcopy_body(void *baton, apr_pool_t *pool)
+{
+  struct hotcopy_body_baton *hbb = baton;
+  svn_fs_t *src_fs = hbb->src_fs;
+  fs_fs_data_t *src_ffd = src_fs->fsap_data;
+  svn_fs_t *dst_fs = hbb->dst_fs;
+  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+  int max_files_per_dir = src_ffd->max_files_per_dir;
+  svn_boolean_t incremental = hbb->incremental;
+  svn_cancel_func_t cancel_func = hbb->cancel_func;
+  void* cancel_baton = hbb->cancel_baton;
+  svn_revnum_t src_youngest;
+  svn_revnum_t dst_youngest;
+  svn_revnum_t rev;
+  svn_revnum_t src_min_unpacked_rev;
+  svn_revnum_t dst_min_unpacked_rev;
+  const char *src_subdir;
+  const char *dst_subdir;
+  const char *revprop_src_subdir;
+  const char *revprop_dst_subdir;
+  apr_pool_t *iterpool;
+  svn_node_kind_t kind;
+
+  /* Try to copy the config.
+   *
+   * ### We try copying the config file before doing anything else,
+   * ### because higher layers will abort the hotcopy if we throw
+   * ### an error from this function, and that renders the hotcopy
+   * ### unusable anyway. */
+  if (src_ffd->format >= SVN_FS_FS__MIN_CONFIG_FILE)
+    {
+      svn_error_t *err;
+
+      err = svn_io_dir_file_copy(src_fs->path, dst_fs->path, PATH_CONFIG,
+                                 pool);
+      if (err)
+        {
+          if (APR_STATUS_IS_ENOENT(err->apr_err))
+            {
+              /* 1.6.0 to 1.6.11 did not copy the configuration file during
+               * hotcopy. So if we're hotcopying a repository which has been
+               * created as a hotcopy itself, it's possible that fsfs.conf
+               * does not exist. Ask the user to re-create it.
+               *
+               * ### It would be nice to make this a non-fatal error,
+               * ### but this function does not get an svn_fs_t object
+               * ### so we have no way of just printing a warning via
+               * ### the fs->warning() callback. */
+
+              const char *msg;
+              const char *src_abspath;
+              const char *dst_abspath;
+              const char *config_relpath;
+              svn_error_t *err2;
+
+              config_relpath = svn_dirent_join(src_fs->path, PATH_CONFIG, pool);
+              err2 = svn_dirent_get_absolute(&src_abspath, src_fs->path, pool);
+              if (err2)
+                return svn_error_trace(svn_error_compose_create(err, err2));
+              err2 = svn_dirent_get_absolute(&dst_abspath, dst_fs->path, pool);
+              if (err2)
+                return svn_error_trace(svn_error_compose_create(err, err2));
+
+              /* ### hack: strip off the 'db/' directory from paths so
+               * ### they make sense to the user */
+              src_abspath = svn_dirent_dirname(src_abspath, pool);
+              dst_abspath = svn_dirent_dirname(dst_abspath, pool);
+
+              msg = apr_psprintf(pool,
+                                 _("Failed to create hotcopy at '%s'. "
+                                   "The file '%s' is missing from the source "
+                                   "repository. Please create this file, for "
+                                   "instance by running 'svnadmin upgrade %s'"),
+                                 dst_abspath, config_relpath, src_abspath);
+              return svn_error_quick_wrap(err, msg);
+            }
+          else
+            return svn_error_trace(err);
+        }
+    }
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  /* Find the youngest revision in the source and destination.
+   * We only support hotcopies from sources with an equal or greater amount
+   * of revisions than the destination.
+   * This also catches the case where users accidentally swap the
+   * source and destination arguments. */
+  SVN_ERR(svn_fs_fs__youngest_rev(&src_youngest, src_fs, pool));
+  if (incremental)
+    {
+      SVN_ERR(svn_fs_fs__youngest_rev(&dst_youngest, dst_fs, pool));
+      if (src_youngest < dst_youngest)
+        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+                 _("The hotcopy destination already contains more revisions "
+                   "(%lu) than the hotcopy source contains (%lu); are source "
+                   "and destination swapped?"),
+                  dst_youngest, src_youngest);
+    }
+  else
+    dst_youngest = 0;
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  /* Copy the min unpacked rev, and read its value. */
+  if (src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+    {
+      SVN_ERR(read_min_unpacked_rev(&src_min_unpacked_rev, src_fs, pool));
+      SVN_ERR(read_min_unpacked_rev(&dst_min_unpacked_rev, dst_fs, pool));
+
+      /* We only support packs coming from the hotcopy source.
+       * The destination should not be packed independently from
+       * the source. This also catches the case where users accidentally
+       * swap the source and destination arguments. */
+      if (src_min_unpacked_rev < dst_min_unpacked_rev)
+        return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+                                 _("The hotcopy destination already contains "
+                                   "more packed revisions (%lu) than the "
+                                   "hotcopy source contains (%lu)"),
+                                   dst_min_unpacked_rev - 1,
+                                   src_min_unpacked_rev - 1);
+
+      SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+                                   PATH_MIN_UNPACKED_REV, pool));
+    }
+  else
+    {
+      src_min_unpacked_rev = 0;
+      dst_min_unpacked_rev = 0;
+    }
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  /*
+   * Copy the necessary rev files.
+   */
+
+  src_subdir = svn_dirent_join(src_fs->path, PATH_REVS_DIR, pool);
+  dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVS_DIR, pool);
+  SVN_ERR(svn_io_make_dir_recursively(dst_subdir, pool));
+
+  iterpool = svn_pool_create(pool);
+  /* First, copy packed shards. */
+  for (rev = 0; rev < src_min_unpacked_rev; rev += max_files_per_dir)
+    {
+      svn_error_t *err;
+
+      svn_pool_clear(iterpool);
+
+      if (cancel_func)
+        SVN_ERR(cancel_func(cancel_baton));
+
+      /* Copy the packed shard. */
+      SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
+                                        src_fs, dst_fs,
+                                        rev, max_files_per_dir,
+                                        iterpool));
+
+      /* If necessary, update 'current' to the most recent packed rev,
+       * so readers can see new revisions which arrived in this pack. */
+      SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs,
+                                     rev + max_files_per_dir - 1,
+                                     iterpool));
+
+      /* Remove revision files which are now packed. */
+      if (incremental)
+        SVN_ERR(hotcopy_remove_rev_files(dst_fs, rev, rev + max_files_per_dir,
+                                         max_files_per_dir, iterpool));
+
+      /* Now that all revisions have moved into the pack, the original
+       * rev dir can be removed. */
+      err = svn_io_remove_dir2(path_rev_shard(dst_fs, rev, iterpool),
+                               TRUE, cancel_func, cancel_baton, iterpool);
+      if (err)
+        {
+          if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
+            svn_error_clear(err);
+          else
+            return svn_error_trace(err);
+        }
+    }
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  /* Now, copy pairs of non-packed revisions and revprop files.
+   * If necessary, update 'current' after copying all files from a shard. */
+  SVN_ERR_ASSERT(rev == src_min_unpacked_rev);
+  SVN_ERR_ASSERT(src_min_unpacked_rev == dst_min_unpacked_rev);
+  revprop_src_subdir = svn_dirent_join(src_fs->path, PATH_REVPROPS_DIR, pool);
+  revprop_dst_subdir = svn_dirent_join(dst_fs->path, PATH_REVPROPS_DIR, pool);
+  SVN_ERR(svn_io_make_dir_recursively(revprop_dst_subdir, pool));
+  for (; rev <= src_youngest; rev++)
+    {
+      svn_error_t *err;
+
+      svn_pool_clear(iterpool);
+
+      if (cancel_func)
+        SVN_ERR(cancel_func(cancel_baton));
+
+      /* Copy the rev file. */
+      err = hotcopy_copy_shard_file(src_subdir, dst_subdir,
+                                    rev, max_files_per_dir,
+                                    iterpool);
+      if (err)
+        {
+          if (APR_STATUS_IS_ENOENT(err->apr_err) &&
+              src_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+            {
+              svn_error_clear(err);
+
+              /* The source rev file does not exist. This can happen if the
+               * source repository is being packed concurrently with this
+               * hotcopy operation.
+               *
+               * If the new revision is now packed, and the youngest revision
+               * we're interested in is not inside this pack, try to copy the
+               * pack instead.
+               *
+               * If the youngest revision ended up being packed, don't try
+               * to be smart and work around this. Just abort the hotcopy. */
+              SVN_ERR(update_min_unpacked_rev(src_fs, pool));
+              if (is_packed_rev(src_fs, rev))
+                {
+                  if (is_packed_rev(src_fs, src_youngest))
+                    return svn_error_createf(
+                             SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+                             _("The assumed HEAD revision (%lu) of the "
+                               "hotcopy source has been packed while the "
+                               "hotcopy was in progress; please restart "
+                               "the hotcopy operation"),
+                             src_youngest);
+
+                  SVN_ERR(hotcopy_copy_packed_shard(&dst_min_unpacked_rev,
+                                                    src_fs, dst_fs,
+                                                    rev, max_files_per_dir,
+                                                    iterpool));
+                  rev = dst_min_unpacked_rev;
+                  continue;
+                }
+              else
+                return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+                                         _("Revision %lu disappeared from the "
+                                           "hotcopy source while hotcopy was "
+                                           "in progress"), rev);
+            }
+          else
+            return svn_error_trace(err);
+        }
+
+      /* Copy the revprop file. */
+      SVN_ERR(hotcopy_copy_shard_file(revprop_src_subdir,
+                                      revprop_dst_subdir,
+                                      rev, max_files_per_dir,
+                                      iterpool));
+
+      /* After completing a full shard, update 'current'. */
+      if (max_files_per_dir && rev % max_files_per_dir == 0)
+        SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, rev, iterpool));
+    }
+  svn_pool_destroy(iterpool);
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  /* We assume that all revisions were copied now, i.e. we didn't exit the
+   * above loop early. 'rev' was last incremented during exit of the loop. */
+  SVN_ERR_ASSERT(rev == src_youngest + 1);
+
+  /* All revisions were copied. Update 'current'. */
+  SVN_ERR(hotcopy_update_current(&dst_youngest, dst_fs, src_youngest, pool));
+
+  /* Replace the locks tree.
+   * This is racy in case readers are currently trying to list locks in
+   * the destination. However, we need to get rid of stale locks.
+   * This is the simplest way of doing this, so we accept this small race. */
+  dst_subdir = svn_dirent_join(dst_fs->path, PATH_LOCKS_DIR, pool);
+  SVN_ERR(svn_io_remove_dir2(dst_subdir, TRUE, cancel_func, cancel_baton,
+                             pool));
+  src_subdir = svn_dirent_join(src_fs->path, PATH_LOCKS_DIR, pool);
+  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+  if (kind == svn_node_dir)
+    SVN_ERR(svn_io_copy_dir_recursively(src_subdir, dst_fs->path,
+                                        PATH_LOCKS_DIR, TRUE,
+                                        cancel_func, cancel_baton, pool));
+
+  /* Now copy the node-origins cache tree. */
+  src_subdir = svn_dirent_join(src_fs->path, PATH_NODE_ORIGINS_DIR, pool);
+  SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+  if (kind == svn_node_dir)
+    SVN_ERR(hotcopy_io_copy_dir_recursively(src_subdir, dst_fs->path,
+                                            PATH_NODE_ORIGINS_DIR, TRUE,
+                                            cancel_func, cancel_baton, pool));
+
+  /*
+   * NB: Data copied below is only read by writers, not readers.
+   *     Writers are still locked out at this point.
+   */
+
+  if (dst_ffd->format >= SVN_FS_FS__MIN_REP_SHARING_FORMAT)
+    {
+      /* Copy the rep cache and then remove entries for revisions
+       * younger than the destination's youngest revision. */
+      src_subdir = svn_dirent_join(src_fs->path, REP_CACHE_DB_NAME, pool);
+      dst_subdir = svn_dirent_join(dst_fs->path, REP_CACHE_DB_NAME, pool);
+      SVN_ERR(svn_io_check_path(src_subdir, &kind, pool));
+      if (kind == svn_node_file)
+        {
+          SVN_ERR(svn_sqlite__hotcopy(src_subdir, dst_subdir, pool));
+          SVN_ERR(svn_fs_fs__del_rep_reference(dst_fs, dst_youngest, pool));
+        }
+    }
+
+  /* Copy the txn-current file. */
+  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+    SVN_ERR(svn_io_dir_file_copy(src_fs->path, dst_fs->path,
+                                 PATH_TXN_CURRENT, pool));
+
+  /* If a revprop generation file exists in the source filesystem,
+   * reset it to zero (since this is on a different path, it will not
+   * overlap with data already in cache).  Also, clean up stale files
+   * used for the named atomics implementation. */
+  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
+                            &kind, pool));
+  if (kind == svn_node_file)
+    SVN_ERR(write_revprop_generation_file(dst_fs, 0, pool));
+
+  SVN_ERR(cleanup_revprop_namespace(dst_fs));
+
+  /* Hotcopied FS is complete. Stamp it with a format file. */
+  dst_ffd->max_files_per_dir = max_files_per_dir;
+  SVN_ERR(svn_fs_fs__write_format(dst_fs, pool));
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Set up shared data between SRC_FS and DST_FS. */
+static void
+hotcopy_setup_shared_fs_data(svn_fs_t *src_fs, svn_fs_t *dst_fs)
+{
+  fs_fs_data_t *src_ffd = src_fs->fsap_data;
+  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+  /* The common pool and mutexes are shared between src and dst filesystems.
+   * During hotcopy we only grab the mutexes for the destination, so there
+   * is no risk of dead-lock. We don't write to the src filesystem. Shared
+   * data for the src_fs has already been initialised in fs_hotcopy(). */
+  dst_ffd->shared = src_ffd->shared;
+}
+
+/* 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)
+{
+  fs_fs_data_t *src_ffd = src_fs->fsap_data;
+  fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+
+  dst_fs->path = apr_pstrdup(pool, dst_path);
+
+  dst_ffd->max_files_per_dir = src_ffd->max_files_per_dir;
+  dst_ffd->config = src_ffd->config;
+  dst_ffd->format = src_ffd->format;
+
+  /* Create the revision data directories. */
+  if (dst_ffd->max_files_per_dir)
+    SVN_ERR(svn_io_make_dir_recursively(path_rev_shard(dst_fs, 0, pool),
+                                        pool));
+  else
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+                                                        PATH_REVS_DIR, pool),
+                                        pool));
+
+  /* Create the revprops directory. */
+  if (src_ffd->max_files_per_dir)
+    SVN_ERR(svn_io_make_dir_recursively(path_revprops_shard(dst_fs, 0, pool),
+                                        pool));
+  else
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+                                                        PATH_REVPROPS_DIR,
+                                                        pool),
+                                        pool));
+
+  /* Create the transaction directory. */
+  SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path, PATH_TXNS_DIR,
+                                                      pool),
+                                      pool));
+
+  /* Create the protorevs directory. */
+  if (dst_ffd->format >= SVN_FS_FS__MIN_PROTOREVS_DIR_FORMAT)
+    SVN_ERR(svn_io_make_dir_recursively(svn_dirent_join(dst_path,
+                                                        PATH_TXN_PROTOS_DIR,
+                                                        pool),
+                                        pool));
+
+  /* Create the 'current' file. */
+  SVN_ERR(svn_io_file_create(svn_fs_fs__path_current(dst_fs, pool),
+                             (dst_ffd->format >=
+                                SVN_FS_FS__MIN_NO_GLOBAL_IDS_FORMAT
+                                ? "0\n" : "0 1 1\n"),
+                             pool));
+
+  /* Create lock file and UUID. */
+  SVN_ERR(svn_io_file_create(path_lock(dst_fs, pool), "", pool));
+  SVN_ERR(svn_fs_fs__set_uuid(dst_fs, src_fs->uuid, pool));
+
+  /* Create the min unpacked rev file. */
+  if (dst_ffd->format >= SVN_FS_FS__MIN_PACKED_FORMAT)
+    SVN_ERR(svn_io_file_create(path_min_unpacked_rev(dst_fs, pool),
+                                                     "0\n", pool));
+  /* Create the txn-current file if the repository supports
+     the transaction sequence file. */
+  if (dst_ffd->format >= SVN_FS_FS__MIN_TXN_CURRENT_FORMAT)
+    {
+      SVN_ERR(svn_io_file_create(path_txn_current(dst_fs, pool),
+                                 "0\n", pool));
+      SVN_ERR(svn_io_file_create(path_txn_current_lock(dst_fs, pool),
+                                 "", pool));
+    }
+
+  dst_ffd->youngest_rev_cache = 0;
+
+  hotcopy_setup_shared_fs_data(src_fs, dst_fs);
+  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,
+                   const char *src_path,
+                   const char *dst_path,
+                   svn_boolean_t incremental,
+                   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));
+
+  SVN_ERR(svn_fs_fs__open(src_fs, src_path, pool));
+
+  if (incremental)
+    {
+      const char *dst_format_abspath;
+      svn_node_kind_t dst_format_kind;
+
+      /* Check destination format to be sure we know how to incrementally
+       * hotcopy to the destination FS. */
+      dst_format_abspath = svn_dirent_join(dst_path, PATH_FORMAT, pool);
+      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));
+          hotcopy_setup_shared_fs_data(src_fs, dst_fs);
+          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));
+    }
+
+  if (cancel_func)
+    SVN_ERR(cancel_func(cancel_baton));
+
+  hbb.src_fs = src_fs;
+  hbb.dst_fs = dst_fs;
+  hbb.incremental = incremental;
+  hbb.cancel_func = cancel_func;
+  hbb.cancel_baton = cancel_baton;
+  SVN_ERR(svn_fs_fs__with_write_lock(dst_fs, hotcopy_body, &hbb, pool));
+
+  return SVN_NO_ERROR;
+}

Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.h?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.h (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/hotcopy.h Sun Dec 23 15:17:47 2012
@@ -0,0 +1,40 @@
+/* hotcopy.h : interface to the native filesystem layer
+ *
+ * ====================================================================
+ *    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 SVN_LIBSVN_FS__HOTCOPY_H
+#define SVN_LIBSVN_FS__HOTCOPY_H
+
+#include "fs.h"
+
+/* 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. Use POOL for temporary allocations. */
+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_cancel_func_t cancel_func,
+                                 void *cancel_baton,
+                                 apr_pool_t *pool);
+
+#endif

Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/lock.c?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/lock.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/lock.c Sun Dec 23 15:17:47 2012
@@ -37,6 +37,7 @@
 #include "lock.h"
 #include "tree.h"
 #include "fs_fs.h"
+#include "transaction.h"
 #include "../libsvn_fs/fs-loader.h"
 
 #include "private/svn_fs_util.h"

Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.c Sun Dec 23 15:17:47 2012
@@ -30,7 +30,9 @@
 #include "../libsvn_fs/fs-loader.h"
 
 #include "low_level.h"
-
+#include "util.h"
+#include "pack.h"
+#include "cached_data.h"
 
 /* The 256 is an arbitrary size large enough to hold the node id and the
  * various flags. */
@@ -1003,3 +1005,143 @@ write_changed_path_info(svn_stream_t *st
 
   return SVN_NO_ERROR;
 }
+
+/* Given an open revision file REV_FILE in FS for REV, locate the trailer that
+   specifies the offset to the root node-id and to the changed path
+   information.  Store the root node offset in *ROOT_OFFSET and the
+   changed path offset in *CHANGES_OFFSET.  If either of these
+   pointers is NULL, do nothing with it.
+
+   If PACKED is true, REV_FILE should be a packed shard file.
+   ### There is currently no such parameter.  This function assumes that
+       is_packed_rev(FS, REV) will indicate whether REV_FILE is a packed
+       file.  Therefore FS->fsap_data->min_unpacked_rev must not have been
+       refreshed since REV_FILE was opened if there is a possibility that
+       revision REV may have become packed since then.
+       TODO: Take an IS_PACKED parameter instead, in order to remove this
+       requirement.
+
+   Allocate temporary variables from POOL. */
+svn_error_t *
+get_root_changes_offset(apr_off_t *root_offset,
+                        apr_off_t *changes_offset,
+                        apr_file_t *rev_file,
+                        svn_fs_t *fs,
+                        svn_revnum_t rev,
+                        apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  apr_off_t offset;
+  apr_off_t rev_offset;
+  char buf[64];
+  int i, num_bytes;
+  const char *str;
+  apr_size_t len;
+  apr_seek_where_t seek_relative;
+
+  /* Determine where to seek to in the file.
+
+     If we've got a pack file, we want to seek to the end of the desired
+     revision.  But we don't track that, so we seek to the beginning of the
+     next revision.
+
+     Unless the next revision is in a different file, in which case, we can
+     just seek to the end of the pack file -- just like we do in the
+     non-packed case. */
+  if (is_packed_rev(fs, rev) && ((rev + 1) % ffd->max_files_per_dir != 0))
+    {
+      SVN_ERR(svn_fs_fs__get_packed_offset(&offset, fs, rev + 1, pool));
+      seek_relative = APR_SET;
+    }
+  else
+    {
+      seek_relative = APR_END;
+      offset = 0;
+    }
+
+  /* Offset of the revision from the start of the pack file, if applicable. */
+  if (is_packed_rev(fs, rev))
+    SVN_ERR(svn_fs_fs__get_packed_offset(&rev_offset, fs, rev, pool));
+  else
+    rev_offset = 0;
+
+  /* We will assume that the last line containing the two offsets
+     will never be longer than 64 characters. */
+  SVN_ERR(svn_io_file_seek(rev_file, seek_relative, &offset, pool));
+
+  offset -= sizeof(buf);
+  SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
+
+  /* Read in this last block, from which we will identify the last line. */
+  len = sizeof(buf);
+  SVN_ERR(svn_io_file_read(rev_file, buf, &len, pool));
+
+  /* This cast should be safe since the maximum amount read, 64, will
+     never be bigger than the size of an int. */
+  num_bytes = (int) len;
+
+  /* The last byte should be a newline. */
+  if (buf[num_bytes - 1] != '\n')
+    {
+      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                               _("Revision file (r%ld) lacks trailing newline"),
+                               rev);
+    }
+
+  /* Look for the next previous newline. */
+  for (i = num_bytes - 2; i >= 0; i--)
+    {
+      if (buf[i] == '\n')
+        break;
+    }
+
+  if (i < 0)
+    {
+      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                               _("Final line in revision file (r%ld) longer "
+                                 "than 64 characters"),
+                               rev);
+    }
+
+  i++;
+  str = &buf[i];
+
+  /* find the next space */
+  for ( ; i < (num_bytes - 2) ; i++)
+    if (buf[i] == ' ')
+      break;
+
+  if (i == (num_bytes - 2))
+    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                             _("Final line in revision file r%ld missing space"),
+                             rev);
+
+  if (root_offset)
+    {
+      apr_int64_t val;
+
+      buf[i] = '\0';
+      SVN_ERR(svn_cstring_atoi64(&val, str));
+      *root_offset = rev_offset + (apr_off_t)val;
+    }
+
+  i++;
+  str = &buf[i];
+
+  /* find the next newline */
+  for ( ; i < num_bytes; i++)
+    if (buf[i] == '\n')
+      break;
+
+  if (changes_offset)
+    {
+      apr_int64_t val;
+
+      buf[i] = '\0';
+      SVN_ERR(svn_cstring_atoi64(&val, str));
+      *changes_offset = rev_offset + (apr_off_t)val;
+    }
+
+  return SVN_NO_ERROR;
+}
+

Modified: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.h?rev=1425472&r1=1425471&r2=1425472&view=diff
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.h (original)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/low_level.h Sun Dec 23 15:17:47 2012
@@ -199,3 +199,11 @@ write_changed_path_info(svn_stream_t *st
                         svn_fs_t *fs,
                         apr_hash_t *changed_paths,
                         apr_pool_t *pool);
+
+svn_error_t *
+get_root_changes_offset(apr_off_t *root_offset,
+                        apr_off_t *changes_offset,
+                        apr_file_t *rev_file,
+                        svn_fs_t *fs,
+                        svn_revnum_t rev,
+                        apr_pool_t *pool);

Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.c?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.c (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.c Sun Dec 23 15:17:47 2012
@@ -0,0 +1,371 @@
+/* pack.c --- FSFS shard packing functionality
+ *
+ * ====================================================================
+ *    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.
+ * ====================================================================
+ */
+#include "svn_pools.h"
+#include "svn_dirent_uri.h"
+#include "private/svn_temp_serializer.h"
+
+#include "fs_fs.h"
+#include "pack.h"
+#include "util.h"
+#include "revprops.h"
+#include "transaction.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+#include "temp_serializer.h"
+
+/* Given REV in FS, set *REV_OFFSET to REV's offset in the packed file.
+   Use POOL for temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_packed_offset(apr_off_t *rev_offset,
+                             svn_fs_t *fs,
+                             svn_revnum_t rev,
+                             apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  svn_stream_t *manifest_stream;
+  svn_boolean_t is_cached;
+  svn_revnum_t shard;
+  apr_int64_t shard_pos;
+  apr_array_header_t *manifest;
+  apr_pool_t *iterpool;
+
+  shard = rev / ffd->max_files_per_dir;
+
+  /* position of the shard within the manifest */
+  shard_pos = rev % ffd->max_files_per_dir;
+
+  /* fetch exactly that element into *rev_offset, if the manifest is found
+     in the cache */
+  SVN_ERR(svn_cache__get_partial((void **) rev_offset, &is_cached,
+                                 ffd->packed_offset_cache, &shard,
+                                 svn_fs_fs__get_sharded_offset, &shard_pos,
+                                 pool));
+
+  if (is_cached)
+      return SVN_NO_ERROR;
+
+  /* Open the manifest file. */
+  SVN_ERR(svn_stream_open_readonly(&manifest_stream,
+                                   path_rev_packed(fs, rev, PATH_MANIFEST,
+                                                   pool),
+                                   pool, pool));
+
+  /* While we're here, let's just read the entire manifest file into an array,
+     so we can cache the entire thing. */
+  iterpool = svn_pool_create(pool);
+  manifest = apr_array_make(pool, ffd->max_files_per_dir, sizeof(apr_off_t));
+  while (1)
+    {
+      svn_boolean_t eof;
+      apr_int64_t val;
+
+      svn_pool_clear(iterpool);
+      SVN_ERR(read_number_from_stream(&val, &eof, manifest_stream, iterpool));
+      if (eof)
+        break;
+
+      APR_ARRAY_PUSH(manifest, apr_off_t) = (apr_off_t)val;
+    }
+  svn_pool_destroy(iterpool);
+
+  *rev_offset = APR_ARRAY_IDX(manifest, rev % ffd->max_files_per_dir,
+                              apr_off_t);
+
+  /* Close up shop and cache the array. */
+  SVN_ERR(svn_stream_close(manifest_stream));
+  return svn_cache__set(ffd->packed_offset_cache, &shard, manifest, pool);
+}
+
+/* Pack the revision SHARD 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.
+ *
+ * If for some reason we detect a partial packing already performed, we
+ * remove the pack file and start again.
+ */
+static svn_error_t *
+pack_rev_shard(const char *pack_file_dir,
+               const char *shard_path,
+               apr_int64_t shard,
+               int max_files_per_dir,
+               svn_cancel_func_t cancel_func,
+               void *cancel_baton,
+               apr_pool_t *pool)
+{
+  const char *pack_file_path, *manifest_file_path;
+  svn_stream_t *pack_stream, *manifest_stream;
+  svn_revnum_t start_rev, end_rev, rev;
+  apr_off_t next_offset;
+  apr_pool_t *iterpool;
+
+  /* Some useful paths. */
+  pack_file_path = svn_dirent_join(pack_file_dir, PATH_PACKED, pool);
+  manifest_file_path = svn_dirent_join(pack_file_dir, PATH_MANIFEST, pool);
+
+  /* Remove any existing pack file for this shard, since it is incomplete. */
+  SVN_ERR(svn_io_remove_dir2(pack_file_dir, TRUE, cancel_func, cancel_baton,
+                             pool));
+
+  /* Create the new directory and pack and manifest files. */
+  SVN_ERR(svn_io_dir_make(pack_file_dir, APR_OS_DEFAULT, pool));
+  SVN_ERR(svn_stream_open_writable(&pack_stream, pack_file_path, pool,
+                                    pool));
+  SVN_ERR(svn_stream_open_writable(&manifest_stream, manifest_file_path,
+                                   pool, pool));
+
+  start_rev = (svn_revnum_t) (shard * max_files_per_dir);
+  end_rev = (svn_revnum_t) ((shard + 1) * (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;
+
+      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));
+
+      /* Update the 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));
+      SVN_ERR(svn_stream_copy3(rev_stream, svn_stream_disown(pack_stream,
+                                                             iterpool),
+                          cancel_func, cancel_baton, iterpool));
+    }
+
+  SVN_ERR(svn_stream_close(manifest_stream));
+  SVN_ERR(svn_stream_close(pack_stream));
+  SVN_ERR(svn_io_copy_perms(shard_path, pack_file_dir, iterpool));
+  SVN_ERR(svn_io_set_file_read_only(pack_file_path, FALSE, iterpool));
+  SVN_ERR(svn_io_set_file_read_only(manifest_file_path, FALSE, iterpool));
+
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* In the file system at FS_PATH, pack the SHARD in REVS_DIR and
+ * REVPROPS_DIR containing exactly MAX_FILES_PER_DIR revisions, using POOL
+ * for allocations.  REVPROPS_DIR will be NULL if revprop packing is not
+ * supported.  COMPRESSION_LEVEL and MAX_PACK_SIZE will be ignored in that
+ * case.
+ * 
+ * CANCEL_FUNC and CANCEL_BATON are what you think they are; similarly
+ * NOTIFY_FUNC and NOTIFY_BATON.
+ *
+ * If for some reason we detect a partial packing already performed, we
+ * remove the pack file and start again.
+ */
+static svn_error_t *
+pack_shard(const char *revs_dir,
+           const char *revsprops_dir,
+           svn_fs_t *fs,
+           apr_int64_t shard,
+           int max_files_per_dir,
+           apr_off_t max_pack_size,
+           int compression_level,
+           svn_fs_pack_notify_t notify_func,
+           void *notify_baton,
+           svn_cancel_func_t cancel_func,
+           void *cancel_baton,
+           apr_pool_t *pool)
+{
+  const char *rev_shard_path, *rev_pack_file_dir;
+  const char *revprops_shard_path, *revprops_pack_file_dir;
+
+  /* Notify caller we're starting to pack this shard. */
+  if (notify_func)
+    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_start,
+                        pool));
+
+  /* Some useful paths. */
+  rev_pack_file_dir = svn_dirent_join(revs_dir,
+                  apr_psprintf(pool,
+                               "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+                               shard),
+                  pool);
+  rev_shard_path = svn_dirent_join(revs_dir,
+                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
+                           pool);
+
+  /* pack the revision content */
+  SVN_ERR(pack_rev_shard(rev_pack_file_dir, rev_shard_path,
+                         shard, max_files_per_dir,
+                         cancel_func, cancel_baton, pool));
+
+  /* if enabled, pack the revprops in an equivalent way */
+  if (revsprops_dir)
+    {
+      revprops_pack_file_dir = svn_dirent_join(revsprops_dir,
+                   apr_psprintf(pool,
+                                "%" APR_INT64_T_FMT PATH_EXT_PACKED_SHARD,
+                                shard),
+                   pool);
+      revprops_shard_path = svn_dirent_join(revsprops_dir,
+                           apr_psprintf(pool, "%" APR_INT64_T_FMT, shard),
+                           pool);
+
+      SVN_ERR(pack_revprops_shard(revprops_pack_file_dir, revprops_shard_path,
+                                  shard, max_files_per_dir,
+                                  (int)(0.9 * max_pack_size),
+                                  compression_level,
+                                  cancel_func, cancel_baton, pool));
+    }
+
+  /* Update the min-unpacked-rev file to reflect our newly packed shard.
+   * (This doesn't update ffd->min_unpacked_rev.  That will be updated by
+   * update_min_unpacked_rev() when necessary.) */
+  SVN_ERR(write_revnum_file(fs,
+                            (svn_revnum_t)((shard + 1) * max_files_per_dir),
+                            pool));
+
+  /* Finally, remove the existing shard directories. */
+  SVN_ERR(svn_io_remove_dir2(rev_shard_path, TRUE,
+                             cancel_func, cancel_baton, pool));
+  if (revsprops_dir)
+    SVN_ERR(delete_revprops_shard(revprops_shard_path,
+                                  shard, max_files_per_dir,
+                                  cancel_func, cancel_baton, pool));
+
+  /* Notify caller we're starting to pack this shard. */
+  if (notify_func)
+    SVN_ERR(notify_func(notify_baton, shard, svn_fs_pack_notify_end,
+                        pool));
+
+  return SVN_NO_ERROR;
+}
+
+struct pack_baton
+{
+  svn_fs_t *fs;
+  svn_fs_pack_notify_t notify_func;
+  void *notify_baton;
+  svn_cancel_func_t cancel_func;
+  void *cancel_baton;
+};
+
+
+/* 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 *'.
+
+   WARNING: if you add a call to this function, please note:
+     The code currently assumes that any piece of code running with
+     the write-lock set can rely on the ffd->min_unpacked_rev and
+     ffd->min_unpacked_revprop caches to be up-to-date (and, by
+     extension, on not having to use a retry when calling
+     svn_fs_fs__path_rev_absolute() and friends).  If you add a call
+     to this function, consider whether you have to call
+     update_min_unpacked_rev().
+     See this thread: http://thread.gmane.org/1291206765.3782.3309.camel@edith
+ */
+static svn_error_t *
+pack_body(void *baton,
+          apr_pool_t *pool)
+{
+  struct pack_baton *pb = baton;
+  fs_fs_data_t *ffd = pb->fs->fsap_data;
+  apr_int64_t completed_shards;
+  apr_int64_t i;
+  svn_revnum_t youngest;
+  apr_pool_t *iterpool;
+  const char *rev_data_path;
+  const char *revprops_data_path = NULL;
+
+  /* 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(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;
+
+  /* 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;
+
+  rev_data_path = svn_dirent_join(pb->fs->path, PATH_REVS_DIR, pool);
+  if (ffd->format >= SVN_FS_FS__MIN_PACKED_REVPROP_FORMAT)
+    revprops_data_path = svn_dirent_join(pb->fs->path, PATH_REVPROPS_DIR,
+                                         pool);
+
+  iterpool = svn_pool_create(pool);
+  for (i = ffd->min_unpacked_rev / ffd->max_files_per_dir;
+       i < completed_shards;
+       i++)
+    {
+      svn_pool_clear(iterpool);
+
+      if (pb->cancel_func)
+        SVN_ERR(pb->cancel_func(pb->cancel_baton));
+
+      SVN_ERR(pack_shard(rev_data_path, revprops_data_path,
+                         pb->fs, i, ffd->max_files_per_dir,
+                         ffd->revprop_pack_size,
+                         ffd->compress_packed_revprops
+                           ? SVN_DELTA_COMPRESSION_LEVEL_DEFAULT
+                           : SVN_DELTA_COMPRESSION_LEVEL_NONE,
+                         pb->notify_func, pb->notify_baton,
+                         pb->cancel_func, pb->cancel_baton, iterpool));
+    }
+
+  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+                svn_fs_pack_notify_t notify_func,
+                void *notify_baton,
+                svn_cancel_func_t cancel_func,
+                void *cancel_baton,
+                apr_pool_t *pool)
+{
+  struct pack_baton pb = { 0 };
+  pb.fs = fs;
+  pb.notify_func = notify_func;
+  pb.notify_baton = notify_baton;
+  pb.cancel_func = cancel_func;
+  pb.cancel_baton = cancel_baton;
+  return svn_fs_fs__with_write_lock(fs, pack_body, &pb, pool);
+}

Added: subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.h?rev=1425472&view=auto
==============================================================================
--- subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.h (added)
+++ subversion/branches/fsfs-format7/subversion/libsvn_fs_fs/pack.h Sun Dec 23 15:17:47 2012
@@ -0,0 +1,53 @@
+/* pack.h : interface FSFS pack functionality
+ *
+ * ====================================================================
+ *    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 SVN_LIBSVN_FS__PACK_H
+#define SVN_LIBSVN_FS__PACK_H
+
+#include "fs.h"
+
+/* Possibly pack the repository at PATH.  This just take full shards, and
+   combines all the revision files into a single one, with a manifest header.
+   Use optional CANCEL_FUNC/CANCEL_BATON for cancellation support.
+
+   Existing filesystem references need not change.  */
+svn_error_t *
+svn_fs_fs__pack(svn_fs_t *fs,
+                svn_fs_pack_notify_t notify_func,
+                void *notify_baton,
+                svn_cancel_func_t cancel_func,
+                void *cancel_baton,
+                apr_pool_t *pool);
+
+/**
+ * For the packed revision @a rev in @a fs,  determine the offset within
+ * the revision pack file and return it in @a rev_offset.  Use @a pool for
+ * allocations.
+ */
+svn_error_t *
+svn_fs_fs__get_packed_offset(apr_off_t *rev_offset,
+                             svn_fs_t *fs,
+                             svn_revnum_t rev,
+                             apr_pool_t *pool);
+
+
+#endif



Mime
View raw message