Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 3A7A3200C05 for ; Mon, 23 Jan 2017 18:37:48 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 387C2160B49; Mon, 23 Jan 2017 17:37:48 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 90BC3160B3C for ; Mon, 23 Jan 2017 18:37:43 +0100 (CET) Received: (qmail 45910 invoked by uid 500); 23 Jan 2017 17:37:42 -0000 Mailing-List: contact dev-help@subversion.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Delivered-To: mailing list dev@subversion.apache.org Received: (qmail 45894 invoked by uid 99); 23 Jan 2017 17:37:42 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 23 Jan 2017 17:37:42 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 878BCC1DD0 for ; Mon, 23 Jan 2017 17:37:41 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 3.73 X-Spam-Level: *** X-Spam-Status: No, score=3.73 tagged_above=-999 required=6.31 tests=[HTML_MESSAGE=2, KAM_INFOUSMEBIZ=0.75, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id yRjXqS2Vx0ON for ; Mon, 23 Jan 2017 17:37:24 +0000 (UTC) Received: from pmta2.delivery6.ore.mailhop.org (pmta2.delivery6.ore.mailhop.org [54.200.129.228]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with ESMTPS id 9A8F55F46D for ; Mon, 23 Jan 2017 17:37:21 +0000 (UTC) X-MHO-User: 440968cb-e192-11e6-acc0-c7e6c9ad01d6 X-Report-Abuse-To: https://support.duocircle.com/support/solutions/articles/5000540958-duocircle-standard-smtp-abuse-information X-Originating-IP: 38.111.151.67 X-Mail-Handler: DuoCircle Outbound SMTP Received: from PerkinsLT6 (unknown [38.111.151.67]) by outbound2.ore.mailhop.org (Halon) with ESMTPSA id 440968cb-e192-11e6-acc0-c7e6c9ad01d6; Mon, 23 Jan 2017 17:35:01 +0000 (UTC) Reply-To: From: "Luke Perkins" To: Subject: SVN-4668 : svnadmin dump node header order has changed Date: Mon, 23 Jan 2017 09:35:31 -0800 Message-ID: <000a01d2759f$19178f70$4b46ae50$@epicdgs.us> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----=_NextPart_000_000B_01D2755C.0AF560E0" X-Mailer: Microsoft Outlook 16.0 Thread-Index: AdJ1nS/jxrdIsaIOTe2XMW895cQJgg== Content-Language: en-us archived-at: Mon, 23 Jan 2017 17:37:48 -0000 This is a multipart message in MIME format. ------=_NextPart_000_000B_01D2755C.0AF560E0 Content-Type: multipart/alternative; boundary="----=_NextPart_001_000C_01D2755C.0AF560E0" ------=_NextPart_001_000C_01D2755C.0AF560E0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Team, Summary: I have attached a proposed set of changes to the following files: 1. Subversion/include/svn_repos.h 2. Subversion/libsvn_repos/dump.c 3. Subversion/svnadmin/svnadmin.c All of these changes are based upon trunk rev. 1779691 (Friday, January 20, 2017). Details: I am new to the code development of SVN, so I am asking for some advice regarding the best way to proceed implementing a proposed fix for svnadmin dump. Since you were the author of the latest changes to libsvn_repos/dump.c regarding the function, "write_revision_headers", I have attached a proposal to be implemented on the svn trunk. See attached. Questions for the team: 1. Would this be an acceptable approach to address future direction for dump while still addressing legacy format? 2. How do we go about getting this checked into the trunk? 3. I have defined a new switch for "svnadmin dump -pre-1.8-dump" to activate the old node key order. I did my best to try to keep the original authors style. Is this an acceptable switch name? 4. A new parameter to the primary function, "svn_repos_dump_fs4", called "svn_boolean_t pre_1_8_dump,". I have search the source code and I think I have all of the function calls covered. Are there any other considerations? 5. What is the verification procedure for implementing this change? Let me know if there are any other considerations we need to implement the proposed fix. Thank-you, Luke Perkins 2581 Flagstone Drive San Jose, California 95132-2611 Cell: 719-339-0987 Home: 408-457-1857 ------=_NextPart_001_000C_01D2755C.0AF560E0 Content-Type: text/html; charset="us-ascii" Content-Transfer-Encoding: quoted-printable

Team,

 

Summary: I = have attached a proposed set of changes to the following = files:

 

  1. Subversion/include/svn_repos.h
  2. Subversion/libsvn_repos/dump.c
  3. Subversion/svnadmin/svnadmin.c

 

All of these = changes are based upon trunk rev. 1779691 (Friday, January 20, = 2017).

 

Details:

 

I am new to = the code development of SVN, so I am asking for some advice regarding = the best way to proceed implementing a proposed fix for svnadmin = dump.

 

Since you were the author of the latest changes to = libsvn_repos/dump.c regarding the function, = “write_revision_headers”, I have attached a proposal to be = implemented on the svn trunk. See attached.

 

Questions = for the team:

 

  1. Would this be an = acceptable approach to address future direction for dump while still = addressing legacy format?
  2. How do we go about = getting this checked into the trunk?
  3. I = have defined a new switch for “svnadmin dump = –pre-1.8-dump” to activate the old node key order. I did my = best to try to keep the original authors style. Is this an acceptable = switch name?
  4. A new parameter to the = primary function, “svn_repos_dump_fs4”, called = “svn_boolean_t pre_1_8_dump,”. I have search the source code = and I think I have all of the function calls covered. Are there any = other considerations?
  5. What is the = verification procedure for implementing this = change?

 

Let me know if there are any other considerations we = need to implement the proposed fix.

 

Thank-you,

 

Luke = Perkins

2581 Flagstone = Drive

San Jose, California = 95132-2611

Cell: = 719-339-0987

Home: = 408-457-1857

 

------=_NextPart_001_000C_01D2755C.0AF560E0-- ------=_NextPart_000_000B_01D2755C.0AF560E0 Content-Type: text/plain; name="svn_repos.h" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="svn_repos.h" /** * @copyright * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * 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. * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * @endcopyright * * @file svn_repos.h * @brief Tools built on top of the filesystem. */ #ifndef SVN_REPOS_H #define SVN_REPOS_H #include #include #include #include #include "svn_types.h" #include "svn_string.h" #include "svn_delta.h" #include "svn_fs.h" #include "svn_io.h" #include "svn_mergeinfo.h" #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* ---------------------------------------------------------------*/ /** * Get libsvn_repos version information. * * @since New in 1.1. */ const svn_version_t * svn_repos_version(void); /* Some useful enums. They need to be declared here for the = notification system to pick them up. */ /** The different "actions" attached to nodes in the dumpfile. */ enum svn_node_action { svn_node_action_change, svn_node_action_add, svn_node_action_delete, svn_node_action_replace }; =0C /** @defgroup svn_repos_authz_callbacks Repository authorization = callbacks * @{ */ /** Callback type for checking authorization on a path. * * Set @a *allowed to TRUE to indicate that some operation is * authorized for @a path in @a root, or set it to FALSE to indicate * unauthorized (presumably according to state stored in @a baton). * * Do not assume @a pool has any lifetime beyond this call. * * The exact operation being authorized depends on the callback * implementation. For read authorization, for example, the caller * would implement an instance that does read checking, and pass it as * a parameter named [perhaps] 'authz_read_func'. The receiver of * that parameter might also take another parameter named * 'authz_write_func', which although sharing this type, would be a * different implementation. * * @note If someday we want more sophisticated authorization states * than just yes/no, @a allowed can become an enum type. */ typedef svn_error_t *(*svn_repos_authz_func_t)(svn_boolean_t *allowed, svn_fs_root_t *root, const char *path, void *baton, apr_pool_t *pool); /** An enum defining the kinds of access authz looks up. * * @since New in 1.3. */ typedef enum svn_repos_authz_access_t { /** No access. */ svn_authz_none =3D 0, /** Path can be read. */ svn_authz_read =3D 1, /** Path can be altered. */ svn_authz_write =3D 2, /** The other access credentials are recursive. */ svn_authz_recursive =3D 4 } svn_repos_authz_access_t; /** Callback type for checking authorization on paths produced by * the repository commit editor. * * Set @a *allowed to TRUE to indicate that the @a required access on * @a path in @a root is authorized, or set it to FALSE to indicate * unauthorized (presumable according to state stored in @a baton). * * If @a path is NULL, the callback should perform a global authz * lookup for the @a required access. That is, the lookup should * check if the @a required access is granted for at least one path of * the repository, and set @a *allowed to TRUE if so. @a root may * also be NULL if @a path is NULL. * * This callback is very similar to svn_repos_authz_func_t, with the * exception of the addition of the @a required parameter. * This is due to historical reasons: when authz was first implemented * for svn_repos_dir_delta2(), it seemed there would need only checks * for read and write operations, hence the svn_repos_authz_func_t * callback prototype and usage scenario. But it was then realized * that lookups due to copying needed to be recursive, and that * brute-force recursive lookups didn't square with the O(1) * performances a copy operation should have. * * So a special way to ask for a recursive lookup was introduced. The * commit editor needs this capability to retain acceptable * performance. Instead of revving the existing callback, causing * unnecessary revving of functions that don't actually need the * extended functionality, this second, more complete callback was * introduced, for use by the commit editor. * * Some day, it would be nice to reunite these two callbacks and do * the necessary revving anyway, but for the time being, this dual * callback mechanism will do. */ typedef svn_error_t *(*svn_repos_authz_callback_t) (svn_repos_authz_access_t required, svn_boolean_t *allowed, svn_fs_root_t *root, const char *path, void *baton, apr_pool_t *pool); /** @} */ /** @defgroup svn_repos_notifications Repository notifications * @{ */ /* Notification system. */ /** The type of action occurring. * * @since New in 1.7. */ typedef enum svn_repos_notify_action_t { /** A warning message is waiting. */ svn_repos_notify_warning =3D 0, /** A revision has finished being dumped. */ svn_repos_notify_dump_rev_end, /** A revision has finished being verified. */ svn_repos_notify_verify_rev_end, /** All revisions have finished being dumped. */ svn_repos_notify_dump_end, /** All revisions have finished being verified. */ svn_repos_notify_verify_end, /** packing of an FSFS shard has commenced */ svn_repos_notify_pack_shard_start, /** packing of an FSFS shard is completed */ svn_repos_notify_pack_shard_end, /** packing of the shard revprops has commenced */ svn_repos_notify_pack_shard_start_revprop, /** packing of the shard revprops has completed */ svn_repos_notify_pack_shard_end_revprop, /** A revision has begun loading */ svn_repos_notify_load_txn_start, /** A revision has finished loading */ svn_repos_notify_load_txn_committed, /** A node has begun loading */ svn_repos_notify_load_node_start, /** A node has finished loading */ svn_repos_notify_load_node_done, /** A copied node has been encountered */ svn_repos_notify_load_copied_node, /** Mergeinfo has been normalized */ svn_repos_notify_load_normalized_mergeinfo, /** The operation has acquired a mutex for the repo. */ svn_repos_notify_mutex_acquired, /** Recover has started. */ svn_repos_notify_recover_start, /** Upgrade has started. */ svn_repos_notify_upgrade_start, /** A revision was skipped during loading. @since New in 1.8. */ svn_repos_notify_load_skipped_rev, /** The structure of a revision is being verified. @since New in 1.8. = */ svn_repos_notify_verify_rev_structure, /** A revprop shard got packed. @since New in 1.9. */ svn_repos_notify_pack_revprops, /** A non-packed revprop shard got removed. @since New in 1.9. */ svn_repos_notify_cleanup_revprops, /** The repository format got bumped. @since New in 1.9. */ svn_repos_notify_format_bumped, /** A revision range was copied. @since New in 1.9. */ svn_repos_notify_hotcopy_rev_range, /** The repository pack did not do anything. @since New in 1.10. */ svn_repos_notify_pack_noop, /** The revision properties got set. @since New in 1.10. */ svn_repos_notify_load_revprop_set } svn_repos_notify_action_t; /** The type of warning occurring. * * @since New in 1.7. */ typedef enum svn_repos_notify_warning_t { /** Referencing copy source data from a revision earlier than the * first revision dumped. */ svn_repos_notify_warning_found_old_reference, /** An #SVN_PROP_MERGEINFO property's encoded mergeinfo references a * revision earlier than the first revision dumped. */ svn_repos_notify_warning_found_old_mergeinfo, /** Found an invalid path in the filesystem. * @see svn_fs.h:"Directory entry names and directory paths" */ /* ### TODO(doxygen): make that a proper doxygen link */ /* See svn_fs__path_valid(). */ svn_repos_notify_warning_invalid_fspath, /** * Detected a name collision. Reported when the names of two or more * entries in the same directory differ only in character * representation (normalization), but are otherwise identical. * * @since New in 1.9. */ svn_repos_notify_warning_name_collision, /** * Detected a mergeinfo path collision. Reported when the paths in * two or more entries in the same svn:mergeinfo property differ * only in character representation (normalization), but are * otherwise identical. * * @since New in 1.9. */ svn_repos_notify_warning_mergeinfo_collision, /** * Detected invalid mergeinfo. * * @since New in 1.9. */ svn_repos_notify_warning_invalid_mergeinfo } svn_repos_notify_warning_t; /** * Structure used by #svn_repos_notify_func_t. * * The only field guaranteed to be populated is @c action. Other fields = are * dependent upon the @c action. (See individual fields for more = information.) * * @note Callers of notification functions should use * svn_repos_notify_create() to create structures of this type to allow = for * future extensibility. * * @since New in 1.7. */ typedef struct svn_repos_notify_t { /** Action that describes what happened in the repository. */ svn_repos_notify_action_t action; /** For #svn_repos_notify_dump_rev_end and = #svn_repos_notify_verify_rev_end, * the revision which just completed. * For #svn_fs_upgrade_format_bumped, the new format version. */ svn_revnum_t revision; /** For #svn_repos_notify_warning, the warning message. */ const char *warning_str; /** For #svn_repos_notify_warning, the warning type. */ svn_repos_notify_warning_t warning; /** For #svn_repos_notify_pack_shard_start, #svn_repos_notify_pack_shard_end, #svn_repos_notify_pack_revprops, #svn_repos_notify_cleanup_revprops #svn_repos_notify_pack_shard_start_revprop, and #svn_repos_notify_pack_shard_end_revprop, the shard processed. */ apr_int64_t shard; /** For #svn_repos_notify_load_txn_committed, the revision committed. = */ svn_revnum_t new_revision; /** For #svn_repos_notify_load_txn_committed, the source revision, if different from @a new_revision, otherwise #SVN_INVALID_REVNUM. For #svn_repos_notify_load_txn_start and #svn_repos_notify_load_skipped_rev, the source revision. */ svn_revnum_t old_revision; /** For #svn_repos_notify_load_node_start, the action being taken on = the node. */ enum svn_node_action node_action; /** For #svn_repos_notify_load_node_start, the path of the node. */ const char *path; /** For #svn_repos_notify_hotcopy_rev_range, the start of the copied revision range. @since New in 1.9. */ svn_revnum_t start_revision; /** For #svn_repos_notify_hotcopy_rev_range, the end of the copied revision range (might be the same as @a start_revision). @since New in 1.9. */ svn_revnum_t end_revision; /* NOTE: Add new fields at the end to preserve binary compatibility. Also, if you add fields here, you have to update svn_repos_notify_create(). */ } svn_repos_notify_t; /** Callback for providing notification from the repository. * Returns @c void. Justification: success of an operation is not = dependent * upon successful notification of that operation. * * @since New in 1.7. */ typedef void (*svn_repos_notify_func_t)(void *baton, const svn_repos_notify_t = *notify, apr_pool_t *scratch_pool); /** * Allocate an #svn_repos_notify_t structure in @a result_pool, = initialize * and return it. * * @since New in 1.7. */ svn_repos_notify_t * svn_repos_notify_create(svn_repos_notify_action_t action, apr_pool_t *result_pool); =0C/** @} */ /** The repository object. */ typedef struct svn_repos_t svn_repos_t; /* Opening and creating repositories. */ /** Find the root path of the repository that contains @a path. * * If a repository was found, the path to the root of the repository * is returned, else @c NULL. The pointer to the returned path may be * equal to @a path. */ const char * svn_repos_find_root_path(const char *path, apr_pool_t *pool); /** Set @a *repos_p to a repository object for the repository at @a = path. * * Allocate @a *repos_p in @a result_pool. * * Acquires a shared lock on the repository, and attaches a cleanup * function to @a result_pool to remove the lock. If no lock can be = acquired, * returns error, with undefined effect on @a *repos_p. If an exclusive * lock is present, this blocks until it's gone. @a fs_config will be * passed to the filesystem initialization function and may be @c NULL. * * Use @a scratch_pool for temporary allocations. * * @since New in 1.9. */ svn_error_t * svn_repos_open3(svn_repos_t **repos_p, const char *path, apr_hash_t *fs_config, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** Similar to svn_repos_open3() but without @a scratch_pool. * * @deprecated Provided for backward compatibility with 1.8 API. * @since New in 1.7. */ SVN_DEPRECATED svn_error_t * svn_repos_open2(svn_repos_t **repos_p, const char *path, apr_hash_t *fs_config, apr_pool_t *pool); /** Similar to svn_repos_open2() with @a fs_config set to NULL. * * @deprecated Provided for backward compatibility with 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_open(svn_repos_t **repos_p, const char *path, apr_pool_t *pool); /** Create a new Subversion repository at @a path, building the = necessary * directory structure, creating the filesystem, and so on. * Return the repository object in @a *repos_p, allocated in @a pool. * * @a config is a client configuration hash of #svn_config_t * items * keyed on config category names, and may be NULL. * * @a fs_config is passed to the filesystem, and may be NULL. * * @a unused_1 and @a unused_2 are not used and should be NULL. */ svn_error_t * svn_repos_create(svn_repos_t **repos_p, const char *path, const char *unused_1, const char *unused_2, apr_hash_t *config, apr_hash_t *fs_config, apr_pool_t *pool); /** * Upgrade the Subversion repository (and its underlying versioned * filesystem) located in the directory @a path to the latest version * supported by this library. If the requested upgrade is not * supported due to the current state of the repository or it * underlying filesystem, return #SVN_ERR_REPOS_UNSUPPORTED_UPGRADE * or #SVN_ERR_FS_UNSUPPORTED_UPGRADE (respectively) and make no * changes to the repository or filesystem. * * Acquires an exclusive lock on the repository, upgrades the * repository, and releases the lock. If an exclusive lock can't be * acquired, returns error. * * If @a nonblocking is TRUE, an error of type EWOULDBLOCK is * returned if the lock is not immediately available. * * If @a start_callback is not NULL, it will be called with @a * start_callback_baton as argument before the upgrade starts, but * after the exclusive lock has been acquired. * * Use @a pool for necessary allocations. * * @note This functionality is provided as a convenience for * administrators wishing to make use of new Subversion functionality * without a potentially costly full repository dump/load. As such, * the operation performs only the minimum amount of work needed to * accomplish this while maintaining the integrity of the repository. * It does *not* guarantee the most optimized repository state as a * dump and subsequent load would. * * @note On some platforms the exclusive lock does not exclude other * threads in the same process so this function should only be called * by a single threaded process, or by a multi-threaded process when * no other threads are accessing the repository. * * @since New in 1.7. */ svn_error_t * svn_repos_upgrade2(const char *path, svn_boolean_t nonblocking, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool); /** * Similar to svn_repos_upgrade2(), but with @a start_callback and = baton, * rather than a notify_callback / baton * * @since New in 1.5. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_upgrade(const char *path, svn_boolean_t nonblocking, svn_error_t *(*start_callback)(void *baton), void *start_callback_baton, apr_pool_t *pool); /** Destroy the Subversion repository found at @a path, using @a pool = for any * necessary allocations. */ svn_error_t * svn_repos_delete(const char *path, apr_pool_t *pool); /** @defgroup svn_repos_capabilities Repository capabilities * @{ */ /** * Set @a *has to TRUE if @a repos has @a capability (one of the * capabilities beginning with @c "SVN_REPOS_CAPABILITY_"), else set * @a *has to FALSE. * * If @a capability isn't recognized, throw #SVN_ERR_UNKNOWN_CAPABILITY, * with the effect on @a *has undefined. * * Use @a pool for all allocation. * * @since New in 1.5. */ svn_error_t * svn_repos_has_capability(svn_repos_t *repos, svn_boolean_t *has, const char *capability, apr_pool_t *pool); /** * Return a set of @a capabilities supported by the running Subversion * library and by @a repos. (Capabilities supported by this version of * Subversion but not by @a repos are not listed. This may happen when * svn_repos_upgrade2() has not been called after a software upgrade.) * * The set is represented as a hash whose const char * keys are the set * members. The values are not defined. * * Allocate @a capabilities in @a result_pool and use @a scratch_pool = for * temporary allocations. * * @see svn_repos_info_format * * @since New in 1.9. */ svn_error_t * svn_repos_capabilities(apr_hash_t **capabilities, svn_repos_t *repos, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** * The capability of doing the right thing with merge-tracking * information, both storing it and responding to queries about it. * * @since New in 1.5. */ #define SVN_REPOS_CAPABILITY_MERGEINFO "mergeinfo" /* *** PLEASE READ THIS IF YOU ADD A NEW CAPABILITY *** * * @c SVN_REPOS_CAPABILITY_foo strings should not include colons, to * be consistent with @c SVN_RA_CAPABILITY_foo strings, which forbid * colons for their own reasons. While this RA limitation has no * direct impact on repository capabilities, there's no reason to be * gratuitously different either. * * If you add a capability, update svn_repos_capabilities(). */ /** @} */ /** * Store in @a repos the client-reported capabilities @a capabilities, * which must be allocated in memory at least as long-lived as @a repos. * * The elements of @a capabilities are 'const char *', a subset of * the constants beginning with @c SVN_RA_CAPABILITY_. * @a capabilities is not copied, so changing it later will affect * what is remembered by @a repos. * * @note The capabilities are passed along to the start-commit hook; * see that hook's template for details. * * @note As of Subversion 1.5, there are no error conditions defined, * so this always returns SVN_NO_ERROR. In future releases it may * return error, however, so callers should check. * * @since New in 1.5. */ svn_error_t * svn_repos_remember_client_capabilities(svn_repos_t *repos, const apr_array_header_t = *capabilities); /** Return the filesystem associated with repository object @a repos. */ svn_fs_t * svn_repos_fs(svn_repos_t *repos); /** Return the type of filesystem associated with repository object * @a repos allocated in @a result_pool. * * @see #svn_fs_backend_names * * @since New in 1.9. */ const char * svn_repos_fs_type(svn_repos_t *repos, apr_pool_t *result_pool); /** Make a hot copy of the Subversion repository found at @a src_path * to @a dst_path. * * Copy a possibly live Subversion repository from @a src_path to * @a dst_path. If @a clean_logs is @c TRUE, perform cleanup on the * source filesystem as part of the copy operation; currently, this * means deleting copied, unused logfiles for a Berkeley DB source * repository. * * If @a incremental is TRUE, make an effort to not re-copy information * already present in the destination. If incremental hotcopy is not * implemented by the filesystem backend, raise = SVN_ERR_UNSUPPORTED_FEATURE. * * For each revision range copied, the @a notify_func function will be * called with the @a notify_baton and a notification structure = containing * appropriate values in @c start_revision and @c end_revision (both * inclusive). @c start_revision might be equal to @c end_revision in * case the copied range consists of a single revision. Currently, this * notification is not triggered by the BDB backend. @a notify_func * may be @c NULL if this notification is not required. * * The optional @a cancel_func callback will be invoked with * @a cancel_baton as usual to allow the user to preempt this = potentially * lengthy operation. *=20 * Use @a scratch_pool for temporary allocations. * * @since New in 1.9. */ svn_error_t * svn_repos_hotcopy3(const char *src_path, const char *dst_path, svn_boolean_t clean_logs, svn_boolean_t incremental, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool); /** * Like svn_repos_hotcopy3(), but with @a notify_func and @a = notify_baton * always passed as @c NULL. * * @since New in 1.8. * @deprecated Provided for backward compatibility with the 1.8 API. */ SVN_DEPRECATED svn_error_t * svn_repos_hotcopy2(const char *src_path, const char *dst_path, svn_boolean_t clean_logs, svn_boolean_t incremental, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Like svn_repos_hotcopy2(), but with @a incremental always passed as * @c FALSE and without cancellation support. * * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_hotcopy(const char *src_path, const char *dst_path, svn_boolean_t clean_logs, apr_pool_t *pool); /** * Possibly update the repository, @a repos, to use a more efficient * filesystem representation. Use @a pool for allocations. * * @since New in 1.7. */ svn_error_t * svn_repos_fs_pack2(svn_repos_t *repos, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_fs_pack2(), but with a #svn_fs_pack_notify_t = instead * of a #svn_repos_notify_t. * * @since New in 1.6. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_pack(svn_repos_t *repos, svn_fs_pack_notify_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Run database recovery procedures on the repository at @a path, * returning the database to a consistent state. Use @a pool for all * allocation. * * Acquires an exclusive lock on the repository, recovers the * database, and releases the lock. If an exclusive lock can't be * acquired, returns error. * * If @a nonblocking is TRUE, an error of type EWOULDBLOCK is * returned if the lock is not immediately available. * * If @a notify_func is not NULL, it will be called with @a * notify_baton as argument before the recovery starts, but * after the exclusive lock has been acquired. * * If @a cancel_func is not @c NULL, it is called periodically with * @a cancel_baton as argument to see if the client wishes to cancel * the recovery. * * @note On some platforms the exclusive lock does not exclude other * threads in the same process so this function should only be called * by a single threaded process, or by a multi-threaded process when * no other threads are accessing the repository. * * @since New in 1.7. */ svn_error_t * svn_repos_recover4(const char *path, svn_boolean_t nonblocking, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void * cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_recover4(), but with @a start callback in place = of * the notify_func / baton. * * @since New in 1.5. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_recover3(const char *path, svn_boolean_t nonblocking, svn_error_t *(*start_callback)(void *baton), void *start_callback_baton, svn_cancel_func_t cancel_func, void * cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_recover3(), but without cancellation support. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_recover2(const char *path, svn_boolean_t nonblocking, svn_error_t *(*start_callback)(void *baton), void *start_callback_baton, apr_pool_t *pool); /** * Similar to svn_repos_recover2(), but with nonblocking set to FALSE, = and * with no callbacks provided. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_recover(const char *path, apr_pool_t *pool); /** * Callback for svn_repos_freeze. * * @since New in 1.8. */ typedef svn_error_t *(*svn_repos_freeze_func_t)(void *baton, apr_pool_t = *pool); /** * Take an exclusive lock on each of the repositories in @a paths to * prevent commits and then while holding all the locks invoke @a * freeze_func passing @a freeze_baton. Each repository may be readable = by * Subversion while frozen, or may be unreadable, depending on which * FS backend the repository uses. Repositories are locked in the * order in which they are specified in the array. * * @note @a freeze_func must not, directly or indirectly, call any = function * that attempts to take out a lock on the underlying repository. These * include functions for packing, hotcopying, setting revprops and = commits. * Attempts to do so may result in a deadlock. * * @note On some platforms the exclusive lock does not exclude other * threads in the same process so this function should only be called * by a single threaded process, or by a multi-threaded process when * no other threads are accessing the repositories. * * @since New in 1.8. */ svn_error_t * svn_repos_freeze(apr_array_header_t *paths, svn_repos_freeze_func_t freeze_func, void *freeze_baton, apr_pool_t *pool); /** This function is a wrapper around svn_fs_berkeley_logfiles(), * returning log file paths relative to the root of the repository. * * @copydoc svn_fs_berkeley_logfiles() */ svn_error_t * svn_repos_db_logfiles(apr_array_header_t **logfiles, const char *path, svn_boolean_t only_unused, apr_pool_t *pool); =0C /* Repository Paths */ /** Return the top-level repository path allocated in @a pool. */ const char * svn_repos_path(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's filesystem directory, allocated in * @a pool. */ const char * svn_repos_db_env(svn_repos_t *repos, apr_pool_t *pool); /** Return path to @a repos's config directory, allocated in @a pool. */ const char * svn_repos_conf_dir(svn_repos_t *repos, apr_pool_t *pool); /** Return path to @a repos's svnserve.conf, allocated in @a pool. */ const char * svn_repos_svnserve_conf(svn_repos_t *repos, apr_pool_t *pool); /** Return path to @a repos's lock directory, allocated in @a pool. */ const char * svn_repos_lock_dir(svn_repos_t *repos, apr_pool_t *pool); /** Return path to @a repos's db lockfile, allocated in @a pool. */ const char * svn_repos_db_lockfile(svn_repos_t *repos, apr_pool_t *pool); /** Return path to @a repos's db logs lockfile, allocated in @a pool. */ const char * svn_repos_db_logs_lockfile(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's hook directory, allocated in @a pool. = */ const char * svn_repos_hook_dir(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's start-commit hook, allocated in @a = pool. */ const char * svn_repos_start_commit_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's pre-commit hook, allocated in @a pool. = */ const char * svn_repos_pre_commit_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's post-commit hook, allocated in @a = pool. */ const char * svn_repos_post_commit_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's pre-revprop-change hook, allocated in * @a pool. */ const char * svn_repos_pre_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's post-revprop-change hook, allocated in * @a pool. */ const char * svn_repos_post_revprop_change_hook(svn_repos_t *repos, apr_pool_t *pool); /** @defgroup svn_repos_lock_hooks Paths to lock hooks * @{ * @since New in 1.2. */ /** Return the path to @a repos's pre-lock hook, allocated in @a pool. = */ const char * svn_repos_pre_lock_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's post-lock hook, allocated in @a pool. = */ const char * svn_repos_post_lock_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's pre-unlock hook, allocated in @a pool. = */ const char * svn_repos_pre_unlock_hook(svn_repos_t *repos, apr_pool_t *pool); /** Return the path to @a repos's post-unlock hook, allocated in @a = pool. */ const char * svn_repos_post_unlock_hook(svn_repos_t *repos, apr_pool_t *pool); /** Specify that Subversion should consult the configuration file * located at @a hooks_env_path to determine how to setup the * environment for hook scripts invoked for the repository @a repos. * As a special case, if @a hooks_env_path is @c NULL, look for the * file in its default location within the repository disk structure. * If @a hooks_env_path is not absolute, it specifies a path relative * to the parent of the file's default location. * * Use @a scratch_pool for temporary allocations. * * If this function is not called, or if the specified configuration * file does not define any environment variables, hooks will run in * an empty environment. * * @since New in 1.8. */ svn_error_t * svn_repos_hooks_setenv(svn_repos_t *repos, const char *hooks_env_path, apr_pool_t *scratch_pool); /** @} */ /* ---------------------------------------------------------------*/ =0C /* Reporting the state of a working copy, for updates. */ /** * Construct and return a @a report_baton that will be passed to the * other functions in this section to describe the state of a = pre-existing * tree (typically, a working copy). When the report is finished, * @a editor/@a edit_baton will be driven in such a way as to transform = the * existing tree to @a revnum and, if @a tgt_path is non-NULL, switch = the * reported hierarchy to @a tgt_path. * * @a fs_base is the absolute path of the node in the filesystem at = which * the comparison should be rooted. @a target is a single path = component, * used to limit the scope of the report to a single entry of @a = fs_base, * or "" if all of @a fs_base itself is the main subject of the report. * * @a tgt_path and @a revnum is the fs path/revision pair that is the * "target" of the delta. @a tgt_path should be provided only when * the source and target paths of the report differ. That is, @a = tgt_path * should *only* be specified when specifying that the resultant editor * drive be one that transforms the reported hierarchy into a pristine = tree * of @a tgt_path at revision @a revnum. A @c NULL value for @a = tgt_path * will indicate that the editor should be driven in such a way as to * transform the reported hierarchy to revision @a revnum, preserving = the * reported hierarchy. * * @a text_deltas instructs the driver of the @a editor to enable * the generation of text deltas. * * @a ignore_ancestry instructs the driver to ignore node ancestry * when determining how to transmit differences. * * @a send_copyfrom_args instructs the driver to send 'copyfrom' * arguments to the editor's add_file() and add_directory() methods, * whenever it deems feasible. * * Use @a authz_read_func and @a authz_read_baton (if not @c NULL) to * avoid sending data through @a editor/@a edit_baton which is not * authorized for transmission. * * @a zero_copy_limit controls the maximum size (in bytes) at which * data blocks may be sent using the zero-copy code path. On that * path, a number of in-memory copy operations have been eliminated to * maximize throughput. However, until the whole block has been * pushed to the network stack, other clients block, so be careful * when using larger values here. Pass 0 for @a zero_copy_limit to * disable this optimization altogether. * * @note Never activate this optimization if @a editor might access * any FSFS data structures (and, hence, caches). So, it is basically * safe for networked editors only. * * All allocation for the context and collected state will occur in * @a pool. * * @a depth is the requested depth of the editor drive. * * If @a depth is #svn_depth_unknown, the editor will affect only the * paths reported by the individual calls to svn_repos_set_path3() and * svn_repos_link_path3(). * * For example, if the reported tree is the @c A subdir of the Greek = Tree * (see Subversion's test suite), at depth #svn_depth_empty, but the * @c A/B subdir is reported at depth #svn_depth_infinity, then * repository-side changes to @c A/mu, or underneath @c A/C and @c * A/D, would not be reflected in the editor drive, but changes * underneath @c A/B would be. * * Additionally, the editor driver will call @c add_directory and * and @c add_file for directories with an appropriate depth. For * example, a directory reported at #svn_depth_files will receive * file (but not directory) additions. A directory at #svn_depth_empty * will receive neither. * * If @a depth is #svn_depth_files, #svn_depth_immediates or * #svn_depth_infinity and @a depth is greater than the reported depth * of the working copy, then the editor driver will emit editor * operations so as to upgrade the working copy to this depth. * * If @a depth is #svn_depth_empty, #svn_depth_files, * #svn_depth_immediates and @a depth is lower * than or equal to the depth of the working copy, then the editor * operations will affect only paths at or above @a depth. * * @since New in 1.8. */ svn_error_t * svn_repos_begin_report3(void **report_baton, svn_revnum_t revnum, svn_repos_t *repos, const char *fs_base, const char *target, const char *tgt_path, svn_boolean_t text_deltas, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t send_copyfrom_args, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_size_t zero_copy_limit, apr_pool_t *pool); /** * The same as svn_repos_begin_report3(), but with @a zero_copy_limit * always passed as 0. * * @since New in 1.5. * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_begin_report2(void **report_baton, svn_revnum_t revnum, svn_repos_t *repos, const char *fs_base, const char *target, const char *tgt_path, svn_boolean_t text_deltas, svn_depth_t depth, svn_boolean_t ignore_ancestry, svn_boolean_t send_copyfrom_args, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * The same as svn_repos_begin_report2(), but taking a boolean * @a recurse flag, and sending FALSE for @a send_copyfrom_args. * * If @a recurse is TRUE, the editor driver will drive the editor with * a depth of #svn_depth_infinity; if FALSE, then with a depth of * #svn_depth_files. * * @note @a username is ignored, and has been removed in a revised * version of this API. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_begin_report(void **report_baton, svn_revnum_t revnum, const char *username, svn_repos_t *repos, const char *fs_base, const char *target, const char *tgt_path, svn_boolean_t text_deltas, svn_boolean_t recurse, svn_boolean_t ignore_ancestry, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Given a @a report_baton constructed by svn_repos_begin_report3(), * record the presence of @a path, at @a revision with depth @a depth, * in the current tree. * * @a path is relative to the anchor/target used in the creation of the * @a report_baton. * * @a revision may be SVN_INVALID_REVNUM if (for example) @a path * represents a locally-added path with no revision number, or @a * depth is #svn_depth_exclude. * * @a path may not be underneath a path on which svn_repos_set_path3() * was previously called with #svn_depth_exclude in this report. * * The first call of this in a given report usually passes an empty * @a path; this is used to set up the correct root revision for the = editor * drive. * * A depth of #svn_depth_unknown is not allowed, and results in an * error. * * If @a start_empty is TRUE and @a path is a directory, then require = the * caller to explicitly provide all the children of @a path - do not = assume * that the tree also contains all the children of @a path at @a = revision. * This is for 'low confidence' client reporting. * * If the caller has a lock token for @a path, then @a lock_token should * be set to that token. Else, @a lock_token should be NULL. * * All temporary allocations are done in @a pool. * * @since New in 1.5. */ svn_error_t * svn_repos_set_path3(void *report_baton, const char *path, svn_revnum_t revision, svn_depth_t depth, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool); /** * Similar to svn_repos_set_path3(), but with @a depth set to * #svn_depth_infinity. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_set_path2(void *report_baton, const char *path, svn_revnum_t revision, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool); /** * Similar to svn_repos_set_path2(), but with @a lock_token set to @c = NULL. * * @deprecated Provided for backward compatibility with the 1.1 API. */ SVN_DEPRECATED svn_error_t * svn_repos_set_path(void *report_baton, const char *path, svn_revnum_t revision, svn_boolean_t start_empty, apr_pool_t *pool); /** * Given a @a report_baton constructed by svn_repos_begin_report3(), * record the presence of @a path in the current tree, containing the = contents * of @a link_path at @a revision with depth @a depth. * * A depth of #svn_depth_unknown is not allowed, and results in an * error. * * @a path may not be underneath a path on which svn_repos_set_path3() * was previously called with #svn_depth_exclude in this report. * * Note that while @a path is relative to the anchor/target used in the * creation of the @a report_baton, @a link_path is an absolute = filesystem * path! * * If @a start_empty is TRUE and @a path is a directory, then require = the * caller to explicitly provide all the children of @a path - do not = assume * that the tree also contains all the children of @a link_path at * @a revision. This is for 'low confidence' client reporting. * * If the caller has a lock token for @a link_path, then @a lock_token * should be set to that token. Else, @a lock_token should be NULL. * * All temporary allocations are done in @a pool. * * @since New in 1.5. */ svn_error_t * svn_repos_link_path3(void *report_baton, const char *path, const char *link_path, svn_revnum_t revision, svn_depth_t depth, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool); /** * Similar to svn_repos_link_path3(), but with @a depth set to * #svn_depth_infinity. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_link_path2(void *report_baton, const char *path, const char *link_path, svn_revnum_t revision, svn_boolean_t start_empty, const char *lock_token, apr_pool_t *pool); /** * Similar to svn_repos_link_path2(), but with @a lock_token set to @c = NULL. * * @deprecated Provided for backward compatibility with the 1.1 API. */ SVN_DEPRECATED svn_error_t * svn_repos_link_path(void *report_baton, const char *path, const char *link_path, svn_revnum_t revision, svn_boolean_t start_empty, apr_pool_t *pool); /** Given a @a report_baton constructed by svn_repos_begin_report3(), * record the non-existence of @a path in the current tree. * * @a path may not be underneath a path on which svn_repos_set_path3() * was previously called with #svn_depth_exclude in this report. * * (This allows the reporter's driver to describe missing pieces of a * working copy, so that 'svn up' can recreate them.) * * All temporary allocations are done in @a pool. */ svn_error_t * svn_repos_delete_path(void *report_baton, const char *path, apr_pool_t *pool); /** Given a @a report_baton constructed by svn_repos_begin_report3(), * finish the report and drive the editor as specified when the report * baton was constructed. * * If an error occurs during the driving of the editor, do NOT abort the * edit; that responsibility belongs to the caller of this function, if * it happens at all. * * After the call to this function, @a report_baton is no longer valid; * it should not be passed to any other reporting functions, including * svn_repos_abort_report(), even if this function returns an error. */ svn_error_t * svn_repos_finish_report(void *report_baton, apr_pool_t *pool); /** Given a @a report_baton constructed by svn_repos_begin_report3(), * abort the report. This function can be called anytime before * svn_repos_finish_report() is called. * * After the call to this function, @a report_baton is no longer valid; * it should not be passed to any other reporting functions. */ svn_error_t * svn_repos_abort_report(void *report_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* The magical dir_delta update routines. */ /** Use the provided @a editor and @a edit_baton to describe the changes * necessary for making a given node (and its descendants, if it is a * directory) under @a src_root look exactly like @a tgt_path under * @a tgt_root. @a src_entry is the node to update. If @a src_entry * is empty, then compute the difference between the entire tree * anchored at @a src_parent_dir under @a src_root and @a tgt_path * under @a tgt_root. Else, describe the changes needed to update * only that entry in @a src_parent_dir. Typically, callers of this * function will use a @a tgt_path that is the concatenation of @a * src_parent_dir and @a src_entry. * * @a src_root and @a tgt_root can both be either revision or = transaction * roots. If @a tgt_root is a revision, @a editor's = set_target_revision() * will be called with the @a tgt_root's revision number, else it will * not be called at all. * * If @a authz_read_func is non-NULL, invoke it before any call to * * @a editor->open_root * @a editor->add_directory * @a editor->open_directory * @a editor->add_file * @a editor->open_file * * passing @a tgt_root, the same path that would be passed to the * editor function in question, and @a authz_read_baton. If the * @a *allowed parameter comes back TRUE, then proceed with the planned * editor call; else if FALSE, then invoke @a editor->absent_file or * @a editor->absent_directory as appropriate, except if the planned * editor call was open_root, throw SVN_ERR_AUTHZ_ROOT_UNREADABLE. * * If @a text_deltas is @c FALSE, send a single @c NULL txdelta window = to * the window handler returned by @a editor->apply_textdelta(). * * If @a depth is #svn_depth_empty, invoke @a editor calls only on * @a src_entry (or @a src_parent_dir, if @a src_entry is empty). * If @a depth is #svn_depth_files, also invoke the editor on file * children, if any; if #svn_depth_immediates, invoke it on * immediate subdirectories as well as files; if #svn_depth_infinity, * recurse fully. * * If @a entry_props is @c TRUE, accompany each opened/added entry with * propchange editor calls that relay special "entry props" (this * is typically used only for working copy updates). * * @a ignore_ancestry instructs the function to ignore node ancestry * when determining how to transmit differences. * * Before completing successfully, this function calls @a editor's * close_edit(), so the caller should expect its @a edit_baton to be * invalid after its use with this function. * * Do any allocation necessary for the delta computation in @a pool. * This function's maximum memory consumption is at most roughly * proportional to the greatest depth of the tree under @a tgt_root, not * the total size of the delta. * * ### svn_repos_dir_delta2 is mostly superseded by the reporter * ### functionality (svn_repos_begin_report3 and friends). * ### svn_repos_dir_delta2 does allow the roots to be transaction * ### roots rather than just revision roots, and it has the * ### entry_props flag. Almost all of Subversion's own code uses the * ### reporter instead; there are some stray references to the * ### svn_repos_dir_delta[2] in comments which should probably * ### actually refer to the reporter. * * @since New in 1.5. */ svn_error_t * svn_repos_dir_delta2(svn_fs_root_t *src_root, const char *src_parent_dir, const char *src_entry, svn_fs_root_t *tgt_root, const char *tgt_path, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_boolean_t text_deltas, svn_depth_t depth, svn_boolean_t entry_props, svn_boolean_t ignore_ancestry, apr_pool_t *pool); /** * Similar to svn_repos_dir_delta2(), but if @a recurse is TRUE, pass * #svn_depth_infinity for @a depth, and if @a recurse is FALSE, * pass #svn_depth_files for @a depth. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_dir_delta(svn_fs_root_t *src_root, const char *src_parent_dir, const char *src_entry, svn_fs_root_t *tgt_root, const char *tgt_path, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_boolean_t text_deltas, svn_boolean_t recurse, svn_boolean_t entry_props, svn_boolean_t ignore_ancestry, apr_pool_t *pool); /** Use the provided @a editor and @a edit_baton to describe the * skeletal changes made in a particular filesystem @a root * (revision or transaction). * * Changes will be limited to those within @a base_dir, and if * @a low_water_mark is set to something other than #SVN_INVALID_REVNUM * it is assumed that the client has no knowledge of revisions prior to * @a low_water_mark. Together, these two arguments define the portion = of * the tree that the client is assumed to have knowledge of, and thus = any * copies of data from outside that part of the tree will be sent in = their * entirety, not as simple copies or deltas against a previous version. * * The @a editor passed to this function should be aware of the fact * that, if @a send_deltas is FALSE, calls to its change_dir_prop(), * change_file_prop(), and apply_textdelta() functions will not * contain meaningful data, and merely serve as indications that * properties or textual contents were changed. * * If @a send_deltas is @c TRUE, the text and property deltas for = changes * will be sent, otherwise NULL text deltas and empty prop changes will = be * used. * * If @a authz_read_func is non-NULL, it will be used to determine if = the * user has read access to the data being accessed. Data that the user * cannot access will be skipped. * * @note This editor driver passes SVN_INVALID_REVNUM for all * revision parameters in the editor interface except the copyfrom * parameter of the add_file() and add_directory() editor functions. * * @since New in 1.4. */ svn_error_t * svn_repos_replay2(svn_fs_root_t *root, const char *base_dir, svn_revnum_t low_water_mark, svn_boolean_t send_deltas, const svn_delta_editor_t *editor, void *edit_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Similar to svn_repos_replay2(), but with @a base_dir set to @c "", * @a low_water_mark set to #SVN_INVALID_REVNUM, @a send_deltas * set to @c FALSE, and @a authz_read_func and @a authz_read_baton * set to @c NULL. * * @deprecated Provided for backward compatibility with the 1.3 API. */ SVN_DEPRECATED svn_error_t * svn_repos_replay(svn_fs_root_t *root, const svn_delta_editor_t *editor, void *edit_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* Making commits. */ /** * Return an @a editor and @a edit_baton to commit changes to the * filesystem of @a repos, beginning at location 'rev:@a base_path', * where "rev" is the argument given to open_root(). * * @a repos is a previously opened repository. @a repos_url_decoded is = the * decoded URL to the base of the repository, and is used to check * copyfrom paths. @a txn is a filesystem transaction object to use * during the commit, or @c NULL to indicate that this function should * create (and fully manage) a new transaction. * * Store the contents of @a revprop_table, a hash mapping const * char * property names to #svn_string_t values, as properties * of the commit transaction, including author and log message if * present. * * @note #SVN_PROP_REVISION_DATE may be present in @a revprop_table, but * it will be overwritten when the transaction is committed. * * Iff @a authz_callback is provided, check read/write authorizations * on paths accessed by editor operations. An operation which fails * due to authz will return SVN_ERR_AUTHZ_UNREADABLE or * SVN_ERR_AUTHZ_UNWRITABLE. * * Calling @a (*editor)->close_edit completes the commit. * * If @a commit_callback is non-NULL, then before @c close_edit returns = (but * after the commit has succeeded) @c close_edit will invoke * @a commit_callback with a filled-in #svn_commit_info_t *, @a = commit_baton, * and @a pool or some subpool thereof as arguments. The @c repos_root = field * of the #svn_commit_info_t is @c NULL. If @a commit_callback * returns an error, that error will be returned from @c close_edit, * otherwise if there was a post-commit hook failure, then that error * will be returned with code SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED. * (Note that prior to Subversion 1.6, @a commit_callback cannot be @c = NULL; * if you don't need a callback, pass a dummy function.) * * Calling @a (*editor)->abort_edit aborts the commit, and will also * abort the commit transaction unless @a txn was supplied (not @c * NULL). Callers who supply their own transactions are responsible * for cleaning them up (either by committing them, or aborting them). * * @since New in 1.5. Since 1.6, @a commit_callback can be @c NULL. * * @note Yes, @a repos_url_decoded is a decoded URL. We = realize * that's sorta wonky. Sorry about that. * * @note Like most commit editors, the returned editor requires that the * @c copyfrom_path parameter passed to its @c add_file and @c = add_directory * methods is a full, URI-encoded URL, not a relative path. */ svn_error_t * svn_repos_get_commit_editor5(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, svn_fs_txn_t *txn, const char *repos_url_decoded, const char *base_path, apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, svn_repos_authz_callback_t authz_callback, void *authz_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_commit_editor5(), but with @a revprop_table * set to a hash containing @a user and @a log_msg as the * #SVN_PROP_REVISION_AUTHOR and #SVN_PROP_REVISION_LOG properties, * respectively. @a user and @a log_msg may both be @c NULL. * * @since New in 1.4. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_commit_editor4(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, svn_fs_txn_t *txn, const char *repos_url, const char *base_path, const char *user, const char *log_msg, svn_commit_callback2_t commit_callback, void *commit_baton, svn_repos_authz_callback_t authz_callback, void *authz_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_commit_editor4(), but * uses the svn_commit_callback_t type. * * @since New in 1.3. * * @deprecated Provided for backward compatibility with the 1.3 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_commit_editor3(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, svn_fs_txn_t *txn, const char *repos_url, const char *base_path, const char *user, const char *log_msg, svn_commit_callback_t callback, void *callback_baton, svn_repos_authz_callback_t authz_callback, void *authz_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_commit_editor3(), but with @a * authz_callback and @a authz_baton set to @c NULL. * * @deprecated Provided for backward compatibility with the 1.2 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_commit_editor2(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, svn_fs_txn_t *txn, const char *repos_url, const char *base_path, const char *user, const char *log_msg, svn_commit_callback_t callback, void *callback_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_commit_editor2(), but with @a txn always * set to @c NULL. * * @deprecated Provided for backward compatibility with the 1.1 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_commit_editor(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, const char *repos_url, const char *base_path, const char *user, const char *log_msg, svn_commit_callback_t callback, void *callback_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* Finding particular revisions. */ /** Set @a *revision to the revision number in @a repos's filesystem = that was * youngest at time @a tm. */ svn_error_t * svn_repos_dated_revision(svn_revnum_t *revision, svn_repos_t *repos, apr_time_t tm, apr_pool_t *pool); /** Given a @a root/@a path within some filesystem, return three pieces = of * information allocated in @a pool: * * - set @a *committed_rev to the revision in which the object was * last modified. (In fs parlance, this is the revision in which * the particular node-rev-id was 'created'.) * * - set @a *committed_date to the date of said revision, or @c NULL * if not available. * * - set @a *last_author to the author of said revision, or @c NULL * if not available. */ svn_error_t * svn_repos_get_committed_info(svn_revnum_t *committed_rev, const char **committed_date, const char **last_author, svn_fs_root_t *root, const char *path, apr_pool_t *pool); /** * Set @a *dirent to an #svn_dirent_t associated with @a path in @a * root. If @a path does not exist in @a root, set @a *dirent to * NULL. Use @a pool for memory allocation. * * @since New in 1.2. */ svn_error_t * svn_repos_stat(svn_dirent_t **dirent, svn_fs_root_t *root, const char *path, apr_pool_t *pool); /** * Callback type to be used with @c svn_repos_list. It will be invoked = for * every directory entry found. * * The full path of the entry is given in @a path and @a dirent contains * various additional information. If @c svn_repos_list has been called * with @a path_info_only set, only the @a kind element of this struct * will be valid. * * @a baton is the user-provided receiver baton. @a scratch_pool may be * used for temporary allocations. * * @since New in 1.10. */ typedef svn_error_t *(* svn_repos_dirent_receiver_t)(const char *path, svn_dirent_t = *dirent, void *baton, apr_pool_t = *scratch_pool); /** * Efficiently list everything within a sub-tree. Specify glob patterns * to search for specific files and folders. * * Walk the sub-tree starting at @a path under @a root up to the given * @a depth. For each directory entry found, @a receiver will be called * with @a receiver_baton. The starting @a path will be reported as = well. * Because retrieving all elements of a @c svn_dirent_t can be = expensive, * you may set @a path_info_only to receive only the path name and the = node * kind. The entries will be reported ordered by their path. * * @a patterns is an optional array of const char *. If it is * not @c NULL, only those directory entries will be reported whose last * path segment matches at least one of these patterns. This feature = uses * @c apr_fnmatch for glob matching and requiring '.' to matched by dots * in the path. * * If @a authz_read_func is not @c NULL, this function will neither = report * entries nor recurse into directories that the user has no access to. * * Cancellation support is provided in the usual way through the = optional * @a cancel_func and @a cancel_baton. * * @a path must point to a directory and @a depth must be at least * @c svn_depth_empty. * * Use @a scratch_pool for temporary memory allocation. * * @since New in 1.10. */ svn_error_t * svn_repos_list(svn_fs_root_t *root, const char *path, apr_array_header_t *patterns, svn_depth_t depth, svn_boolean_t path_info_only, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_repos_dirent_receiver_t receiver, void *receiver_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool); /** * Given @a path which exists at revision @a start in @a fs, set * @a *deleted to the revision @a path was first deleted, within the * inclusive revision range bounded by @a start and @a end. If @a path * does not exist at revision @a start or was not deleted within the * specified range, then set @a *deleted to SVN_INVALID_REVNUM. * Use @a pool for memory allocation. * * @since New in 1.5. */ svn_error_t * svn_repos_deleted_rev(svn_fs_t *fs, const char *path, svn_revnum_t start, svn_revnum_t end, svn_revnum_t *deleted, apr_pool_t *pool); /** Callback type for use with svn_repos_history(). @a path and @a * revision represent interesting history locations in the lifetime * of the path passed to svn_repos_history(). @a baton is the same * baton given to svn_repos_history(). @a pool is provided for the * convenience of the implementor, who should not expect it to live * longer than a single callback call. * * Signal to callback driver to stop processing/invoking this callback * by returning the #SVN_ERR_CEASE_INVOCATION error code. * * @note SVN_ERR_CEASE_INVOCATION is new in 1.5. */ typedef svn_error_t *(*svn_repos_history_func_t)(void *baton, const char *path, svn_revnum_t revision, apr_pool_t *pool); /** * Call @a history_func (with @a history_baton) for each interesting * history location in the lifetime of @a path in @a fs, from the * youngest of @a end and @a start to the oldest. Stop processing if * @a history_func returns #SVN_ERR_CEASE_INVOCATION. Only cross * filesystem copy history if @a cross_copies is @c TRUE. And do all * of this in @a pool. * * If @a authz_read_func is non-NULL, then use it (and @a * authz_read_baton) to verify that @a path in @a end is readable; if * not, return SVN_ERR_AUTHZ_UNREADABLE. Also verify the readability * of every ancestral path/revision pair before pushing them at @a * history_func. If a pair is deemed unreadable, then do not send * them; instead, immediately stop traversing history and return * SVN_NO_ERROR. * * @since New in 1.1. * * @note SVN_ERR_CEASE_INVOCATION is new in 1.5. */ svn_error_t * svn_repos_history2(svn_fs_t *fs, const char *path, svn_repos_history_func_t history_func, void *history_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_revnum_t start, svn_revnum_t end, svn_boolean_t cross_copies, apr_pool_t *pool); /** * Similar to svn_repos_history2(), but with @a authz_read_func * and @a authz_read_baton always set to NULL. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_history(svn_fs_t *fs, const char *path, svn_repos_history_func_t history_func, void *history_baton, svn_revnum_t start, svn_revnum_t end, svn_boolean_t cross_copies, apr_pool_t *pool); /** * Set @a *locations to be a mapping of the revisions to the paths of * the file @a fs_path present at the repository in revision * @a peg_revision, where the revisions are taken out of the array * @a location_revisions. * * @a location_revisions is an array of svn_revnum_t's and @a *locations * maps 'svn_revnum_t *' to 'const char *'. * * If optional @a authz_read_func is non-NULL, then use it (and @a * authz_read_baton) to verify that the peg-object is readable. If not, * return SVN_ERR_AUTHZ_UNREADABLE. Also use the @a authz_read_func * to check that every path returned in the hash is readable. If an * unreadable path is encountered, stop tracing and return * SVN_NO_ERROR. * * @a pool is used for all allocations. * * @since New in 1.1. */ svn_error_t * svn_repos_trace_node_locations(svn_fs_t *fs, apr_hash_t **locations, const char *fs_path, svn_revnum_t peg_revision, const apr_array_header_t = *location_revisions, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Call @a receiver and @a receiver_baton to report successive * location segments in revisions between @a start_rev and @a end_rev * (inclusive) for the line of history identified by the peg-object @a * path in @a peg_revision (and in @a repos). * * @a end_rev may be #SVN_INVALID_REVNUM to indicate that you want * to trace the history of the object to its origin. * * @a start_rev may be #SVN_INVALID_REVNUM to indicate "the HEAD * revision". Otherwise, @a start_rev must be younger than @a end_rev * (unless @a end_rev is #SVN_INVALID_REVNUM). * * @a peg_revision may be #SVN_INVALID_REVNUM to indicate "the HEAD * revision", and must evaluate to be at least as young as @a start_rev. * * If optional @a authz_read_func is not @c NULL, then use it (and @a * authz_read_baton) to verify that the peg-object is readable. If * not, return #SVN_ERR_AUTHZ_UNREADABLE. Also use the @a * authz_read_func to check that every path reported in a location * segment is readable. If an unreadable path is encountered, report * a final (possibly truncated) location segment (if any), stop * tracing history, and return #SVN_NO_ERROR. * * @a pool is used for all allocations. * * @since New in 1.5. */ svn_error_t * svn_repos_node_location_segments(svn_repos_t *repos, const char *path, svn_revnum_t peg_revision, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_location_segment_receiver_t = receiver, void *receiver_baton, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* Retrieving log messages. */ /** Path change descriptor. * * @note Identical to #svn_fs_path_change3_t but with all information * known, i.e. @a node_kind is never #svn_node_unknown and * @a copyfrom_known is always @c TRUE. * * @see svn_fs_path_change3_t * * @since New in 1.10. */ typedef svn_fs_path_change3_t svn_repos_path_change_t; /** The callback invoked by log message loopers, such as * svn_repos_get_logs5(). * * This function is invoked once on each changed path, in a potentially * random order that may even change between invocations for the same * revisions. * * @a baton is what you think it is, and @a change contains relevant * information for the changed path. Please note that @a change may be * modified within this callback but it will become invalid as soon as * the callback returns. * * Use @a scratch_pool for temporary allocation. The caller may clear = it * between or after invocations. * * @since New in 1.10. */ typedef svn_error_t *(*svn_repos_path_change_receiver_t)( void *baton, svn_repos_path_change_t *change, apr_pool_t *scratch_pool); /** * A structure to represent all the information about a particular log = entry. * * @since New in 1.10. */ typedef struct svn_repos_log_entry_t { /** The revision of the commit. */ svn_revnum_t revision; /** The hash of requested revision properties, which may be NULL if it * would contain no revprops. Maps (const char *) property name to * (svn_string_t *) property value. */ apr_hash_t *revprops; /** * Whether or not this message has children. * * When a log operation requests additional merge information, extra = log * entries may be returned as a result of this entry. The new = entries, are * considered children of the original entry, and will follow it. = When * the HAS_CHILDREN flag is set, the receiver should increment its = stack * depth, and wait until an entry is provided with SVN_INVALID_REVNUM = which * indicates the end of the children. * * For log operations which do not request additional merge = information, the * HAS_CHILDREN flag is always FALSE. * * For more information see: * = https://svn.apache.org/repos/asf/subversion/trunk/notes/merge-tracking/de= sign.html#commutative-reporting */ svn_boolean_t has_children; /** * Whether @a revision should be interpreted as non-inheritable in the * same sense of #svn_merge_range_t. * * Currently always FALSE. */ svn_boolean_t non_inheritable; /** * Whether @a revision is a merged revision resulting from a reverse = merge. */ svn_boolean_t subtractive_merge; /* NOTE: Add new fields at the end to preserve binary compatibility. = */ } svn_repos_log_entry_t; /** The callback invoked by log message loopers, such as * svn_repos_get_logs5(). * * This function is invoked once on each log message, in the order * determined by the caller (see above-mentioned functions). * * @a baton is what you think it is, and @a log_entry contains relevant * information for the log message. * * If @a log_entry->has_children is @c TRUE, the message will be = followed * immediately by any number of merged revisions (child messages), which = are * terminated by an invocation with SVN_INVALID_REVNUM. This usage may * be recursive. * * Use @a scratch_pool for temporary allocation. The caller may clear = it * between or after invocations. * * @since New in 1.10. */ typedef svn_error_t *(*svn_repos_log_entry_receiver_t)( void *baton, svn_repos_log_entry_t *log_entry, apr_pool_t *scratch_pool); /** * Invoke @a revision_receiver with @a revision_receiver_baton on each * revision from @a start to @a end in @a repos's filesystem. @a start = may * be greater or less than @a end; this just controls whether the log is * processed in descending or ascending revision number order. * * If not @c NULL, @a path_change_receiver will be invoked with * @a path_change_receiver_baton for each changed path in the respective * revision. These changes will be reported before the @a = revision_receiver * is invoked for that revision. So, for each revision in the log, = there * is a number of calls to @a path_change_receiver followed by a single * invocation of @a revision_receiver, implicitly marking the end of the * changes list for that revision. If a revision does not contain any * changes (or if none are visible due to @a authz_read_func), * @a path_change_receiver will not be called for that revision. * * If @a start or @a end is #SVN_INVALID_REVNUM, it defaults to = youngest. * * If @a paths is non-NULL and has one or more elements, then only show * revisions in which at least one of @a paths was changed (i.e., if * file, text or props changed; if dir, props or entries changed or any = node * changed below it). Each path is a const char * representing * an absolute path in the repository. If @a paths is NULL or empty, * show all revisions regardless of what paths were changed in those * revisions. * * If @a limit is greater than zero then only invoke @a = revision_receiver * on the first @a limit logs. * * If @a strict_node_history is set, copy history (if any exists) will * not be traversed while harvesting revision logs for each path. * * If @a include_merged_revisions is set, log information for revisions * which have been merged to @a paths will also be returned, unless = these * revisions are already part of @a start to @a end in @a repos's * filesystem, as limited by @a paths. In the latter case those = revisions * are skipped and @a receiver is not invoked. * * If @a revprops is NULL, retrieve all revision properties; else, = retrieve * only the revision properties named by the (const char *) array = elements * (i.e. retrieve none if the array is empty). * * If any invocation of @a revision_receiver or @a path_change_receiver * returnn an error, return that error immediately and without wrapping = it. * * If @a start or @a end is a non-existent revision, return the error * #SVN_ERR_FS_NO_SUCH_REVISION, without ever invoking @a = revision_receiver. * * If optional @a authz_read_func is non-NULL, then use this function * (along with optional @a authz_read_baton) to check the readability * of each changed-path in each revision about to be "pushed" at * @a path_change_receiver. If a revision has some changed-paths = readable * and others unreadable, unreadable paths are omitted from the * @a path_change_receiver invocations and only svn:author and svn:date * will be available in the revprops field in the @a revision_receiver * callback. If a revision has no changed-paths readable at all, then = all * paths are omitted and no revprops are available. If * @a path_change_receiver is @c NULL, the same filtering is performed * just without reporting any path changes. * * Use @a scratch_pool for temporary allocations. * * @see svn_repos_path_change_receiver_t, svn_repos_log_entry_receiver_t * * @since New in 1.10. */ svn_error_t * svn_repos_get_logs5(svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, int limit, svn_boolean_t strict_node_history, svn_boolean_t include_merged_revisions, const apr_array_header_t *revprops, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_repos_path_change_receiver_t = path_change_receiver, void *path_change_receiver_baton, svn_repos_log_entry_receiver_t revision_receiver, void *revision_receiver_baton, apr_pool_t *scratch_pool); /** * Similar to svn_repos_get_logs5 but using a #svn_log_entry_receiver_t * @a receiver to receive revision properties and changed paths through = a * single callback and the @a discover_changed_paths flag to control it. * * If @a discover_changed_paths, then each call to @a receiver passes a * hash mapping paths committed in that revision to information about = them * as the receiver's @a changed_paths argument. * Otherwise, each call to @a receiver passes NULL for @a changed_paths. * * @see svn_log_entry_receiver_t * * @since New in 1.5. * * @deprecated Provided for backward compatibility with the 1.9 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_logs4(svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, int limit, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_boolean_t include_merged_revisions, const apr_array_header_t *revprops, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_log_entry_receiver_t receiver, void *receiver_baton, apr_pool_t *pool); /** * Same as svn_repos_get_logs4(), but with @a receiver being * #svn_log_message_receiver_t instead of #svn_log_entry_receiver_t. * Also, @a include_merged_revisions is set to @c FALSE and @a revprops = is * svn:author, svn:date, and svn:log. If @a paths is empty, nothing * is returned. * * @since New in 1.2. * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_logs3(svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, int limit, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_log_message_receiver_t receiver, void *receiver_baton, apr_pool_t *pool); /** * Same as svn_repos_get_logs3(), but with @a limit always set to 0. * * @deprecated Provided for backward compatibility with the 1.1 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_logs2(svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_log_message_receiver_t receiver, void *receiver_baton, apr_pool_t *pool); /** * Same as svn_repos_get_logs2(), but with @a authz_read_func and * @a authz_read_baton always set to NULL. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_logs(svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t start, svn_revnum_t end, svn_boolean_t discover_changed_paths, svn_boolean_t strict_node_history, svn_log_message_receiver_t receiver, void *receiver_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* Retrieving mergeinfo. */ /** * Fetch the mergeinfo for @a paths at @a revision in @a repos, and * set @a *catalog to a catalog of this mergeinfo. @a *catalog will * never be @c NULL but may be empty. * * The paths in @a paths, and the keys of @a catalog, start with '/'. * * @a inherit indicates whether explicit, explicit or inherited, or * only inherited mergeinfo for @a paths is fetched. * * If @a revision is #SVN_INVALID_REVNUM, it defaults to youngest. * * If @a include_descendants is TRUE, then additionally return the * mergeinfo for any descendant of any element of @a paths which has * the #SVN_PROP_MERGEINFO property explicitly set on it. (Note * that inheritance is only taken into account for the elements in @a * paths; descendants of the elements in @a paths which get their * mergeinfo via inheritance are not included in @a *catalog.) * * If optional @a authz_read_func is non-NULL, then use this function * (along with optional @a authz_read_baton) to check the readability * of each path which mergeinfo was requested for (from @a paths). * Silently omit unreadable paths from the request for mergeinfo. * * Use @a pool for all allocations. * * @since New in 1.5. */ svn_error_t * svn_repos_fs_get_mergeinfo(svn_mergeinfo_catalog_t *catalog, svn_repos_t *repos, const apr_array_header_t *paths, svn_revnum_t revision, svn_mergeinfo_inheritance_t inherit, svn_boolean_t include_descendants, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /* Retrieving multiple revisions of a file. */ /** * Retrieve a subset of the interesting revisions of a file @a path in * @a repos as seen in revision @a end. Invoke @a handler with * @a handler_baton as its first argument for each such revision. * @a pool is used for all allocations. See svn_fs_history_prev() for * a discussion of interesting revisions. * * If optional @a authz_read_func is non-NULL, then use this function * (along with optional @a authz_read_baton) to check the readability * of the rev-path in each interesting revision encountered. * * Revision discovery happens from @a end to @a start, and if an * unreadable revision is encountered before @a start is reached, then * revision discovery stops and only the revisions from @a end to the * oldest readable revision are returned (So it will appear that @a * path was added without history in the latter revision). * * If there is an interesting revision of the file that is less than or * equal to start, the iteration will start at that revision. Else, the * iteration will start at the first revision of the file in the = repository, * which has to be less than or equal to end. Note that if the function * succeeds, @a handler will have been called at least once. * * In a series of calls, the file contents for the first interesting = revision * will be provided as a text delta against the empty file. In the = following * calls, the delta will be against the contents for the previous call. * * If @a include_merged_revisions is TRUE, revisions which a included as = a * result of a merge between @a start and @a end will be included. * * Since Subversion 1.8 this function has been enabled to support = reversion * the revision range for @a include_merged_revision @c FALSE reporting = by * switching @a start with @a end. * * @note Prior to Subversion 1.9, this function may request delta = handlers * from @a handler even for empty text deltas. Starting with 1.9, the * delta handler / baton return arguments passed to @a handler will be * #NULL unless there is an actual difference in the file contents = between * the current and the previous call. * * @since New in 1.5. */ svn_error_t * svn_repos_get_file_revs2(svn_repos_t *repos, const char *path, svn_revnum_t start, svn_revnum_t end, svn_boolean_t include_merged_revisions, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); /** * Similar to #svn_file_rev_handler_t, but without the @a * result_of_merge parameter. * * @deprecated Provided for backward compatibility with 1.4 API. * @since New in 1.1. */ typedef svn_error_t *(*svn_repos_file_rev_handler_t) (void *baton, const char *path, svn_revnum_t rev, apr_hash_t *rev_props, svn_txdelta_window_handler_t *delta_handler, void **delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool); /** * Similar to svn_repos_get_file_revs2(), with @a = include_merged_revisions * set to FALSE. * * @deprecated Provided for backward compatibility with the 1.4 API. * @since New in 1.1. */ SVN_DEPRECATED svn_error_t * svn_repos_get_file_revs(svn_repos_t *repos, const char *path, svn_revnum_t start, svn_revnum_t end, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, svn_repos_file_rev_handler_t handler, void *handler_baton, apr_pool_t *pool); /* ---------------------------------------------------------------*/ =0C /** * @defgroup svn_repos_hook_wrappers Hook-sensitive wrappers for = libsvn_fs \ * routines. * @{ */ /** Like svn_fs_commit_txn(), but invoke the @a repos' pre- and * post-commit hooks around the commit. Use @a pool for any necessary * allocations. * * If the pre-commit hook fails, do not attempt to commit the * transaction and throw the original error to the caller. * * A successful commit is indicated by a valid revision value in @a * *new_rev, not if svn_fs_commit_txn() returns an error, which can * occur during its post commit FS processing. If the transaction was * not committed, then return the associated error and do not execute * the post-commit hook. * * If the commit succeeds the post-commit hook is executed. If the * post-commit hook returns an error, always wrap it with * SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED; this allows the caller to * find the post-commit hook error in the returned error chain. If * both svn_fs_commit_txn() and the post-commit hook return errors, * then svn_fs_commit_txn()'s error is the parent error and the * SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED wrapped error is the child * error. * * @a conflict_p, @a new_rev, and @a txn are as in svn_fs_commit_txn(). */ svn_error_t * svn_repos_fs_commit_txn(const char **conflict_p, svn_repos_t *repos, svn_revnum_t *new_rev, svn_fs_txn_t *txn, apr_pool_t *pool); /** Like svn_fs_begin_txn(), but use @a revprop_table, a hash mapping * const char * property names to #svn_string_t values, to * set the properties on transaction @a *txn_p. @a repos is the * repository object which contains the filesystem. @a rev, @a * *txn_p, and @a pool are as in svn_fs_begin_txn(). * * Before a txn is created, the repository's start-commit hooks are * run; if any of them fail, no txn is created, @a *txn_p is unaffected, * and #SVN_ERR_REPOS_HOOK_FAILURE is returned. * * @note @a revprop_table may contain an #SVN_PROP_REVISION_DATE = property, * which will be set on the transaction, but that will be overwritten * when the transaction is committed. * * @since New in 1.5. */ svn_error_t * svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p, svn_repos_t *repos, svn_revnum_t rev, apr_hash_t *revprop_table, apr_pool_t *pool); /** * Same as svn_repos_fs_begin_txn_for_commit2(), but with @a = revprop_table * set to a hash containing @a author and @a log_msg as the * #SVN_PROP_REVISION_AUTHOR and #SVN_PROP_REVISION_LOG properties, * respectively. @a author and @a log_msg may both be @c NULL. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_begin_txn_for_commit(svn_fs_txn_t **txn_p, svn_repos_t *repos, svn_revnum_t rev, const char *author, const char *log_msg, apr_pool_t *pool); /** Like svn_fs_begin_txn(), but use @a author to set the corresponding * property on transaction @a *txn_p. @a repos is the repository object * which contains the filesystem. @a rev, @a *txn_p, and @a pool are as = in * svn_fs_begin_txn(). * * ### Someday: before a txn is created, some kind of read-hook could * be called here. * * @note This function was never fully implemented, nor used. Ignore it. * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_begin_txn_for_update(svn_fs_txn_t **txn_p, svn_repos_t *repos, svn_revnum_t rev, const char *author, apr_pool_t *pool); /** @} */ /** @defgroup svn_repos_fs_locks Repository lock wrappers * @{ */ /** Like svn_fs_lock_many(), but invoke the @a repos's pre- and * post-lock hooks before and after the locking action. * * The pre-lock is run for every path in @a targets. Those targets for * which the pre-lock is successful are passed to svn_fs_lock_many and * the post-lock is run for those that are successfully locked. * Pre-lock hook errors are passed to @a lock_callback. * * For each path in @a targets @a lock_callback will be invoked * passing @a lock_baton and the lock and error that apply to path. * @a lock_callback can be NULL in which case it is not called and any * errors that would have been passed to the callback are not reported. * * If an error occurs when running the post-lock hook the error is * returned wrapped with #SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED. If the * caller sees this error, it knows that some locks succeeded. * * The pre-lock hook may cause a different token to be used for the * lock, instead of the token supplied; see the pre-lock-hook * documentation for more. * * The lock and path passed to @a lock_callback will be allocated in * @a result_pool. Use @a scratch_pool for temporary allocations. * * @note This function is not atomic. If it returns an error, some = targets * may remain unlocked while others may have been locked. * * @see svn_fs_lock_many * * @since New in 1.9. */ svn_error_t * svn_repos_fs_lock_many(svn_repos_t *repos, apr_hash_t *lock_targets, const char *comment, svn_boolean_t is_dav_comment, apr_time_t expiration_date, svn_boolean_t steal_lock, svn_fs_lock_callback_t lock_callback, void *lock_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** Similar to svn_repos_fs_lock_many() but locks only a single path. * * @since New in 1.2. */ svn_error_t * svn_repos_fs_lock(svn_lock_t **lock, svn_repos_t *repos, const char *path, const char *token, const char *comment, svn_boolean_t is_dav_comment, apr_time_t expiration_date, svn_revnum_t current_rev, svn_boolean_t steal_lock, apr_pool_t *pool); /** Like svn_fs_unlock_many(), but invoke the @a repos's pre- and * post-unlock hooks before and after the unlocking action. * * The pre-unlock hook is run for every path in @a targets. Those * targets for which the pre-unlock is successful are passed to * svn_fs_unlock_many and the post-unlock is run for those that are * successfully unlocked. Pre-unlock hook errors are passed to @a * lock_callback. * * For each path in @a targets @a lock_callback will be invoked * passing @a lock_baton and error that apply to path. The lock * passed to the callback will be NULL. @a lock_callback can be NULL * in which case it is not called and any errors that would have been * passed to the callback are not reported. * * If an error occurs when running the post-unlock hook, return the * original error wrapped with #SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED. * If the caller sees this error, it knows that some unlocks * succeeded. * * The path passed to @a lock_callback will be allocated in @a = result_pool. * Use @a scratch_pool for temporary allocations. * * @note This function is not atomic. If it returns an error, some = targets * may remain locked while others may have been unlocked. * * @see svn_fs_unlock_many * * @since New in 1.9. */ svn_error_t * svn_repos_fs_unlock_many(svn_repos_t *repos, apr_hash_t *unlock_targets, svn_boolean_t break_lock, svn_fs_lock_callback_t lock_callback, void *lock_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** Similar to svn_repos_fs_unlock_many() but only unlocks a single = path. * * @since New in 1.2. */ svn_error_t * svn_repos_fs_unlock(svn_repos_t *repos, const char *path, const char *token, svn_boolean_t break_lock, apr_pool_t *pool); /** Look up all the locks in and under @a path in @a repos, setting @a * *locks to a hash which maps const char * paths to the * #svn_lock_t locks associated with those paths. Use @a * authz_read_func and @a authz_read_baton to "screen" all returned * locks. That is: do not return any locks on any paths that are * unreadable in HEAD, just silently omit them. * * @a depth limits the returned locks to those associated with paths * within the specified depth of @a path, and must be one of the * following values: #svn_depth_empty, #svn_depth_files, * #svn_depth_immediates, or #svn_depth_infinity. * * @since New in 1.7. */ svn_error_t * svn_repos_fs_get_locks2(apr_hash_t **locks, svn_repos_t *repos, const char *path, svn_depth_t depth, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Similar to svn_repos_fs_get_locks2(), but with @a depth always * passed as svn_depth_infinity. * * @since New in 1.2. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_get_locks(apr_hash_t **locks, svn_repos_t *repos, const char *path, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** @} */ /** @defgroup svn_repos_properties Versioned and Unversioned Properties * * Prop-changing and prop-reading wrappers for libsvn_fs routines. * @{ */ /** * Like svn_fs_change_rev_prop2(), but validate the name and value of = the * property and invoke the @a repos's pre- and post-revprop-change hooks * around the change as specified by @a use_pre_revprop_change_hook and * @a use_post_revprop_change_hook (respectively). * * @a rev is the revision whose property to change, @a name is the * name of the property, and @a new_value is the new value of the * property. If @a old_value_p is not @c NULL, then @a *old_value_p * is the expected current (preexisting) value of the property (or @c = NULL * for "unset"). @a author is the authenticated username of the person * changing the property value, or NULL if not available. * * If @a authz_read_func is non-NULL, then use it (with @a * authz_read_baton) to validate the changed-paths associated with @a * rev. If the revision contains any unreadable changed paths, then * return #SVN_ERR_AUTHZ_UNREADABLE. * * Validate @a name and @a new_value like the same way * svn_repos_fs_change_node_prop() does. * * Use @a pool for temporary allocations. * * @since New in 1.7. */ svn_error_t * svn_repos_fs_change_rev_prop4(svn_repos_t *repos, svn_revnum_t rev, const char *author, const char *name, const svn_string_t *const *old_value_p, const svn_string_t *new_value, svn_boolean_t use_pre_revprop_change_hook, svn_boolean_t = use_post_revprop_change_hook, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Similar to svn_repos_fs_change_rev_prop4(), but with @a old_value_p = always * set to @c NULL. (In other words, it is similar to * svn_fs_change_rev_prop().) * * @deprecated Provided for backward compatibility with the 1.6 API. * @since New in 1.5. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_change_rev_prop3(svn_repos_t *repos, svn_revnum_t rev, const char *author, const char *name, const svn_string_t *new_value, svn_boolean_t use_pre_revprop_change_hook, svn_boolean_t = use_post_revprop_change_hook, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Similar to svn_repos_fs_change_rev_prop3(), but with the @a * use_pre_revprop_change_hook and @a use_post_revprop_change_hook * always set to @c TRUE. * * @deprecated Provided for backward compatibility with the 1.4 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_change_rev_prop2(svn_repos_t *repos, svn_revnum_t rev, const char *author, const char *name, const svn_string_t *new_value, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Similar to svn_repos_fs_change_rev_prop2(), but with the * @a authz_read_func parameter always NULL. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_fs_change_rev_prop(svn_repos_t *repos, svn_revnum_t rev, const char *author, const char *name, const svn_string_t *new_value, apr_pool_t *pool); /** * Set @a *value_p to the value of the property named @a propname on * revision @a rev in the filesystem opened in @a repos. If @a rev * has no property by that name, set @a *value_p to zero. Allocate * the result in @a pool. * * If @a authz_read_func is non-NULL, then use it (with @a * authz_read_baton) to validate the changed-paths associated with @a * rev. If the changed-paths are all unreadable, then set @a *value_p * to zero unconditionally. If only some of the changed-paths are * unreadable, then allow 'svn:author' and 'svn:date' propvalues to be * fetched, but return 0 for any other property. * * @since New in 1.1. */ svn_error_t * svn_repos_fs_revision_prop(svn_string_t **value_p, svn_repos_t *repos, svn_revnum_t rev, const char *propname, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** * Set @a *table_p to the entire property list of revision @a rev in * filesystem opened in @a repos, as a hash table allocated in @a * pool. The table maps char * property names to * #svn_string_t * values; the names and values are allocated in @a * pool. * * If @a authz_read_func is non-NULL, then use it (with @a * authz_read_baton) to validate the changed-paths associated with @a * rev. If the changed-paths are all unreadable, then return an empty * hash. If only some of the changed-paths are unreadable, then return * an empty hash, except for 'svn:author' and 'svn:date' properties * (assuming those properties exist). * * @since New in 1.1. */ svn_error_t * svn_repos_fs_revision_proplist(apr_hash_t **table_p, svn_repos_t *repos, svn_revnum_t rev, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); /** Validating wrapper for svn_fs_change_node_prop() (which see for * argument descriptions). * * If @a name's kind is not #svn_prop_regular_kind, return * #SVN_ERR_REPOS_BAD_ARGS. If @a name is an "svn:" property, validate = its * @a value and return SVN_ERR_BAD_PROPERTY_VALUE if it is invalid for = the * property. * * @note Originally, the only properties validated were the "svn:" = properties * #SVN_PROP_REVISION_LOG and #SVN_PROP_REVISION_DATE. For the current * validation rules see the private function svn_repos__validate_prop(). */ svn_error_t * svn_repos_fs_change_node_prop(svn_fs_root_t *root, const char *path, const char *name, const svn_string_t *value, apr_pool_t *pool); /** * Set @a *inherited_values to a depth-first ordered array of * #svn_prop_inherited_item_t * structures (the path_or_url members of * which are relative filesystem paths) representing the properties * inherited by @a path in @a root. If no properties are inherited, * then set @a *inherited_values to an empty array. * * if @a propname is NULL then retrieve all explicit and/or inherited * properties. Otherwise retrieve only the properties named @a = propname. * * If optional @a authz_read_func is non-NULL, then use this function * (along with optional @a authz_read_baton) to check the readability * of each parent path from which properties are inherited. Silently = omit * properties for unreadable parent paths. * * Allocate @a *inherited_props in @a result_pool. Use @a scratch_pool = for * temporary allocations. * * @since New in 1.8. */ svn_error_t * svn_repos_fs_get_inherited_props(apr_array_header_t **inherited_props, svn_fs_root_t *root, const char *path, const char *propname, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** Validating wrapper for svn_fs_change_txn_prop() (which see for * argument descriptions). See svn_repos_fs_change_txn_props() for more * information. */ svn_error_t * svn_repos_fs_change_txn_prop(svn_fs_txn_t *txn, const char *name, const svn_string_t *value, apr_pool_t *pool); /** Validating wrapper for svn_fs_change_txn_props() (which see for * argument descriptions). Validate properties and their values the * same way svn_repos_fs_change_node_prop() does. * * @since New in 1.5. */ svn_error_t * svn_repos_fs_change_txn_props(svn_fs_txn_t *txn, const apr_array_header_t *props, apr_pool_t *pool); /** @} */ /* ---------------------------------------------------------------*/ =0C /** * @defgroup svn_repos_inspection Data structures and editor things for = \ * repository inspection. * @{ * * As it turns out, the svn_repos_replay2(), svn_repos_dir_delta2() and * svn_repos_begin_report3() interfaces can be extremely useful for * examining the repository, or more exactly, changes to the repository. * These drivers allows for differences between two trees to be * described using an editor. * * By using the editor obtained from svn_repos_node_editor() with one of * the drivers mentioned above, the description of how to transform one * tree into another can be used to build an in-memory linked-list tree, * which each node representing a repository node that was changed. */ /** A node in the repository. */ typedef struct svn_repos_node_t { /** Node type (file, dir, etc.) */ svn_node_kind_t kind; /** How this node entered the node tree: 'A'dd, 'D'elete, 'R'eplace */ char action; /** Were there any textual mods? (files only) */ svn_boolean_t text_mod; /** Where there any property mods? */ svn_boolean_t prop_mod; /** The name of this node as it appears in its parent's entries list = */ const char *name; /** The filesystem revision where this was copied from (if any) */ svn_revnum_t copyfrom_rev; /** The filesystem path where this was copied from (if any) */ const char *copyfrom_path; /** Pointer to the next sibling of this node */ struct svn_repos_node_t *sibling; /** Pointer to the first child of this node */ struct svn_repos_node_t *child; /** Pointer to the parent of this node */ struct svn_repos_node_t *parent; } svn_repos_node_t; /** Set @a *editor and @a *edit_baton to an editor that, when driven by * a driver such as svn_repos_replay2(), builds an svn_repos_node_t = * * tree representing the delta from @a base_root to @a root in @a * repos's filesystem. * * The editor can also be driven by svn_repos_dir_delta2() or * svn_repos_begin_report3(), but unless you have special needs, * svn_repos_replay2() is preferred. * * Invoke svn_repos_node_from_baton() on @a edit_baton to obtain the = root * node afterwards. * * Note that the delta includes "bubbled-up" directories; that is, * many of the directory nodes will have no prop_mods. * * Allocate the tree and its contents in @a node_pool; do all other * allocation in @a pool. */ svn_error_t * svn_repos_node_editor(const svn_delta_editor_t **editor, void **edit_baton, svn_repos_t *repos, svn_fs_root_t *base_root, svn_fs_root_t *root, apr_pool_t *node_pool, apr_pool_t *pool); /** Return the root node of the linked-list tree generated by driving = the * editor (associated with @a edit_baton) created by = svn_repos_node_editor(). * This is only really useful if used *after* the editor drive is = completed. */ svn_repos_node_t * svn_repos_node_from_baton(void *edit_baton); /** * Return repository format information for @a repos. * * Set @a *repos_format to the repository format number of @a repos, = which is * an integer that increases when incompatible changes are made (such as * by #svn_repos_upgrade2). * * Set @a *supports_version to the version number of the minimum = Subversion * GA release that can read and write @a repos; allocate it in * @a result_pool. Use @a scratch_pool for temporary allocations. * * @see svn_fs_info_format, svn_repos_capabilities * * @since New in 1.9. */ svn_error_t * svn_repos_info_format(int *repos_format, svn_version_t **supports_version, svn_repos_t *repos, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** @} */ /* ---------------------------------------------------------------*/ =0C /** * @defgroup svn_repos_dump_load Dumping, loading and verifying = filesystem data * @{ * * The filesystem 'dump' format contains nothing but the abstract * structure of the filesystem -- independent of any internal node-id * schema or database back-end. All of the data in the dumpfile is * acquired by public function calls into svn_fs.h. Similarly, the * parser which reads the dumpfile is able to reconstruct the * filesystem using only public svn_fs.h routines. * * Thus the dump/load feature's main purpose is for *migrating* data * from one svn filesystem to another -- presumably two filesystems * which have different internal implementations. * * If you simply want to backup your filesystem, you're probably * better off using the built-in facilities of the DB backend (using * Berkeley DB's hot-backup feature, for example.) * * For a description of the dumpfile format, see * /trunk/notes/fs_dumprestore.txt. */ /* The RFC822-style headers in our dumpfile format. */ #define SVN_REPOS_DUMPFILE_MAGIC_HEADER = "SVN-fs-dump-format-version" #define SVN_REPOS_DUMPFILE_FORMAT_VERSION 3 #define SVN_REPOS_DUMPFILE_FORMAT_VERSION_DELTAS 3 #define SVN_REPOS_DUMPFILE_UUID "UUID" #define SVN_REPOS_DUMPFILE_CONTENT_LENGTH "Content-length" #define SVN_REPOS_DUMPFILE_REVISION_NUMBER "Revision-number" #define SVN_REPOS_DUMPFILE_NODE_PATH "Node-path" #define SVN_REPOS_DUMPFILE_NODE_KIND "Node-kind" #define SVN_REPOS_DUMPFILE_NODE_ACTION "Node-action" #define SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH = "Node-copyfrom-path" #define SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV "Node-copyfrom-rev" /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 = "Text-copy-source-md5" /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 = "Text-copy-source-sha1" #define SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM \ = SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 "Text-content-md5" /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 "Text-content-sha1" #define SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM \ = SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 #define SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH = "Prop-content-length" #define SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH = "Text-content-length" /** @since New in 1.1. */ #define SVN_REPOS_DUMPFILE_PROP_DELTA "Prop-delta" /** @since New in 1.1. */ #define SVN_REPOS_DUMPFILE_TEXT_DELTA "Text-delta" /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 = "Text-delta-base-md5" /** @since New in 1.6. */ #define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 = "Text-delta-base-sha1" /** @since New in 1.5. */ #define SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM \ = SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 /** The different policies for processing the UUID in the dumpfile. */ enum svn_repos_load_uuid { /** only update uuid if the repos has no revisions. */ svn_repos_load_uuid_default, /** never update uuid. */ svn_repos_load_uuid_ignore, /** always update uuid. */ svn_repos_load_uuid_force }; /** Callback type for use with svn_repos_verify_fs3(). @a revision * and @a verify_err are the details of a single verification failure * that occurred during the svn_repos_verify_fs3() call. @a baton is * the same baton given to svn_repos_verify_fs3(). @a scratch_pool is * provided for the convenience of the implementor, who should not * expect it to live longer than a single callback call. * * @a verify_err will be cleared and becomes invalid after the callback * returns, use svn_error_dup() to preserve the error. If a callback = uses * @a verify_err as the return value or as a part of the return value, = it * should also call svn_error_dup() for @a verify_err. Implementors of = this * callback are forbidden to call svn_error_clear() for @a verify_err. * * @see svn_repos_verify_fs3 * * @since New in 1.9. */ typedef svn_error_t *(*svn_repos_verify_callback_t)(void *baton, svn_revnum_t = revision, svn_error_t = *verify_err, apr_pool_t = *scratch_pool); /** * Verify the contents of the file system in @a repos. * * Verify the revisions from @a start_rev to @a end_rev inclusive. If * @a start_rev is #SVN_INVALID_REVNUM, start at revision 0; if @a = end_rev * is #SVN_INVALID_REVNUM, end at the head revision. @a start_rev must = be * older than or equal to @a end_rev. If revision 0 is included in the * range, then also verify "global invariants" of the repository, as * described in svn_fs_verify(). * * If @a check_normalization is @c TRUE, report any name collisions * within the same directory or svn:mergeinfo property where the names * differ only in character representation, but are otherwise * identical. * * If @a metadata_only is @c TRUE, backends that have a concept of = separate * metadata verification will only perform that and skip the more = expensive * file context reconstruction and verification. For FSFS format 7+ and * FSX, this allows for a very fast check against external corruption. * * If @a verify_callback is not @c NULL, call it with @a verify_baton = upon * receiving an FS-specific structure failure or a revision verification * failure. Set @c revision callback argument to #SVN_INVALID_REVNUM or * to the revision number respectively. Set @c verify_err to = svn_error_t * describing the reason of the failure. @c verify_err will be cleared * after the callback returns, use svn_error_dup() to preserve the = error. * If @a verify_callback returns an error different from #SVN_NO_ERROR, * stop verifying the repository and immediately return the error from * @a verify_callback. * * If @a verify_callback is @c NULL, this function returns the first * encountered verification error or #SVN_NO_ERROR if there were no = failures * during the verification. Errors that prevent the verification = process * from continuing, such as #SVN_ERR_CANCELLED, are returned immediately * and do not trigger an invocation of @a verify_callback. * * If @a notify_func is not null, then call it with @a notify_baton and * with a notification structure in which the fields are set as follows. * (For a warning that does not apply to a specific revision, the = revision * number is #SVN_INVALID_REVNUM.) * * For each FS-specific structure warning: * @c action =3D svn_repos_notify_verify_rev_structure * @c revision =3D the revision or #SVN_INVALID_REVNUM * * For each revision verification warning: * @c action =3D #svn_repos_notify_warning * @c warning and @c warning_str fields set accordingly * ### TODO: Set @c revision =3D the revision? * * For each successfully verified revision: * @c action =3D #svn_repos_notify_verify_rev_end * @c revision =3D the revision * * At the end: * @c action =3D svn_repos_notify_verify_end * ### Do we really need a callback to tell us the function we * called has reached its end and is about to return? * ### Not sent, currently, if a FS structure error is found. * * If @a cancel_func is not @c NULL, call it periodically with @a * cancel_baton as argument to see if the caller wishes to cancel the * verification. * * Use @a scratch_pool for temporary allocation. * * @see svn_repos_verify_callback_t * * @since New in 1.9. */ svn_error_t * svn_repos_verify_fs3(svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t check_normalization, svn_boolean_t metadata_only, svn_repos_notify_func_t notify_func, void *notify_baton, svn_repos_verify_callback_t verify_callback, void *verify_baton, svn_cancel_func_t cancel, void *cancel_baton, apr_pool_t *scratch_pool); /** * Like svn_repos_verify_fs3(), but with @a verify_callback and * @a verify_baton set to @c NULL and with @a check_normalization * and @a metadata_only set to @c FALSE. * * @since New in 1.7. * @deprecated Provided for backward compatibility with the 1.8 API. */ SVN_DEPRECATED svn_error_t * svn_repos_verify_fs2(svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel, void *cancel_baton, apr_pool_t *scratch_pool); /** * Similar to svn_repos_verify_fs2(), but with a feedback_stream instead = of * handling feedback via the notify_func handler. * * If @a feedback_stream is not @c NULL, write feedback to it (lines of * the form "* Verified revision %ld\n"). * * @since New in 1.5. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_verify_fs(svn_repos_t *repos, svn_stream_t *feedback_stream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Dump the contents of the filesystem within already-open @a repos into * writable @a dumpstream. If @a dumpstream is * @c NULL, this is effectively a primitive verify. It is not complete, * however; see instead svn_repos_verify_fs3(). * * Begin at revision @a start_rev, and dump every revision up through * @a end_rev. If @a start_rev is #SVN_INVALID_REVNUM, start at = revision * 0. If @a end_rev is #SVN_INVALID_REVNUM, end at the head revision. * * If @a incremental is @c TRUE, the first revision dumped will be a = diff * against the previous revision (usually it looks like a full dump of * the tree). * * If @a use_deltas is @c TRUE, output only node properties which have * changed relative to the previous contents, and output text contents * as svndiff data against the previous contents. Regardless of how * this flag is set, the first revision of a non-incremental dump will * be done with full plain text. A dump with @a use_deltas set cannot * be loaded by Subversion 1.0.x. * * If @a include_revprops is @c TRUE, output the revision properties as * well, otherwise omit them. * * If @a include_changes is @c TRUE, output the revision contents, i.e. * tree and node changes. * * If @a pre_1_8_dump is @c TRUE, write the node dump keys in the * order as generated in version 1.8 and earlier releases of svnadmin * dump.=20 * * If @a notify_func is not null, then call it with @a notify_baton and * with a notification structure in which the fields are set as follows. * (For a warning or error notification that does not apply to a = specific * revision, the revision number is #SVN_INVALID_REVNUM.) * * For each warning: * @c action =3D #svn_repos_notify_warning * @c warning and @c warning_str fields set accordingly * ### TODO: Set @c revision =3D the revision or = #SVN_INVALID_REVNUM? * * For each successfully dumped revision: * @c action =3D #svn_repos_notify_dump_rev_end * @c revision =3D the revision * * At the end: * @c action =3D svn_repos_notify_verify_end * ### Do we really need a callback to tell us the function we * called has reached its end and is about to return? * * At the end, if there were certain warnings previously: * @c action =3D #svn_repos_notify_warning * @c warning and @c warning_str fields set accordingly, * reiterating the existence of previous warnings * ### This is a presentation issue. Caller could do this itself. * * If @a cancel_func is not @c NULL, it is called periodically with * @a cancel_baton as argument to see if the client wishes to cancel * the dump. * * Use @a scratch_pool for temporary allocation. * * @since New in 1.10. */ svn_error_t * svn_repos_dump_fs4(svn_repos_t *repos, svn_stream_t *stream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t incremental, svn_boolean_t use_deltas, svn_boolean_t include_revprops, svn_boolean_t include_changes, svn_boolean_t pre_1_8_dump, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_dump_fs4(), but with @a include_revprops and=20 * @a include_changes both set to @c TRUE. * * @since New in 1.7. * @deprecated Provided for backward compatibility with the 1.9 API. */ SVN_DEPRECATED svn_error_t * svn_repos_dump_fs3(svn_repos_t *repos, svn_stream_t *dumpstream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t incremental, svn_boolean_t use_deltas, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool); /** * Similar to svn_repos_dump_fs3(), but with a feedback_stream instead = of * handling feedback via the notify_func handler * * @since New in 1.1. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_dump_fs2(svn_repos_t *repos, svn_stream_t *dumpstream, svn_stream_t *feedback_stream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t incremental, svn_boolean_t use_deltas, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_dump_fs2(), but with the @a use_deltas * parameter always set to @c FALSE. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_dump_fs(svn_repos_t *repos, svn_stream_t *dumpstream, svn_stream_t *feedback_stream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t incremental, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Read and parse dumpfile-formatted @a dumpstream, reconstructing * filesystem revisions in already-open @a repos, handling uuids in * accordance with @a uuid_action. Use @a pool for all allocation. * * If the dumpstream contains copy history that is unavailable in the * repository, an error will be thrown. * * The repository's UUID will be updated iff * the dumpstream contains a UUID and * @a uuid_action is not equal to #svn_repos_load_uuid_ignore and * either the repository contains no revisions or * @a uuid_action is equal to #svn_repos_load_uuid_force. * * If the dumpstream contains no UUID, then @a uuid_action is * ignored and the repository UUID is not touched. * * @a start_rev and @a end_rev act as filters, the lower and upper * (inclusive) range values of revisions in @a dumpstream which will * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in * which case no revision-based filtering occurs at all), or both are * valid revisions (where @a start_rev is older than or equivalent to * @a end_rev). * * If @a parent_dir is not NULL, then the parser will reparent all the * loaded nodes, from root to @a parent_dir. The directory @a = parent_dir * must be an existing directory in the repository. * * If @a use_pre_commit_hook is set, call the repository's pre-commit * hook before committing each loaded revision. * * If @a use_post_commit_hook is set, call the repository's * post-commit hook after committing each loaded revision. * * If @a validate_props is set, then validate Subversion revision and * node properties (those in the svn: namespace) against established * rules for those things. * * If @a ignore_dates is set, ignore any revision datestamps found in * @a dumpstream, allowing the revisions created by the load process * to be stamped as if they were newly created via the normal commit * process. * * If non-NULL, use @a notify_func and @a notify_baton to send = notification * of events to the caller. * * If @a cancel_func is not @c NULL, it is called periodically with * @a cancel_baton as argument to see if the client wishes to cancel * the load. * * @since New in 1.9. */ svn_error_t * svn_repos_load_fs5(svn_repos_t *repos, svn_stream_t *dumpstream, svn_revnum_t start_rev, svn_revnum_t end_rev, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_boolean_t use_pre_commit_hook, svn_boolean_t use_post_commit_hook, svn_boolean_t validate_props, svn_boolean_t ignore_dates, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** Similar to svn_repos_load_fs5(), but with @a ignore_dates * always passed as FALSE. * * @since New in 1.8. * @deprecated Provided for backward compatibility with the 1.8 API. */ SVN_DEPRECATED svn_error_t * svn_repos_load_fs4(svn_repos_t *repos, svn_stream_t *dumpstream, svn_revnum_t start_rev, svn_revnum_t end_rev, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_boolean_t use_pre_commit_hook, svn_boolean_t use_post_commit_hook, svn_boolean_t validate_props, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** Similar to svn_repos_load_fs4(), but with @a start_rev and @a * end_rev always passed as #SVN_INVALID_REVNUM. * * @since New in 1.7. * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_load_fs3(svn_repos_t *repos, svn_stream_t *dumpstream, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_boolean_t use_pre_commit_hook, svn_boolean_t use_post_commit_hook, svn_boolean_t validate_props, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_load_fs3(), but with @a feedback_stream in * place of the #svn_repos_notify_func_t and baton and with * @a validate_props always FALSE. * * @since New in 1.2. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_load_fs2(svn_repos_t *repos, svn_stream_t *dumpstream, svn_stream_t *feedback_stream, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_boolean_t use_pre_commit_hook, svn_boolean_t use_post_commit_hook, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_load_fs2(), but with @a use_pre_commit_hook and * @a use_post_commit_hook always @c FALSE. * * @deprecated Provided for backward compatibility with the 1.1 API. */ SVN_DEPRECATED svn_error_t * svn_repos_load_fs(svn_repos_t *repos, svn_stream_t *dumpstream, svn_stream_t *feedback_stream, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Read and parse dumpfile-formatted @a dumpstream, extracting the * revision properties from it and apply them to the already-open * @a repos. Use @a scratch_pool for temporary allocations. * * If, after filtering by the @a start_rev and @a end_rev, the = dumpstream * contains revisions missing in @a repos, an error will be thrown. * * @a start_rev and @a end_rev act as filters, the lower and upper * (inclusive) range values of revisions in @a dumpstream which will * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in * which case no revision-based filtering occurs at all), or both are * valid revisions (where @a start_rev is older than or equivalent to * @a end_rev). * * If @a validate_props is set, then validate Subversion revision * properties (those in the svn: namespace) against established * rules for those things. * * If @a ignore_dates is set, ignore any revision datestamps found in * @a dumpstream, keeping whatever timestamps the revisions currently * have. * * If non-NULL, use @a notify_func and @a notify_baton to send = notification * of events to the caller. * * If @a cancel_func is not @c NULL, it is called periodically with * @a cancel_baton as argument to see if the client wishes to cancel * the load. * * @remark No repository hooks will be triggered. * * @since New in 1.10. */ svn_error_t * svn_repos_load_fs_revprops(svn_repos_t *repos, svn_stream_t *dumpstream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t validate_props, svn_boolean_t ignore_dates, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool); /** * A vtable that is driven by svn_repos_parse_dumpstream3(). * * @since New in 1.8. */ typedef struct svn_repos_parse_fns3_t { /** The parser has discovered a new "magic header" record within the * parsing session represented by @a parse_baton. The dump-format * version number is @a version. */ svn_error_t *(*magic_header_record)(int version, void *parse_baton, apr_pool_t *pool); /** The parser has discovered a new uuid record within the parsing * session represented by @a parse_baton. The uuid's value is * @a uuid, and it is allocated in @a pool. */ svn_error_t *(*uuid_record)(const char *uuid, void *parse_baton, apr_pool_t *pool); /** The parser has discovered a new revision record within the * parsing session represented by @a parse_baton. All the headers are * placed in @a headers (allocated in @a pool), which maps const * char * header-name =3D=3D> const char * header-value. * The @a revision_baton received back (also allocated in @a pool) * represents the revision. */ svn_error_t *(*new_revision_record)(void **revision_baton, apr_hash_t *headers, void *parse_baton, apr_pool_t *pool); /** The parser has discovered a new node record within the current * revision represented by @a revision_baton. All the headers are * placed in @a headers (as with @c new_revision_record), allocated in * @a pool. The @a node_baton received back is allocated in @a pool * and represents the node. */ svn_error_t *(*new_node_record)(void **node_baton, apr_hash_t *headers, void *revision_baton, apr_pool_t *pool); /** For a given @a revision_baton, set a property @a name to @a value. = */ svn_error_t *(*set_revision_property)(void *revision_baton, const char *name, const svn_string_t *value); /** For a given @a node_baton, set a property @a name to @a value. */ svn_error_t *(*set_node_property)(void *node_baton, const char *name, const svn_string_t *value); /** For a given @a node_baton, delete property @a name. */ svn_error_t *(*delete_node_property)(void *node_baton, const char = *name); /** For a given @a node_baton, remove all properties. */ svn_error_t *(*remove_node_props)(void *node_baton); /** For a given @a node_baton, set @a stream to a writable stream * capable of receiving the node's fulltext. The parser will write * the fulltext to the stream and then close the stream to signal * completion. * * If a @c NULL is returned instead of a stream, the vtable is * indicating that no text is desired, and the parser will not * attempt to send it. */ svn_error_t *(*set_fulltext)(svn_stream_t **stream, void *node_baton); /** For a given @a node_baton, set @a handler and @a handler_baton * to a window handler and baton capable of receiving a delta * against the node's previous contents. The parser will send all * the windows of data to this handler, and will then send a NULL * window to signal completion. * * If a @c NULL is returned instead of a handler, the vtable is * indicating that no delta is desired, and the parser will not * attempt to send it. */ svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *handler, void **handler_baton, void *node_baton); /** The parser has reached the end of the current node represented by * @a node_baton, it can be freed. */ svn_error_t *(*close_node)(void *node_baton); /** The parser has reached the end of the current revision * represented by @a revision_baton. In other words, there are no = more * changed nodes within the revision. The baton can be freed. */ svn_error_t *(*close_revision)(void *revision_baton); } svn_repos_parse_fns3_t; /** * Read and parse dumpfile-formatted @a stream, calling callbacks in * @a parse_fns/@a parse_baton, and using @a pool for allocations. * * If @a deltas_are_text is @c TRUE, handle text-deltas with the @a * set_fulltext callback. This is useful when manipulating a dump * stream without loading it. Otherwise handle text-deltas with the * @a apply_textdelta callback. * * If @a cancel_func is not @c NULL, it is called periodically with * @a cancel_baton as argument to see if the client wishes to cancel * the dump. * * This parser has built-in knowledge of the dumpfile format, but only * in a limited sense: * * * it recognizes the "magic" format-version header. * * * it recognizes the UUID header. * * * it recognizes revision and node records by looking for either * a REVISION_NUMBER or NODE_PATH headers. * * * it recognizes the CONTENT-LENGTH headers, so it knows if and * how to suck up the content body. * * * it knows how to parse a content body into two parts: props * and text, and pass the pieces to the vtable. * * This is enough knowledge to make it easy on vtable implementors, * but still allow expansion of the format: most headers do not have * to be handled explicitly. * * ### [JAF] Wouldn't it be more efficient to support a start/end rev * range here than only supporting it in receivers such as * svn_repos_get_fs_build_parser4()? This parser could then skip = over * chunks of the input stream before the oldest required rev, and * could stop reading entirely after the youngest required rev. * * @since New in 1.8. * @since Starting in 1.10, @a parse_fns may contain #NULL pointers for * those callbacks that the caller is not interested in. */ svn_error_t * svn_repos_parse_dumpstream3(svn_stream_t *stream, const svn_repos_parse_fns3_t *parse_fns, void *parse_baton, svn_boolean_t deltas_are_text, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Set @a *parser and @a *parse_baton to a vtable parser which commits = new * revisions to the fs in @a repos. The constructed parser will treat * UUID records in a manner consistent with @a uuid_action. Use @a pool * to operate on the fs. * * @a start_rev and @a end_rev act as filters, the lower and upper * (inclusive) range values of revisions which will * be loaded. Either both of these values are #SVN_INVALID_REVNUM (in * which case no revision-based filtering occurs at all), or both are * valid revisions (where @a start_rev is older than or equivalent to * @a end_rev). They refer to dump stream revision numbers rather than * committed revision numbers. * * If @a use_history is true, then when the parser encounters a node = that * is added-with-history, it will require 'copy-from' history to exist = in * the repository at the relative (adjusted) copy-from revision and = path. * It will perform a copy from that source location, and will fail if no * suitable source exists there. If @a use_history is false, then it = will * instead convert every copy to a plain add. * * ### The 'use_history=3DFALSE' case is unused and untested in = Subversion. * It seems to me it would not work with a deltas dumpfile (a driver * that calls the @c apply_textdelta method), as it would not have * access to the delta base text. * * If @a use_pre_commit_hook is set, call the repository's pre-commit * hook before committing each loaded revision. * * If @a use_post_commit_hook is set, call the repository's * post-commit hook after committing each loaded revision. * * If @a validate_props is set, then validate Subversion revision and * node properties (those in the svn: namespace) against established * rules for those things. * * If @a ignore_dates is set, ignore any revision datestamps found in * @a dumpstream, allowing the revisions created by the load process * to be stamped as if they were newly created via the normal commit * process. * * If @a parent_dir is not NULL, then the parser will reparent all the * loaded nodes, from root to @a parent_dir. The directory @a = parent_dir * must be an existing directory in the repository. * * @since New in 1.9. */ svn_error_t * svn_repos_get_fs_build_parser5(const svn_repos_parse_fns3_t **parser, void **parse_baton, svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t use_history, svn_boolean_t validate_props, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_boolean_t use_pre_commit_hook, svn_boolean_t use_post_commit_hook, svn_boolean_t ignore_dates, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_fs_build_parser5(), but with the * @c use_pre_commit_hook, @c use_post_commit_hook and @c ignore_dates * arguments all false. * * @since New in 1.8. * @deprecated Provided for backward compatibility with the 1.8 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_fs_build_parser4(const svn_repos_parse_fns3_t **parser, void **parse_baton, svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t use_history, svn_boolean_t validate_props, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool); /** * A vtable that is driven by svn_repos_parse_dumpstream2(). * Similar to #svn_repos_parse_fns3_t except that it lacks * the magic_header_record callback. * * @deprecated Provided for backward compatibility with the 1.7 API. */ typedef struct svn_repos_parse_fns2_t { /** Same as #svn_repos_parse_fns3_t.new_revision_record. */ svn_error_t *(*new_revision_record)(void **revision_baton, apr_hash_t *headers, void *parse_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns3_t.uuid_record. */ svn_error_t *(*uuid_record)(const char *uuid, void *parse_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns3_t.new_node_record. */ svn_error_t *(*new_node_record)(void **node_baton, apr_hash_t *headers, void *revision_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns3_t.set_revision_property. */ svn_error_t *(*set_revision_property)(void *revision_baton, const char *name, const svn_string_t *value); /** Same as #svn_repos_parse_fns3_t.set_node_property. */ svn_error_t *(*set_node_property)(void *node_baton, const char *name, const svn_string_t *value); /** Same as #svn_repos_parse_fns3_t.delete_node_property. */ svn_error_t *(*delete_node_property)(void *node_baton, const char *name); /** Same as #svn_repos_parse_fns3_t.remove_node_props. */ svn_error_t *(*remove_node_props)(void *node_baton); /** Same as #svn_repos_parse_fns3_t.set_fulltext. */ svn_error_t *(*set_fulltext)(svn_stream_t **stream, void *node_baton); /** Same as #svn_repos_parse_fns3_t.apply_textdelta. */ svn_error_t *(*apply_textdelta)(svn_txdelta_window_handler_t *handler, void **handler_baton, void *node_baton); /** Same as #svn_repos_parse_fns3_t.close_node. */ svn_error_t *(*close_node)(void *node_baton); /** Same as #svn_repos_parse_fns3_t.close_revision. */ svn_error_t *(*close_revision)(void *revision_baton); } svn_repos_parse_fns2_t; /** @deprecated Provided for backward compatibility with the 1.7 API. */ typedef svn_repos_parse_fns2_t svn_repos_parser_fns2_t; /** * A vtable that is driven by svn_repos_parse_dumpstream(). * Similar to #svn_repos_parse_fns2_t except that it lacks * the delete_node_property and apply_textdelta callbacks. * * @deprecated Provided for backward compatibility with the 1.0 API. */ typedef struct svn_repos_parse_fns_t { /** Same as #svn_repos_parse_fns2_t.new_revision_record. */ svn_error_t *(*new_revision_record)(void **revision_baton, apr_hash_t *headers, void *parse_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns2_t.uuid_record. */ svn_error_t *(*uuid_record)(const char *uuid, void *parse_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns2_t.new_node_record. */ svn_error_t *(*new_node_record)(void **node_baton, apr_hash_t *headers, void *revision_baton, apr_pool_t *pool); /** Same as #svn_repos_parse_fns2_t.set_revision_property. */ svn_error_t *(*set_revision_property)(void *revision_baton, const char *name, const svn_string_t *value); /** Same as #svn_repos_parse_fns2_t.set_node_property. */ svn_error_t *(*set_node_property)(void *node_baton, const char *name, const svn_string_t *value); /** Same as #svn_repos_parse_fns2_t.remove_node_props. */ svn_error_t *(*remove_node_props)(void *node_baton); /** Same as #svn_repos_parse_fns2_t.set_fulltext. */ svn_error_t *(*set_fulltext)(svn_stream_t **stream, void *node_baton); /** Same as #svn_repos_parse_fns2_t.close_node. */ svn_error_t *(*close_node)(void *node_baton); /** Same as #svn_repos_parse_fns2_t.close_revision. */ svn_error_t *(*close_revision)(void *revision_baton); } svn_repos_parser_fns_t; /** * Similar to svn_repos_parse_dumpstream3(), but uses the more limited * #svn_repos_parser_fns2_t vtable type. * * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_parse_dumpstream2(svn_stream_t *stream, const svn_repos_parser_fns2_t *parse_fns, void *parse_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_parse_dumpstream2(), but uses the more limited * #svn_repos_parser_fns_t vtable type. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_parse_dumpstream(svn_stream_t *stream, const svn_repos_parser_fns_t *parse_fns, void *parse_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_fs_build_parser4(), but with @a start_rev * and @a end_rev always passed as #SVN_INVALID_REVNUM, and yielding * the more limited svn_repos_parse_fns2_t. * * @since New in 1.7. * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_fs_build_parser3(const svn_repos_parse_fns2_t **parser, void **parse_baton, svn_repos_t *repos, svn_boolean_t use_history, svn_boolean_t validate_props, enum svn_repos_load_uuid uuid_action, const char *parent_dir, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool); /** * Similar to svn_repos_get_fs_build_parser3(), but with @a outstream * in place if a #svn_repos_notify_func_t and baton and with * @a validate_props always FALSE. * * @since New in 1.1. * @deprecated Provided for backward compatibility with the 1.6 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_fs_build_parser2(const svn_repos_parse_fns2_t **parser, void **parse_baton, svn_repos_t *repos, svn_boolean_t use_history, enum svn_repos_load_uuid uuid_action, svn_stream_t *outstream, const char *parent_dir, apr_pool_t *pool); /** * Similar to svn_repos_get_fs_build_parser2(), but yields the more * limited svn_repos_parser_fns_t vtable type. * * @deprecated Provided for backward compatibility with the 1.0 API. */ SVN_DEPRECATED svn_error_t * svn_repos_get_fs_build_parser(const svn_repos_parser_fns_t **parser, void **parse_baton, svn_repos_t *repos, svn_boolean_t use_history, enum svn_repos_load_uuid uuid_action, svn_stream_t *outstream, const char *parent_dir, apr_pool_t *pool); /** @} */ /** A data type which stores the authz information. * * @since New in 1.3. */ typedef struct svn_authz_t svn_authz_t; /** * This should be called before any other authz function. * * @a pool must support multi-threaded access if the application will = use * authz from multiple threads. * * @since New in 1.10. */ svn_error_t * svn_repos_authz_initialize(apr_pool_t *pool); /** * Read authz configuration data from @a path (a dirent, an absolute = file url * or a registry path) into @a *authz_p, allocated in @a pool. * * If @a groups_path (a dirent, an absolute file url, or a registry = path) is * set, use the global groups parsed from it. * * If @a path or @a groups_path is not a valid authz rule file, then = return * #SVN_ERR_AUTHZ_INVALID_CONFIG. The contents of @a *authz_p is then * undefined. If @a must_exist is TRUE, a missing authz or groups file * is also an error other than #SVN_ERR_AUTHZ_INVALID_CONFIG (exact = error * depends on the access type). * * For efficient access of in-repository authz, you may provide @a = repos_hint * which will be tried first and may remove the need to open a temporary * repository instance. Otherwise, set it to NULL and the repositories = will * be opened as needed. * * @since New in 1.10. */ svn_error_t * svn_repos_authz_read3(svn_authz_t **authz_p, const char *path, const char *groups_path, svn_boolean_t must_exist, svn_repos_t *repos_hint, apr_pool_t *result_pool, apr_pool_t *scratch_pool); /** * Similar to svn_repos_authz_read3(), but with @a repos_hint set to @c = NULL. * * @since New in 1.8. * @deprecated Provided for backward compatibility with the 1.9 API. */ SVN_DEPRECATED svn_error_t * svn_repos_authz_read2(svn_authz_t **authz_p, const char *path, const char *groups_path, svn_boolean_t must_exist, apr_pool_t *pool); /** * Similar to svn_repos_authz_read2(), but with @a groups_path and @a * repos_root always passed as @c NULL. * * @since New in 1.3. * @deprecated Provided for backward compatibility with the 1.7 API. */ SVN_DEPRECATED svn_error_t * svn_repos_authz_read(svn_authz_t **authz_p, const char *file, svn_boolean_t must_exist, apr_pool_t *pool); /** * Read authz configuration data from @a stream into @a *authz_p, * allocated in @a pool. * * If @a groups_stream is set, use the global groups parsed from it. * * @since New in 1.8. */ svn_error_t * svn_repos_authz_parse(svn_authz_t **authz_p, svn_stream_t *stream, svn_stream_t *groups_stream, apr_pool_t *pool); /** * Check whether @a user can access @a path in the repository @a * repos_name with the @a required_access. @a authz lists the ACLs to * check against. Set @a *access_granted to indicate if the requested * access is granted. * * If @a path is NULL, then check whether @a user has the @a * required_access anywhere in the repository. Set @a *access_granted * to TRUE if at least one path is accessible with the @a * required_access. * * For compatibility with 1.6, and earlier, @a repos_name can be NULL * in which case it is equivalent to a @a repos_name of "". * * @note Presently, @a repos_name must byte-for-byte match the = repos_name * specified in the authz file; it is treated as an opaque string, and = not * as a dirent. * * @since New in 1.3. */ svn_error_t * svn_repos_authz_check_access(svn_authz_t *authz, const char *repos_name, const char *path, const char *user, svn_repos_authz_access_t required_access, svn_boolean_t *access_granted, apr_pool_t *pool); =0C /** Revision Access Levels * * Like most version control systems, access to versioned objects in * Subversion is determined on primarily path-based system. Users = either * do or don't have the ability to read a given path. * * However, unlike many version control systems where versioned objects * maintain their own distinct version information (revision numbers, * authors, log messages, change timestamps, etc.), Subversion binds * multiple paths changed as part of a single commit operation into a * set, calls the whole thing a revision, and hangs commit metadata * (author, date, log message, etc.) off of that revision. So, commit * metadata is shared across all the paths changed as part of a given * commit operation. * * It is common (or, at least, we hope it is) for log messages to give * detailed information about changes made in the commit to which the = log * message is attached. Such information might include a mention of all * the files changed, what was changed in them, and so on. But this * causes a problem when presenting information to readers who aren't * authorized to read every path in the repository. Simply knowing that * a given path exists may be a security leak, even if the user can't = see * the contents of the data located at that path. * * So Subversion does what it reasonably can to prevent the leak of this * information, and does so via a staged revision access policy. A * reader can be said to have one of three levels of access to a given * revision's metadata, based solely on the reader's access rights to = the * paths changed or copied in that revision: * * 'full access' -- Granted when the reader has access to all paths * changed or copied in the revision, or when no paths were * changed in the revision at all, this access level permits * full visibility of all revision property names and values, * and the full changed-paths information. * * 'no access' -- Granted when the reader does not have access to any * paths changed or copied in the revision, this access level * denies the reader access to all revision properties and all * changed-paths information. * * 'partial access' -- Granted when the reader has access to at least * one, but not all, of the paths changed or copied in the = revision, * this access level permits visibility of the svn:date and * svn:author revision properties and only the paths of the * changed-paths information to which the reader has access. * */ /** An enum defining levels of revision access. * * @since New in 1.5. */ typedef enum svn_repos_revision_access_level_t { /** no access allowed to the revision properties and all changed-paths * information. */ svn_repos_revision_access_none, /** access granted to some (svn:date and svn:author) revision = properties and * changed-paths information on paths the read has access to. */ svn_repos_revision_access_partial, /** access granted to all revision properites and changed-paths * information. */ svn_repos_revision_access_full } svn_repos_revision_access_level_t; /** * Set @a access to the access level granted for @a revision in @a * repos, as determined by consulting the @a authz_read_func callback * function and its associated @a authz_read_baton. * * @a authz_read_func may be @c NULL, in which case @a access will be * set to #svn_repos_revision_access_full. * * @since New in 1.5. */ svn_error_t * svn_repos_check_revision_access(svn_repos_revision_access_level_t = *access_level, svn_repos_t *repos, svn_revnum_t revision, svn_repos_authz_func_t authz_read_func, void *authz_read_baton, apr_pool_t *pool); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* SVN_REPOS_H */ ------=_NextPart_000_000B_01D2755C.0AF560E0 Content-Type: text/plain; name="dump.c" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="dump.c" /* dump.c --- writing filesystem contents into a portable 'dumpfile' = format. * * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * 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. * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ #include #include "svn_private_config.h" #include "svn_pools.h" #include "svn_error.h" #include "svn_fs.h" #include "svn_hash.h" #include "svn_iter.h" #include "svn_repos.h" #include "svn_string.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_time.h" #include "svn_checksum.h" #include "svn_props.h" #include "svn_sorts.h" #include "private/svn_repos_private.h" #include "private/svn_mergeinfo_private.h" #include "private/svn_fs_private.h" #include "private/svn_sorts_private.h" #include "private/svn_utf_private.h" #include "private/svn_cache.h" #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) /*----------------------------------------------------------------------*= / =0C /* To be able to check whether a path exists in the current revision (as changes come in), we need to track the relevant tree changes. In particular, we remember deletions, additions and copies including their copy-from info. Since the dump performs a pre-order tree walk, we only need to store the data for the stack of parent folders. The problem that we are trying to solve is that the dump receives transforming operations whose validity depends on previous operations in the same revision but cannot be checked against the final state as stored in the repository as that is the state *after* we applied the respective tree changes. Note that the tracker functions don't perform any sanity or validity checks. Those higher-level tests have to be done in the calling = code. However, there is no way to corrupt the data structure using the provided functions. */ /* Single entry in the path tracker. Not all levels along the path hierarchy do need to have an instance of this struct but only those that got changed by a tree modification. Please note that the path info in this struct is stored in re-usable stringbuf objects such that we don't need to allocate more memory = than the longest path we encounter. */ typedef struct path_tracker_entry_t { /* path in the current tree */ svn_stringbuf_t *path; /* copy-from path (must be empty if COPYFROM_REV is = SVN_INVALID_REVNUM) */ svn_stringbuf_t *copyfrom_path; /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements that don't copy history, i.e. with no sub-tree) */ svn_revnum_t copyfrom_rev; /* if FALSE, PATH has been deleted */ svn_boolean_t exists; } path_tracker_entry_t; /* Tracks all tree modifications above the current path. */ typedef struct path_tracker_t { /* Container for all relevant tree changes in depth order. May contain more entries than DEPTH to allow for reusing memory. Only entries 0 .. DEPTH-1 are valid. */ apr_array_header_t *stack; /* Number of relevant entries in STACK. May be 0 */ int depth; /* Revision that we current track. If DEPTH is 0, paths are exist in REVISION exactly when they exist in REVISION-1. This applies only to the current state of our tree walk. */ svn_revnum_t revision; /* Allocate container entries here. */ apr_pool_t *pool; } path_tracker_t; /* Return a new path tracker object for REVISION, allocated in POOL. */ static path_tracker_t * tracker_create(svn_revnum_t revision, apr_pool_t *pool) { path_tracker_t *result =3D apr_pcalloc(pool, sizeof(*result)); result->stack =3D apr_array_make(pool, 16, = sizeof(path_tracker_entry_t)); result->revision =3D revision; result->pool =3D pool; return result; } /* Remove all entries from TRACKER that are not relevant to PATH = anymore. * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to * parent folders but not to PATH itself. * * This internal function implicitly updates the tracker state during = the * tree by removing "past" entries. Other functions will add entries = when * we encounter a new tree change. */ static void tracker_trim(path_tracker_t *tracker, const char *path, svn_boolean_t allow_exact_match) { /* remove everything that is unrelated to PATH. Note that TRACKER->STACK is depth-ordered, i.e. stack[N] is a (maybe indirect) parent of stack[N+1] for N+1 < DEPTH. */ for (; tracker->depth; --tracker->depth) { path_tracker_entry_t *parent =3D &APR_ARRAY_IDX(tracker->stack, tracker->depth - 1, = path_tracker_entry_t); const char *rel_path =3D svn_dirent_skip_ancestor(parent->path->data, path); /* always keep parents. Keep exact matches when allowed. */ if (rel_path && (allow_exact_match || *rel_path !=3D '\0')) break; } } /* Using TRACKER, check what path at what revision in the repository = must be checked to decide that whether PATH exists. Return the info in *ORIG_PATH and *ORIG_REV, respectively. If the path is known to not exist, *ORIG_PATH will be NULL and = *ORIG_REV will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH has just been added in the revision currently being tracked. Use POOL for allocations. Note that *ORIG_PATH may be allocated in = POOL, a reference to internal data with the same lifetime as TRACKER or = just PATH. */ static void tracker_lookup(const char **orig_path, svn_revnum_t *orig_rev, path_tracker_t *tracker, const char *path, apr_pool_t *pool) { tracker_trim(tracker, path, TRUE); if (tracker->depth =3D=3D 0) { /* no tree changes -> paths are the same as in the previous rev. = */ *orig_path =3D path; *orig_rev =3D tracker->revision - 1; } else { path_tracker_entry_t *parent =3D &APR_ARRAY_IDX(tracker->stack, tracker->depth - 1, = path_tracker_entry_t); if (parent->exists) { const char *rel_path =3D svn_dirent_skip_ancestor(parent->path->data, path); if (parent->copyfrom_rev !=3D SVN_INVALID_REVNUM) { /* parent is a copy with history. Translate path. */ *orig_path =3D = svn_dirent_join(parent->copyfrom_path->data, rel_path, pool); *orig_rev =3D parent->copyfrom_rev; } else if (*rel_path =3D=3D '\0') { /* added in this revision with no history */ *orig_path =3D path; *orig_rev =3D tracker->revision; } else { /* parent got added but not this path */ *orig_path =3D NULL; *orig_rev =3D SVN_INVALID_REVNUM; } } else { /* (maybe parent) path has been deleted */ *orig_path =3D NULL; *orig_rev =3D SVN_INVALID_REVNUM; } } } /* Return a reference to the stack entry in TRACKER for PATH. If no suitable entry exists, add one. Implicitly updates the tracked tree location. Only the PATH member of the result is being updated. All other = members will have undefined values. */ static path_tracker_entry_t * tracker_add_entry(path_tracker_t *tracker, const char *path) { path_tracker_entry_t *entry; tracker_trim(tracker, path, FALSE); if (tracker->depth =3D=3D tracker->stack->nelts) { entry =3D apr_array_push(tracker->stack); entry->path =3D svn_stringbuf_create_empty(tracker->pool); entry->copyfrom_path =3D = svn_stringbuf_create_empty(tracker->pool); } else { entry =3D &APR_ARRAY_IDX(tracker->stack, tracker->depth, path_tracker_entry_t); } svn_stringbuf_set(entry->path, path); ++tracker->depth; return entry; } /* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to PATH in the tracked revision. */ static void tracker_path_copy(path_tracker_t *tracker, const char *path, const char *copyfrom_path, svn_revnum_t copyfrom_rev) { path_tracker_entry_t *entry =3D tracker_add_entry(tracker, path); svn_stringbuf_set(entry->copyfrom_path, copyfrom_path); entry->copyfrom_rev =3D copyfrom_rev; entry->exists =3D TRUE; } /* Update the TRACKER with a plain addition of PATH (without history). */ static void tracker_path_add(path_tracker_t *tracker, const char *path) { path_tracker_entry_t *entry =3D tracker_add_entry(tracker, path); svn_stringbuf_setempty(entry->copyfrom_path); entry->copyfrom_rev =3D SVN_INVALID_REVNUM; entry->exists =3D TRUE; } /* Update the TRACKER with a replacement of PATH with a plain addition (without history). */ static void tracker_path_replace(path_tracker_t *tracker, const char *path) { /* this will implicitly purge all previous sub-tree info from STACK. Thus, no need to tack the deletion explicitly. */ tracker_path_add(tracker, path); } /* Update the TRACKER with a deletion of PATH. */ static void tracker_path_delete(path_tracker_t *tracker, const char *path) { path_tracker_entry_t *entry =3D tracker_add_entry(tracker, path); svn_stringbuf_setempty(entry->copyfrom_path); entry->copyfrom_rev =3D SVN_INVALID_REVNUM; entry->exists =3D FALSE; } /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, in which case the delta will be computed against an empty file, as per the svn_fs_get_file_delta_stream docstring. Record the length of the temporary file in *LEN, and rewind the file before returning. */ static svn_error_t * store_delta(apr_file_t **tempfile, svn_filesize_t *len, svn_fs_root_t *oldroot, const char *oldpath, svn_fs_root_t *newroot, const char *newpath, apr_pool_t = *pool) { svn_stream_t *temp_stream; apr_off_t offset; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t wh; void *whb; /* Create a temporary file and open a stream to it. Note that we need the file handle in order to rewind it. */ SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); temp_stream =3D svn_stream_from_aprfile2(*tempfile, TRUE, pool); /* Compute the delta and send it to the temporary file. */ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, newroot, newpath, pool)); svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0, SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); /* Get the length of the temporary file and rewind it. */ SVN_ERR(svn_io_file_get_offset(&offset, *tempfile, pool)); *len =3D offset; offset =3D 0; return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); } /* Send a notification of type #svn_repos_notify_warning, subtype = WARNING, with message WARNING_FMT formatted with the remaining variable = arguments. Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON. */ __attribute__((format(printf, 5, 6))) static void notify_warning(apr_pool_t *scratch_pool, svn_repos_notify_func_t notify_func, void *notify_baton, svn_repos_notify_warning_t warning, const char *warning_fmt, ...) { va_list va; svn_repos_notify_t *notify; if (notify_func =3D=3D NULL) return; notify =3D svn_repos_notify_create(svn_repos_notify_warning, = scratch_pool); notify->warning =3D warning; va_start(va, warning_fmt); notify->warning_str =3D apr_pvsprintf(scratch_pool, warning_fmt, va); va_end(va); notify_func(notify_baton, notify, scratch_pool); } =0C /*----------------------------------------------------------------------*= / =0C /* Write to STREAM the header in HEADERS named KEY, if present. */ static svn_error_t * write_header(svn_stream_t *stream, apr_hash_t *headers, const char *key, apr_pool_t *scratch_pool) { const char *val =3D svn_hash_gets(headers, key); if (val) { SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s: %s\n", key, val)); } return SVN_NO_ERROR; } /* Write headers, in arbitrary order. * ### TODO: use a stable order * ### Modifies HEADERS. */ static svn_error_t * write_revision_headers_v1651614(svn_stream_t *stream, apr_hash_t *headers, apr_pool_t *scratch_pool) { const char **h; apr_hash_index_t *hi; static const char *revision_headers_order[] =3D { SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */ NULL }; /* Write some headers in a given order */ for (h =3D revision_headers_order; *h; h++) { SVN_ERR(write_header(stream, headers, *h, scratch_pool)); svn_hash_sets(headers, *h, NULL); } /* Write any and all remaining headers except Content-length. * ### TODO: use a stable order */ for (hi =3D apr_hash_first(scratch_pool, headers); hi; hi =3D = apr_hash_next(hi)) { const char *key =3D apr_hash_this_key(hi); if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) !=3D 0) SVN_ERR(write_header(stream, headers, key, scratch_pool)); } /* Content-length must be last */ SVN_ERR(write_header(stream, headers, = SVN_REPOS_DUMPFILE_CONTENT_LENGTH, scratch_pool)); return SVN_NO_ERROR; } /* Write headers using fixed order compatible with v1.8 and before. * * Order of keys: * 1) SVN_REPOS_DUMPFILE_CONTENT_LENGTH * 2) SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH * 3) SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 * 4) SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 * 5) rest of them as presented by apr_hash_first function. */ static svn_error_t * write_revision_headers_svn_4668(svn_stream_t *stream, apr_hash_t *headers, apr_pool_t *scratch_pool) { const char **h; apr_hash_index_t *hi; static const char *revision_headers_order[] =3D { SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */ NULL }; /* Write some headers in a given order */ for (h =3D revision_headers_order; *h; h++) { SVN_ERR(write_header(stream, headers, *h, scratch_pool)); svn_hash_sets(headers, *h, NULL); } /* This will be the first key written. */ SVN_ERR(write_header(stream, headers, = SVN_REPOS_DUMPFILE_CONTENT_LENGTH, scratch_pool)); /* This will be the second key written. */ SVN_ERR(write_header(stream, headers, = SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH, scratch_pool)); /* This will be the third key written. */ SVN_ERR(write_header(stream, headers, = SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, scratch_pool)); /* This will be the forth key written. */ SVN_ERR(write_header(stream, headers, = SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, scratch_pool)); /* Write any and all remaining headers except those listed above. */ for (hi =3D apr_hash_first(scratch_pool, headers); hi; hi =3D = apr_hash_next(hi)) { const char *key =3D apr_hash_this_key(hi); if ((strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) !=3D 0) && (strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH) !=3D 0) = && (strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1) !=3D 0) && (strcmp(key, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5) !=3D 0) ) SVN_ERR(write_header(stream, headers, key, scratch_pool)); } return SVN_NO_ERROR; } /* Universal function call before choosing desired property order. */ static svn_error_t * write_revision_headers(svn_stream_t *stream, apr_hash_t *headers, apr_pool_t *scratch_pool, svn_boolean_t pre_1_8_dump) { if (pre_1_8_dump) return write_revision_headers_svn_4668( stream, = headers, scratch_pool ); return write_revision_headers_v1651614( stream, headers, scratch_pool = ); } /* A header entry: the element type of the apr_array_header_t which is * the real type of svn_repos__dumpfile_headers_t. */ typedef struct svn_repos__dumpfile_header_entry_t { const char *key, *val; } svn_repos__dumpfile_header_entry_t; svn_repos__dumpfile_headers_t * svn_repos__dumpfile_headers_create(apr_pool_t *pool) { svn_repos__dumpfile_headers_t *headers =3D apr_array_make(pool, 5, = sizeof(svn_repos__dumpfile_header_entry_t)); return headers; } void svn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers, const char *key, const char *val) { svn_repos__dumpfile_header_entry_t *h =3D &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); h->key =3D apr_pstrdup(headers->pool, key); h->val =3D apr_pstrdup(headers->pool, val); } void svn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers, const char *key, const char *val_fmt, ...) { va_list ap; svn_repos__dumpfile_header_entry_t *h =3D &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); h->key =3D apr_pstrdup(headers->pool, key); va_start(ap, val_fmt); h->val =3D apr_pvsprintf(headers->pool, val_fmt, ap); va_end(ap); } svn_error_t * svn_repos__dump_headers(svn_stream_t *stream, svn_repos__dumpfile_headers_t *headers, apr_pool_t *scratch_pool) { int i; for (i =3D 0; i < headers->nelts; i++) { svn_repos__dumpfile_header_entry_t *h =3D &APR_ARRAY_IDX(headers, i, = svn_repos__dumpfile_header_entry_t); SVN_ERR(svn_stream_printf(stream, scratch_pool, "%s: %s\n", h->key, h->val)); } /* End of headers */ SVN_ERR(svn_stream_puts(stream, "\n")); return SVN_NO_ERROR; } svn_error_t * svn_repos__dump_revision_record(svn_stream_t *dump_stream, svn_revnum_t revision, apr_hash_t *extra_headers, apr_hash_t *revprops, svn_boolean_t props_section_always, apr_pool_t *scratch_pool, svn_boolean_t pre_1_8_dump ) { svn_stringbuf_t *propstring =3D NULL; apr_hash_t *headers; if (extra_headers) headers =3D apr_hash_copy(scratch_pool, extra_headers); else headers =3D apr_hash_make(scratch_pool); /* ### someday write a revision-content-checksum */ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER, apr_psprintf(scratch_pool, "%ld", revision)); if (apr_hash_count(revprops) || props_section_always) { svn_stream_t *propstream; propstring =3D svn_stringbuf_create_empty(scratch_pool); propstream =3D svn_stream_from_stringbuf(propstring, = scratch_pool); SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", = scratch_pool)); SVN_ERR(svn_stream_close(propstream)); svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, apr_psprintf(scratch_pool, "%" APR_SIZE_T_FMT, propstring->len)); } /* Write out a regular Content-length header for the benefit of non-Subversion RFC-822 parsers. */ svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, apr_psprintf(scratch_pool, "%" APR_SIZE_T_FMT, propstring->len)); SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool, = pre_1_8_dump)); /* End of headers */ SVN_ERR(svn_stream_puts(dump_stream, "\n")); /* Property data. */ if (propstring) { SVN_ERR(svn_stream_write(dump_stream, propstring->data, = &propstring->len)); } /* put an end to revision */ SVN_ERR(svn_stream_puts(dump_stream, "\n")); return SVN_NO_ERROR; } svn_error_t * svn_repos__dump_node_record(svn_stream_t *dump_stream, svn_repos__dumpfile_headers_t *headers, svn_stringbuf_t *props_str, svn_boolean_t has_text, svn_filesize_t text_content_length, svn_boolean_t content_length_always, apr_pool_t *scratch_pool) { svn_filesize_t content_length =3D 0; /* add content-length headers */ if (props_str) { svn_repos__dumpfile_header_pushf( headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, "%" APR_SIZE_T_FMT, props_str->len); content_length +=3D props_str->len; } if (has_text) { svn_repos__dumpfile_header_pushf( headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH, "%" SVN_FILESIZE_T_FMT, text_content_length); content_length +=3D text_content_length; } if (content_length_always || props_str || has_text) { svn_repos__dumpfile_header_pushf( headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, "%" SVN_FILESIZE_T_FMT, content_length); } /* write the headers */ SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool)); /* write the props */ if (props_str) { SVN_ERR(svn_stream_write(dump_stream, props_str->data, = &props_str->len)); } return SVN_NO_ERROR; } /*----------------------------------------------------------------------*= / =0C /** An editor which dumps node-data in 'dumpfile format' to a file. **/ /* Look, mom! No file batons! */ struct edit_baton { /* The relpath which implicitly prepends all full paths coming into this editor. This will almost always be "". */ const char *path; /* The stream to dump to. */ svn_stream_t *stream; /* Send feedback here, if non-NULL */ svn_repos_notify_func_t notify_func; void *notify_baton; /* The fs revision root, so we can read the contents of paths. */ svn_fs_root_t *fs_root; svn_revnum_t current_rev; /* The fs, so we can grab historic information if needed. */ svn_fs_t *fs; /* True if dumped nodes should output deltas instead of full text. */ svn_boolean_t use_deltas; /* True if this "dump" is in fact a verify. */ svn_boolean_t verify; /* True if checking UCS normalization during a verify. */ svn_boolean_t check_normalization; /* The first revision dumped in this dumpstream. */ svn_revnum_t oldest_dumped_rev; /* If not NULL, set to true if any references to revisions older than OLDEST_DUMPED_REV were found in the dumpstream. */ svn_boolean_t *found_old_reference; /* If not NULL, set to true if any mergeinfo was dumped which contains revisions older than OLDEST_DUMPED_REV. */ svn_boolean_t *found_old_mergeinfo; /* Structure allows us to verify the paths currently being dumped. If NULL, validity checks are being skipped. */ path_tracker_t *path_tracker; }; struct dir_baton { struct edit_baton *edit_baton; /* has this directory been written to the output stream? */ svn_boolean_t written_out; /* the repository relpath associated with this directory */ const char *path; /* The comparison repository relpath and revision of this directory. If both of these are valid, use them as a source against which to compare the directory instead of the default comparison source of PATH in the previous revision. */ const char *cmp_path; svn_revnum_t cmp_rev; /* hash of paths that need to be deleted, though some -might- be replaced. maps const char * paths to this dir_baton. (they're full paths, because that's what the editor driver gives us. but really, they're all within this directory.) */ apr_hash_t *deleted_entries; /* A flag indicating that new entries have been added to this directory in this revision. Used to optimize detection of UCS representation collisions; we will only check for that in revisions where new names appear in the directory. */ svn_boolean_t check_name_collision; /* pool to be used for deleting the hash items */ apr_pool_t *pool; }; /* Make a directory baton to represent the directory was path (relative to EDIT_BATON's path) is PATH. CMP_PATH/CMP_REV are the path/revision against which this directory should be compared for changes. If either is omitted (NULL for the path, SVN_INVALID_REVNUM for the rev), just compare this directory PATH against itself in the previous revision. PB is the directory baton of this directory's parent, or NULL if this is the top-level directory of the edit. Perform all allocations in POOL. */ static struct dir_baton * make_dir_baton(const char *path, const char *cmp_path, svn_revnum_t cmp_rev, void *edit_baton, struct dir_baton *pb, apr_pool_t *pool) { struct edit_baton *eb =3D edit_baton; struct dir_baton *new_db =3D apr_pcalloc(pool, sizeof(*new_db)); const char *full_path; /* A path relative to nothing? I don't think so. */ SVN_ERR_ASSERT_NO_RETURN(!path || pb); /* Construct the full path of this node. */ if (pb) full_path =3D svn_relpath_join(eb->path, path, pool); else full_path =3D apr_pstrdup(pool, eb->path); /* Remove leading slashes from copyfrom paths. */ if (cmp_path) cmp_path =3D svn_relpath_canonicalize(cmp_path, pool); new_db->edit_baton =3D eb; new_db->path =3D full_path; new_db->cmp_path =3D cmp_path; new_db->cmp_rev =3D cmp_rev; new_db->written_out =3D FALSE; new_db->deleted_entries =3D apr_hash_make(pool); new_db->check_name_collision =3D FALSE; new_db->pool =3D pool; return new_db; } static svn_error_t * fetch_kind_func(svn_node_kind_t *kind, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *scratch_pool); /* Return an error when PATH in REVISION does not exist or is of a different kind than EXPECTED_KIND. If the latter is = svn_node_unknown, skip that check. Use EB for context information. If REVISION is the current revision, use EB's path tracker to follow renames, deletions, etc. Use SCRATCH_POOL for temporary allocations. No-op if EB's path tracker has not been initialized. */ static svn_error_t * node_must_exist(struct edit_baton *eb, const char *path, svn_revnum_t revision, svn_node_kind_t expected_kind, apr_pool_t *scratch_pool) { svn_node_kind_t kind =3D svn_node_none; /* in case the caller is trying something stupid ... */ if (eb->path_tracker =3D=3D NULL) return SVN_NO_ERROR; /* paths pertaining to the revision currently being processed must be translated / checked using our path tracker. */ if (revision =3D=3D eb->path_tracker->revision) tracker_lookup(&path, &revision, eb->path_tracker, path, = scratch_pool); /* determine the node type (default: no such node) */ if (path) SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); /* check results */ if (kind =3D=3D svn_node_none) return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' not found in r%ld."), path, revision); if (expected_kind !=3D kind && expected_kind !=3D svn_node_unknown) return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Unexpected node kind %d for '%s' at = r%ld. " "Expected kind was %d."), kind, path, revision, expected_kind); return SVN_NO_ERROR; } /* Return an error when PATH exists in REVISION. Use EB for context information. If REVISION is the current revision, use EB's path tracker to follow renames, deletions, etc. Use SCRATCH_POOL for temporary allocations. No-op if EB's path tracker has not been initialized. */ static svn_error_t * node_must_not_exist(struct edit_baton *eb, const char *path, svn_revnum_t revision, apr_pool_t *scratch_pool) { svn_node_kind_t kind =3D svn_node_none; /* in case the caller is trying something stupid ... */ if (eb->path_tracker =3D=3D NULL) return SVN_NO_ERROR; /* paths pertaining to the revision currently being processed must be translated / checked using our path tracker. */ if (revision =3D=3D eb->path_tracker->revision) tracker_lookup(&path, &revision, eb->path_tracker, path, = scratch_pool); /* determine the node type (default: no such node) */ if (path) SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); /* check results */ if (kind !=3D svn_node_none) return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, _("Path '%s' exists in r%ld."), path, revision); return SVN_NO_ERROR; } /* If the mergeinfo in MERGEINFO_STR refers to any revisions older than * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to = TRUE, * otherwise leave *FOUND_OLD_MERGEINFO unchanged. */ static svn_error_t * verify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo, const char *mergeinfo_str, svn_revnum_t oldest_dumped_rev, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool) { svn_mergeinfo_t mergeinfo, old_mergeinfo; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool)); SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( &old_mergeinfo, mergeinfo, oldest_dumped_rev - 1, 0, TRUE, pool, pool)); if (apr_hash_count(old_mergeinfo)) { notify_warning(pool, notify_func, notify_baton, svn_repos_notify_warning_found_old_mergeinfo, _("Mergeinfo referencing revision(s) prior " "to the oldest dumped revision (r%ld). " "Loading this dump may result in invalid " "mergeinfo."), oldest_dumped_rev); if (found_old_mergeinfo) *found_old_mergeinfo =3D TRUE; } return SVN_NO_ERROR; } /* Unique string pointers used by verify_mergeinfo_normalization() and check_name_collision() */ static const char normalized_unique[] =3D "normalized_unique"; static const char normalized_collision[] =3D "normalized_collision"; /* Baton for extract_mergeinfo_paths */ struct extract_mergeinfo_paths_baton { apr_hash_t *result; svn_boolean_t normalize; svn_membuf_t buffer; }; /* Hash iterator that uniquifies all keys into a single hash table, optionally normalizing them first. */ static svn_error_t * extract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, void *val, apr_pool_t *iterpool) { struct extract_mergeinfo_paths_baton *const xb =3D baton; if (xb->normalize) { const char *normkey; SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer)); svn_hash_sets(xb->result, apr_pstrdup(xb->buffer.pool, normkey), normalized_unique); } else apr_hash_set(xb->result, apr_pmemdup(xb->buffer.pool, key, klen + 1), klen, normalized_unique); return SVN_NO_ERROR; } /* Baton for filter_mergeinfo_paths */ struct filter_mergeinfo_paths_baton { apr_hash_t *paths; }; /* Compare two sets of denormalized paths from mergeinfo entries, removing duplicates. */ static svn_error_t * filter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, void *val, apr_pool_t *iterpool) { struct filter_mergeinfo_paths_baton *const fb =3D baton; if (apr_hash_get(fb->paths, key, klen)) apr_hash_set(fb->paths, key, klen, NULL); return SVN_NO_ERROR; } /* Baton used by the check_mergeinfo_normalization hash iterator. */ struct verify_mergeinfo_normalization_baton { const char* path; apr_hash_t *normalized_paths; svn_membuf_t buffer; svn_repos_notify_func_t notify_func; void *notify_baton; }; /* Hash iterator that verifies normalization and collision of paths in an svn:mergeinfo property. */ static svn_error_t * verify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t = klen, void *val, apr_pool_t *iterpool) { struct verify_mergeinfo_normalization_baton *const vb =3D baton; const char *const path =3D key; const char *normpath; const char *found; SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer)); found =3D svn_hash_gets(vb->normalized_paths, normpath); if (!found) svn_hash_sets(vb->normalized_paths, apr_pstrdup(vb->buffer.pool, normpath), normalized_unique); else if (found =3D=3D normalized_collision) /* Skip already reported collision */; else { /* Report path collision in mergeinfo */ svn_hash_sets(vb->normalized_paths, apr_pstrdup(vb->buffer.pool, normpath), normalized_collision); notify_warning(iterpool, vb->notify_func, vb->notify_baton, svn_repos_notify_warning_mergeinfo_collision, _("Duplicate representation of path '%s'" " in %s property of '%s'"), normpath, SVN_PROP_MERGEINFO, vb->path); } return SVN_NO_ERROR; } /* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the svn:mergeinfo property value being set; OLD_MERGEINFO is the previous property value, which may be NULL. Only the paths that were added in are checked, including collision checks. This minimizes the number of notifications we generate for a given mergeinfo property. */ static svn_error_t * check_mergeinfo_normalization(const char *path, const char *new_mergeinfo, const char *old_mergeinfo, svn_repos_notify_func_t notify_func, void *notify_baton, apr_pool_t *pool) { svn_mergeinfo_t mergeinfo; apr_hash_t *normalized_paths; apr_hash_t *added_paths; struct extract_mergeinfo_paths_baton extract_baton; struct verify_mergeinfo_normalization_baton verify_baton; SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool)); extract_baton.result =3D apr_hash_make(pool); extract_baton.normalize =3D FALSE; svn_membuf__create(&extract_baton.buffer, 0, pool); SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo, extract_mergeinfo_paths, &extract_baton, pool)); added_paths =3D extract_baton.result; if (old_mergeinfo) { struct filter_mergeinfo_paths_baton filter_baton; svn_mergeinfo_t oldinfo; extract_baton.result =3D apr_hash_make(pool); extract_baton.normalize =3D TRUE; SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool)); SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, extract_mergeinfo_paths, &extract_baton, pool)); normalized_paths =3D extract_baton.result; filter_baton.paths =3D added_paths; SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, filter_mergeinfo_paths, &filter_baton, pool)); } else normalized_paths =3D apr_hash_make(pool); verify_baton.path =3D path; verify_baton.normalized_paths =3D normalized_paths; verify_baton.buffer =3D extract_baton.buffer; verify_baton.notify_func =3D notify_func; verify_baton.notify_baton =3D notify_baton; SVN_ERR(svn_iter_apr_hash(NULL, added_paths, verify_mergeinfo_normalization, &verify_baton, pool)); return SVN_NO_ERROR; } /* A special case of dump_node(), for a delete record. * * The only thing special about this version is it only writes one blank * line, not two, after the headers. Why? Historical precedent for the * case where a delete record is used as part of a (delete + = add-with-history) * in implementing a replacement. * * Also it doesn't do a path-tracker check. */ static svn_error_t * dump_node_delete(svn_stream_t *stream, const char *node_relpath, apr_pool_t *pool) { svn_repos__dumpfile_headers_t *headers =3D svn_repos__dumpfile_headers_create(pool); /* Node-path: ... */ svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); /* Node-action: delete */ svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); SVN_ERR(svn_repos__dump_headers(stream, headers, pool)); return SVN_NO_ERROR; } /* This helper is the main "meat" of the editor -- it does all the work of writing a node record. Write out a node record for PATH of type KIND under EB->FS_ROOT. ACTION describes what is happening to the node (see enum = svn_node_action). Write record to writable EB->STREAM. If the node was itself copied, IS_COPY is TRUE and the path/revision of the copy source are in CMP_PATH/CMP_REV. If IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part of a copied subtree. */ static svn_error_t * dump_node(struct edit_baton *eb, const char *path, svn_node_kind_t kind, enum svn_node_action action, svn_boolean_t is_copy, const char *cmp_path, svn_revnum_t cmp_rev, apr_pool_t *pool) { svn_stringbuf_t *propstring; apr_size_t len; svn_boolean_t must_dump_text =3D FALSE, must_dump_props =3D FALSE; const char *compare_path =3D path; svn_revnum_t compare_rev =3D eb->current_rev - 1; svn_fs_root_t *compare_root =3D NULL; apr_file_t *delta_file =3D NULL; svn_repos__dumpfile_headers_t *headers =3D svn_repos__dumpfile_headers_create(pool); svn_filesize_t textlen; /* Maybe validate the path. */ if (eb->verify || eb->notify_func) { svn_error_t *err =3D svn_fs__path_valid(path, pool); if (err) { if (eb->notify_func) { char errbuf[512]; /* ### svn_strerror() magic number */ notify_warning(pool, eb->notify_func, eb->notify_baton, svn_repos_notify_warning_invalid_fspath, _("E%06d: While validating fspath '%s': = %s"), err->apr_err, path, svn_err_best_message(err, errbuf, = sizeof(errbuf))); } /* Return the error in addition to notifying about it. */ if (eb->verify) return svn_error_trace(err); else svn_error_clear(err); } } /* Write out metadata headers for this file node. */ svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_PATH, path); if (kind =3D=3D svn_node_file) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); else if (kind =3D=3D svn_node_dir) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); /* Remove leading slashes from copyfrom paths. */ if (cmp_path) cmp_path =3D svn_relpath_canonicalize(cmp_path, pool); /* Validate the comparison path/rev. */ if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) { compare_path =3D cmp_path; compare_rev =3D cmp_rev; } switch (action) { case svn_node_action_change: if (eb->path_tracker) SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, = pool), apr_psprintf(pool, _("Change invalid path '%s' in = r%ld"), path, eb->current_rev)); svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); /* either the text or props changed, or possibly both. */ SVN_ERR(svn_fs_revision_root(&compare_root, svn_fs_root_fs(eb->fs_root), compare_rev, pool)); SVN_ERR(svn_fs_props_changed(&must_dump_props, compare_root, compare_path, eb->fs_root, path, pool)); if (kind =3D=3D svn_node_file) SVN_ERR(svn_fs_contents_changed(&must_dump_text, compare_root, compare_path, eb->fs_root, path, pool)); break; case svn_node_action_delete: if (eb->path_tracker) { SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, = pool), apr_psprintf(pool, _("Deleting invalid path '%s' in = r%ld"), path, eb->current_rev)); tracker_path_delete(eb->path_tracker, path); } svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); /* we can leave this routine quietly now, don't need to dump any content. */ must_dump_text =3D FALSE; must_dump_props =3D FALSE; break; case svn_node_action_replace: if (eb->path_tracker) SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, svn_node_unknown, pool), apr_psprintf(pool, _("Replacing non-existent path '%s' in = r%ld"), path, eb->current_rev)); if (! is_copy) { if (eb->path_tracker) tracker_path_replace(eb->path_tracker, path); /* a simple delete+add, implied by a single 'replace' action. = */ svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); /* definitely need to dump all content for a replace. */ if (kind =3D=3D svn_node_file) must_dump_text =3D TRUE; must_dump_props =3D TRUE; break; } else { /* more complex: delete original, then add-with-history. */ /* ### Why not write a 'replace' record? Don't know. */ if (eb->path_tracker) { tracker_path_delete(eb->path_tracker, path); } /* ### Unusually, we end this 'delete' node record with only a = single blank line after the header block -- no extra blank = line. */ SVN_ERR(dump_node_delete(eb->stream, path, pool)); /* The remaining action is a non-replacing add-with-history */ /* action =3D svn_node_action_add; */ } /* FALL THROUGH to 'add' */ case svn_node_action_add: if (eb->path_tracker) SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool), apr_psprintf(pool, _("Adding already existing path '%s' in = r%ld"), path, eb->current_rev)); svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); if (! is_copy) { if (eb->path_tracker) tracker_path_add(eb->path_tracker, path); /* Dump all contents for a simple 'add'. */ if (kind =3D=3D svn_node_file) must_dump_text =3D TRUE; must_dump_props =3D TRUE; } else { if (eb->path_tracker) { SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev, kind, pool), apr_psprintf(pool, _("Copying from invalid path to " "'%s' in r%ld"), path, eb->current_rev)); tracker_path_copy(eb->path_tracker, path, compare_path, compare_rev); } if (!eb->verify && cmp_rev < eb->oldest_dumped_rev && eb->notify_func) { notify_warning(pool, eb->notify_func, eb->notify_baton, = svn_repos_notify_warning_found_old_reference, _("Referencing data in revision %ld," " which is older than the oldest" " dumped revision (r%ld). Loading this = dump" " into an empty repository" " will fail."), cmp_rev, eb->oldest_dumped_rev); if (eb->found_old_reference) *eb->found_old_reference =3D TRUE; } svn_repos__dumpfile_header_pushf( headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", = cmp_rev); svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path); SVN_ERR(svn_fs_revision_root(&compare_root, svn_fs_root_fs(eb->fs_root), compare_rev, pool)); /* Need to decide if the copied node had any extra textual or property mods as well. */ SVN_ERR(svn_fs_props_changed(&must_dump_props, compare_root, compare_path, eb->fs_root, path, pool)); if (kind =3D=3D svn_node_file) { svn_checksum_t *checksum; const char *hex_digest; SVN_ERR(svn_fs_contents_changed(&must_dump_text, compare_root, = compare_path, eb->fs_root, path, pool)); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, compare_root, compare_path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, = hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, = hex_digest); } } break; } if ((! must_dump_text) && (! must_dump_props)) { /* If we're not supposed to dump text or props, so be it, we can just go home. However, if either one needs to be dumped, then our dumpstream format demands that at a *minimum*, we see a lone "PROPS-END" as a divider between text and props content within the content-block. */ SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool)); len =3D 1; return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */ } /*** Start prepping content to dump... ***/ /* If we are supposed to dump properties, write out a property length header and generate a stringbuf that contains those property values here. */ if (must_dump_props) { apr_hash_t *prophash, *oldhash =3D NULL; svn_stream_t *propstream; SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); /* If this is a partial dump, then issue a warning if we dump = mergeinfo properties that refer to revisions older than the first = revision dumped. */ if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) { svn_string_t *mergeinfo_str =3D svn_hash_gets(prophash, = SVN_PROP_MERGEINFO); if (mergeinfo_str) { /* An error in verifying the mergeinfo must not prevent = dumping the data. Ignore any such error. */ svn_error_clear(verify_mergeinfo_revisions( eb->found_old_mergeinfo, mergeinfo_str->data, = eb->oldest_dumped_rev, eb->notify_func, eb->notify_baton, pool)); } } /* If we're checking UCS normalization, also parse any changed mergeinfo and warn about denormalized paths and name collisions there. */ if (eb->verify && eb->check_normalization && eb->notify_func) { /* N.B.: This hash lookup happens only once; the conditions for verifying historic mergeinfo references and checking UCS normalization are mutually exclusive. */ svn_string_t *mergeinfo_str =3D svn_hash_gets(prophash, = SVN_PROP_MERGEINFO); if (mergeinfo_str) { svn_string_t *oldinfo_str =3D NULL; if (compare_root) { SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, = compare_path, pool)); oldinfo_str =3D svn_hash_gets(oldhash, = SVN_PROP_MERGEINFO); } SVN_ERR(check_mergeinfo_normalization( path, mergeinfo_str->data, (oldinfo_str ? oldinfo_str->data : NULL), eb->notify_func, eb->notify_baton, pool)); } } if (eb->use_deltas && compare_root) { /* Fetch the old property hash to diff against and output a = header saying that our property contents are a delta. */ if (!oldhash) /* May have been set for normalization = check */ SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, = compare_path, pool)); svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); } else oldhash =3D apr_hash_make(pool); propstring =3D svn_stringbuf_create_ensure(0, pool); propstream =3D svn_stream_from_stringbuf(propstring, pool); SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, "PROPS-END", pool)); SVN_ERR(svn_stream_close(propstream)); } /* If we are supposed to dump text, write out a text length header here, and an MD5 checksum (if available). */ if (must_dump_text && (kind =3D=3D svn_node_file)) { svn_checksum_t *checksum; const char *hex_digest; if (eb->use_deltas) { /* Compute the text delta now and write it into a temporary file, so that we can find its length. Output a header saying our text contents are a delta. */ SVN_ERR(store_delta(&delta_file, &textlen, compare_root, compare_path, eb->fs_root, path, pool)); svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); if (compare_root) { SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, compare_root, compare_path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, = hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, compare_root, compare_path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, = hex_digest); } } else { /* Just fetch the length of the file. */ SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, = pool)); } SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, eb->fs_root, path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest); SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, eb->fs_root, path, FALSE, pool)); hex_digest =3D svn_checksum_to_cstring(checksum, pool); if (hex_digest) svn_repos__dumpfile_header_push( headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest); } /* 'Content-length:' is the last header before we dump the content, and is the sum of the text and prop contents lengths. We write this only for the benefit of non-Subversion RFC-822 parsers. */ SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, must_dump_props ? propstring : = NULL, must_dump_text, must_dump_text ? textlen : 0, TRUE /*content_length_always*/, pool)); /* Dump text content */ if (must_dump_text && (kind =3D=3D svn_node_file)) { svn_stream_t *contents; if (delta_file) { /* Make sure to close the underlying file when the stream is closed. */ contents =3D svn_stream_from_aprfile2(delta_file, FALSE, = pool); } else SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, = pool)); SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, = pool), NULL, NULL, pool)); } len =3D 2; return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ } static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { *root_baton =3D make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, edit_baton, NULL, pool); return SVN_NO_ERROR; } static svn_error_t * delete_entry(const char *path, svn_revnum_t revision, void *parent_baton, apr_pool_t *pool) { struct dir_baton *pb =3D parent_baton; const char *mypath =3D apr_pstrdup(pb->pool, path); /* remember this path needs to be deleted. */ svn_hash_sets(pb->deleted_entries, mypath, pb); return SVN_NO_ERROR; } static svn_error_t * add_directory(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_rev, apr_pool_t *pool, void **child_baton) { struct dir_baton *pb =3D parent_baton; struct edit_baton *eb =3D pb->edit_baton; void *was_deleted; svn_boolean_t is_copy =3D FALSE; struct dir_baton *new_db =3D make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool); /* This might be a replacement -- is the path already deleted? */ was_deleted =3D svn_hash_gets(pb->deleted_entries, path); /* Detect an add-with-history. */ is_copy =3D ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); /* Dump the node. */ SVN_ERR(dump_node(eb, path, svn_node_dir, was_deleted ? svn_node_action_replace : = svn_node_action_add, is_copy, is_copy ? copyfrom_path : NULL, is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, pool)); if (was_deleted) /* Delete the path, it's now been dumped. */ svn_hash_sets(pb->deleted_entries, path, NULL); /* Check for normalized name clashes, but only if this is actually a new name in the parent, not a replacement. */ if (!was_deleted && eb->verify && eb->check_normalization && = eb->notify_func) { pb->check_name_collision =3D TRUE; } new_db->written_out =3D TRUE; *child_baton =3D new_db; return SVN_NO_ERROR; } static svn_error_t * open_directory(const char *path, void *parent_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **child_baton) { struct dir_baton *pb =3D parent_baton; struct edit_baton *eb =3D pb->edit_baton; struct dir_baton *new_db; const char *cmp_path =3D NULL; svn_revnum_t cmp_rev =3D SVN_INVALID_REVNUM; /* If the parent directory has explicit comparison path and rev, record the same for this one. */ if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) { cmp_path =3D svn_relpath_join(pb->cmp_path, svn_relpath_basename(path, pool), = pool); cmp_rev =3D pb->cmp_rev; } new_db =3D make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool); *child_baton =3D new_db; return SVN_NO_ERROR; } static svn_error_t * close_directory(void *dir_baton, apr_pool_t *pool) { struct dir_baton *db =3D dir_baton; struct edit_baton *eb =3D db->edit_baton; apr_pool_t *subpool =3D svn_pool_create(pool); int i; apr_array_header_t *sorted_entries; /* Sort entries lexically instead of as paths. Even though the entries * are full paths they're all in the same directory (see comment in = struct * dir_baton definition). So we really want to sort by basename, in = which * case the lexical sort function is more efficient. */ sorted_entries =3D svn_sort__hash(db->deleted_entries, svn_sort_compare_items_lexically, = pool); for (i =3D 0; i < sorted_entries->nelts; i++) { const char *path =3D APR_ARRAY_IDX(sorted_entries, i, svn_sort__item_t).key; svn_pool_clear(subpool); /* By sending 'svn_node_unknown', the Node-kind: header simply = won't be written out. No big deal at all, really. The loader shouldn't care. */ SVN_ERR(dump_node(eb, path, svn_node_unknown, svn_node_action_delete, FALSE, NULL, SVN_INVALID_REVNUM, subpool)); } svn_pool_destroy(subpool); return SVN_NO_ERROR; } static svn_error_t * add_file(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_rev, apr_pool_t *pool, void **file_baton) { struct dir_baton *pb =3D parent_baton; struct edit_baton *eb =3D pb->edit_baton; void *was_deleted; svn_boolean_t is_copy =3D FALSE; /* This might be a replacement -- is the path already deleted? */ was_deleted =3D svn_hash_gets(pb->deleted_entries, path); /* Detect add-with-history. */ is_copy =3D ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); /* Dump the node. */ SVN_ERR(dump_node(eb, path, svn_node_file, was_deleted ? svn_node_action_replace : = svn_node_action_add, is_copy, is_copy ? copyfrom_path : NULL, is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, pool)); if (was_deleted) /* delete the path, it's now been dumped. */ svn_hash_sets(pb->deleted_entries, path, NULL); /* Check for normalized name clashes, but only if this is actually a new name in the parent, not a replacement. */ if (!was_deleted && eb->verify && eb->check_normalization && = eb->notify_func) { pb->check_name_collision =3D TRUE; } *file_baton =3D NULL; /* muhahahaha */ return SVN_NO_ERROR; } static svn_error_t * open_file(const char *path, void *parent_baton, svn_revnum_t ancestor_revision, apr_pool_t *pool, void **file_baton) { struct dir_baton *pb =3D parent_baton; struct edit_baton *eb =3D pb->edit_baton; const char *cmp_path =3D NULL; svn_revnum_t cmp_rev =3D SVN_INVALID_REVNUM; /* If the parent directory has explicit comparison path and rev, record the same for this one. */ if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) { cmp_path =3D svn_relpath_join(pb->cmp_path, svn_relpath_basename(path, pool), = pool); cmp_rev =3D pb->cmp_rev; } SVN_ERR(dump_node(eb, path, svn_node_file, svn_node_action_change, FALSE, cmp_path, cmp_rev, pool)); *file_baton =3D NULL; /* muhahahaha again */ return SVN_NO_ERROR; } static svn_error_t * change_dir_prop(void *parent_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { struct dir_baton *db =3D parent_baton; struct edit_baton *eb =3D db->edit_baton; /* This function is what distinguishes between a directory that is opened to merely get somewhere, vs. one that is opened because it *actually* changed by itself. Instead of recording the prop changes here, we just use this method to trigger writing the node; dump_node() finds all the changes. */ if (! db->written_out) { SVN_ERR(dump_node(eb, db->path, svn_node_dir, svn_node_action_change, /* ### We pass is_copy=3DFALSE; this might be = wrong but the parameter isn't used when = action=3Dchange. */ FALSE, db->cmp_path, db->cmp_rev, pool)); db->written_out =3D TRUE; } return SVN_NO_ERROR; } static svn_error_t * fetch_props_func(apr_hash_t **props, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct edit_baton *eb =3D baton; svn_error_t *err; svn_fs_root_t *fs_root; if (!SVN_IS_VALID_REVNUM(base_revision)) base_revision =3D eb->current_rev - 1; SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, = scratch_pool)); err =3D svn_fs_node_proplist(props, fs_root, path, result_pool); if (err && err->apr_err =3D=3D SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *props =3D apr_hash_make(result_pool); return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); return SVN_NO_ERROR; } static svn_error_t * fetch_kind_func(svn_node_kind_t *kind, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *scratch_pool) { struct edit_baton *eb =3D baton; svn_fs_root_t *fs_root; if (!SVN_IS_VALID_REVNUM(base_revision)) base_revision =3D eb->current_rev - 1; SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, = scratch_pool)); SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); return SVN_NO_ERROR; } static svn_error_t * fetch_base_func(const char **filename, void *baton, const char *path, svn_revnum_t base_revision, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct edit_baton *eb =3D baton; svn_stream_t *contents; svn_stream_t *file_stream; const char *tmp_filename; svn_error_t *err; svn_fs_root_t *fs_root; if (!SVN_IS_VALID_REVNUM(base_revision)) base_revision =3D eb->current_rev - 1; SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, = scratch_pool)); err =3D svn_fs_file_contents(&contents, fs_root, path, scratch_pool); if (err && err->apr_err =3D=3D SVN_ERR_FS_NOT_FOUND) { svn_error_clear(err); *filename =3D NULL; return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, svn_io_file_del_on_pool_cleanup, scratch_pool, scratch_pool)); SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, = scratch_pool)); *filename =3D apr_pstrdup(result_pool, tmp_filename); return SVN_NO_ERROR; } static svn_error_t * get_dump_editor(const svn_delta_editor_t **editor, void **edit_baton, svn_fs_t *fs, svn_revnum_t to_rev, const char *root_path, svn_stream_t *stream, svn_boolean_t *found_old_reference, svn_boolean_t *found_old_mergeinfo, svn_error_t *(*custom_close_directory)(void *dir_baton, apr_pool_t *scratch_pool), svn_repos_notify_func_t notify_func, void *notify_baton, svn_revnum_t oldest_dumped_rev, svn_boolean_t use_deltas, svn_boolean_t verify, svn_boolean_t check_normalization, apr_pool_t *pool) { /* Allocate an edit baton to be stored in every directory baton. Set it up for the directory baton we create here, which is the root baton. */ struct edit_baton *eb =3D apr_pcalloc(pool, sizeof(*eb)); svn_delta_editor_t *dump_editor =3D svn_delta_default_editor(pool); svn_delta_shim_callbacks_t *shim_callbacks =3D svn_delta_shim_callbacks_default(pool); /* Set up the edit baton. */ eb->stream =3D stream; eb->notify_func =3D notify_func; eb->notify_baton =3D notify_baton; eb->oldest_dumped_rev =3D oldest_dumped_rev; eb->path =3D apr_pstrdup(pool, root_path); SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); eb->fs =3D fs; eb->current_rev =3D to_rev; eb->use_deltas =3D use_deltas; eb->verify =3D verify; eb->check_normalization =3D check_normalization; eb->found_old_reference =3D found_old_reference; eb->found_old_mergeinfo =3D found_old_mergeinfo; /* In non-verification mode, we will allow anything to be dumped = because it might be an incremental dump with possible manual intervention. Also, this might be the last resort when it comes to data recovery. Else, make sure that all paths exists at their respective = revisions. */ eb->path_tracker =3D verify ? tracker_create(to_rev, pool) : NULL; /* Set up the editor. */ dump_editor->open_root =3D open_root; dump_editor->delete_entry =3D delete_entry; dump_editor->add_directory =3D add_directory; dump_editor->open_directory =3D open_directory; if (custom_close_directory) dump_editor->close_directory =3D custom_close_directory; else dump_editor->close_directory =3D close_directory; dump_editor->change_dir_prop =3D change_dir_prop; dump_editor->add_file =3D add_file; dump_editor->open_file =3D open_file; *edit_baton =3D eb; *editor =3D dump_editor; shim_callbacks->fetch_kind_func =3D fetch_kind_func; shim_callbacks->fetch_props_func =3D fetch_props_func; shim_callbacks->fetch_base_func =3D fetch_base_func; shim_callbacks->fetch_baton =3D eb; SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, = *edit_baton, NULL, NULL, shim_callbacks, pool, = pool)); return SVN_NO_ERROR; } /*----------------------------------------------------------------------*= / =0C /** The main dumping routine, svn_repos_dump_fs. **/ /* Helper for svn_repos_dump_fs. Write a revision record of REV in FS to writable STREAM, using POOL. Dump revision properties as well if INCLUDE_REVPROPS has been set. */ static svn_error_t * write_revision_record(svn_stream_t *stream, svn_fs_t *fs, svn_revnum_t rev, svn_boolean_t include_revprops, apr_pool_t *pool, svn_boolean_t pre_1_8_dump ) { apr_hash_t *props; apr_time_t timetemp; svn_string_t *datevalue; if (include_revprops) { SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, pool, = pool)); /* Run revision date properties through the time conversion to canonicalize them. */ /* ### Remove this when it is no longer needed for sure. */ datevalue =3D svn_hash_gets(props, SVN_PROP_REVISION_DATE); if (datevalue) { SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, = pool)); datevalue =3D svn_string_create(svn_time_to_cstring(timetemp, = pool), pool); svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); } } else { /* Although we won't use it, we still need this container for the call below. */ props =3D apr_hash_make(pool); } SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props, include_revprops, pool, pre_1_8_dump)); return SVN_NO_ERROR; } /* The main dumper. */ svn_error_t * svn_repos_dump_fs4(svn_repos_t *repos, svn_stream_t *stream, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t incremental, svn_boolean_t use_deltas, svn_boolean_t include_revprops, svn_boolean_t include_changes, svn_boolean_t pre_1_8_dump, svn_repos_notify_func_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) { const svn_delta_editor_t *dump_editor; void *dump_edit_baton =3D NULL; svn_revnum_t rev; svn_fs_t *fs =3D svn_repos_fs(repos); apr_pool_t *iterpool =3D svn_pool_create(pool); svn_revnum_t youngest; const char *uuid; int version; svn_boolean_t found_old_reference =3D FALSE; svn_boolean_t found_old_mergeinfo =3D FALSE; svn_repos_notify_t *notify; /* Make sure we catch up on the latest revprop changes. This is the = only * time we will refresh the revprop data in this query. */ SVN_ERR(svn_fs_refresh_revision_props(fs, pool)); /* Determine the current youngest revision of the filesystem. */ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); /* Use default vals if necessary. */ if (! SVN_IS_VALID_REVNUM(start_rev)) start_rev =3D 0; if (! SVN_IS_VALID_REVNUM(end_rev)) end_rev =3D youngest; if (! stream) stream =3D svn_stream_empty(pool); /* Validate the revisions. */ if (start_rev > end_rev) return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, _("Start revision %ld" " is greater than end revision %ld"), start_rev, end_rev); if (end_rev > youngest) return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, _("End revision %ld is invalid " "(youngest revision is %ld)"), end_rev, youngest); /* Write out the UUID. */ SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); /* If we're not using deltas, use the previous version, for compatibility with svn 1.0.x. */ version =3D SVN_REPOS_DUMPFILE_FORMAT_VERSION; if (!use_deltas) version--; /* Write out "general" metadata for the dumpfile. In this case, a magic header followed by a dumpfile format version. */ SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", version)); SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID ": %s\n\n", uuid)); /* Create a notify object that we can reuse in the loop. */ if (notify_func) notify =3D svn_repos_notify_create(svn_repos_notify_dump_rev_end, pool); /* Main loop: we're going to dump revision REV. */ for (rev =3D start_rev; rev <=3D end_rev; rev++) { svn_fs_root_t *to_root; svn_boolean_t use_deltas_for_rev; svn_pool_clear(iterpool); /* Check for cancellation. */ if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); /* Write the revision record. */ SVN_ERR(write_revision_record(stream, fs, rev, include_revprops, iterpool, pre_1_8_dump)); /* When dumping revision 0, we just write out the revision record. The parser might want to use its properties. If we don't want revision changes at all, skip in any case. */ if (rev =3D=3D 0 || !include_changes) goto loop_end; /* Fetch the editor which dumps nodes to a file. Regardless of what we've been told, don't use deltas for the first rev of a non-incremental dump. */ use_deltas_for_rev =3D use_deltas && (incremental || rev !=3D = start_rev); SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev, "", stream, &found_old_reference, &found_old_mergeinfo, NULL, notify_func, notify_baton, start_rev, use_deltas_for_rev, FALSE, = FALSE, iterpool)); /* Drive the editor in one way or another. */ SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); /* If this is the first revision of a non-incremental dump, we're in for a full tree dump. Otherwise, we want to simply replay the revision. */ if ((rev =3D=3D start_rev) && (! incremental)) { /* Compare against revision 0, so everything appears to be = added. */ svn_fs_root_t *from_root; SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, iterpool)); SVN_ERR(svn_repos_dir_delta2(from_root, "", "", to_root, "", dump_editor, dump_edit_baton, NULL, NULL, FALSE, /* don't send text-deltas = */ svn_depth_infinity, FALSE, /* don't send entry props = */ FALSE, /* don't ignore ancestry = */ iterpool)); } else { /* The normal case: compare consecutive revs. */ SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, = FALSE, dump_editor, dump_edit_baton, NULL, NULL, iterpool)); /* While our editor close_edit implementation is a no-op, we = still do this for completeness. */ SVN_ERR(dump_editor->close_edit(dump_edit_baton, iterpool)); } loop_end: if (notify_func) { notify->revision =3D rev; notify_func(notify_baton, notify, iterpool); } } if (notify_func) { /* Did we issue any warnings about references to revisions older = than the oldest dumped revision? If so, then issue a final generic warning, since the inline warnings already issued might easily = be missed. */ notify =3D svn_repos_notify_create(svn_repos_notify_dump_end, = iterpool); notify_func(notify_baton, notify, iterpool); if (found_old_reference) { notify_warning(iterpool, notify_func, notify_baton, svn_repos_notify_warning_found_old_reference, _("The range of revisions dumped " "contained references to " "copy sources outside that " "range.")); } /* Ditto if we issued any warnings about old revisions referenced in dumped mergeinfo. */ if (found_old_mergeinfo) { notify_warning(iterpool, notify_func, notify_baton, svn_repos_notify_warning_found_old_mergeinfo, _("The range of revisions dumped " "contained mergeinfo " "which reference revisions outside " "that range.")); } } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /*----------------------------------------------------------------------*= / =0C /* verify, based on dump */ /* Creating a new revision that changes /A/B/E/bravo means creating new directory listings for /, /A, /A/B, and /A/B/E in the new revision, = with each entry not changed in the new revision a link back to the entry = in a previous revision. svn_repos_replay()ing a revision does not verify = that those links are correct. For paths actually changed in the revision we verify, we get = directory contents or file length twice: once in the dump editor, and once = here. We could create a new verify baton, store in it the changed paths, = and skip those here, but that means building an entire wrapper editor and managing two levels of batons. The impact from checking these = entries twice should be minimal, while the code to avoid it is not. */ static svn_error_t * verify_directory_entry(void *baton, const void *key, apr_ssize_t klen, void *val, apr_pool_t *pool) { struct dir_baton *db =3D baton; svn_fs_dirent_t *dirent =3D (svn_fs_dirent_t *)val; char *path; svn_boolean_t right_kind; path =3D svn_relpath_join(db->path, (const char *)key, pool); /* since we can't access the directory entries directly by their ID, we need to navigate from the FS_ROOT to them (relatively expensive because we may start at a never rev than the last change to node). We check that the node kind stored in the noderev matches the dir entry. This also ensures that all entries point to valid noderevs. */ switch (dirent->kind) { case svn_node_dir: SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, = pool)); if (!right_kind) return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Node '%s' is not a directory."), path); break; case svn_node_file: SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, = pool)); if (!right_kind) return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Node '%s' is not a file."), path); break; default: return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("Unexpected node kind %d for '%s'"), dirent->kind, path); } return SVN_NO_ERROR; } /* Baton used by the check_name_collision hash iterator. */ struct check_name_collision_baton { struct dir_baton *dir_baton; apr_hash_t *normalized; svn_membuf_t buffer; }; /* Scan the directory and report all entry names that differ only in Unicode character representation. */ static svn_error_t * check_name_collision(void *baton, const void *key, apr_ssize_t klen, void *val, apr_pool_t *iterpool) { struct check_name_collision_baton *const cb =3D baton; const char *name; const char *found; SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer)); found =3D svn_hash_gets(cb->normalized, name); if (!found) svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), normalized_unique); else if (found =3D=3D normalized_collision) /* Skip already reported collision */; else { struct dir_baton *const db =3D cb->dir_baton; struct edit_baton *const eb =3D db->edit_baton; const char* normpath; svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), normalized_collision); SVN_ERR(svn_utf__normalize( &normpath, svn_relpath_join(db->path, name, iterpool), SVN_UTF__UNKNOWN_LENGTH, &cb->buffer)); notify_warning(iterpool, eb->notify_func, eb->notify_baton, svn_repos_notify_warning_name_collision, _("Duplicate representation of path '%s'"), = normpath); } return SVN_NO_ERROR; } static svn_error_t * verify_close_directory(void *dir_baton, apr_pool_t *pool) { struct dir_baton *db =3D dir_baton; apr_hash_t *dirents; SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, db->path, pool)); SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, dir_baton, pool)); if (db->check_name_collision) { struct check_name_collision_baton check_baton; check_baton.dir_baton =3D db; check_baton.normalized =3D apr_hash_make(pool); svn_membuf__create(&check_baton.buffer, 0, pool); SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision, &check_baton, pool)); } return close_directory(dir_baton, pool); } /* Verify revision REV in file system FS. */ static svn_error_t * verify_one_revision(svn_fs_t *fs, svn_revnum_t rev, svn_repos_notify_func_t notify_func, void *notify_baton, svn_revnum_t start_rev, svn_boolean_t check_normalization, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *scratch_pool) { const svn_delta_editor_t *dump_editor; void *dump_edit_baton; svn_fs_root_t *to_root; apr_hash_t *props; const svn_delta_editor_t *cancel_editor; void *cancel_edit_baton; /* Get cancellable dump editor, but with our close_directory = handler.*/ SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev, "", svn_stream_empty(scratch_pool), NULL, NULL, verify_close_directory, notify_func, notify_baton, start_rev, FALSE, TRUE, /* use_deltas, verify */ check_normalization, scratch_pool)); SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, dump_editor, = dump_edit_baton, &cancel_editor, &cancel_edit_baton, scratch_pool)); SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool)); SVN_ERR(svn_fs_verify_root(to_root, scratch_pool)); SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, cancel_editor, cancel_edit_baton, NULL, NULL, scratch_pool)); /* While our editor close_edit implementation is a no-op, we still do this for completeness. */ SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool)); SVN_ERR(svn_fs_revision_proplist2(&props, fs, rev, FALSE, = scratch_pool, scratch_pool)); return SVN_NO_ERROR; } /* Baton type used for forwarding notifications from FS API to REPOS = API. */ struct verify_fs_notify_func_baton_t { /* notification function to call (must not be NULL) */ svn_repos_notify_func_t notify_func; /* baton to use for it */ void *notify_baton; /* type of notification to send (we will simply plug in the revision) = */ svn_repos_notify_t *notify; }; /* Forward the notification to BATON. */ static void verify_fs_notify_func(svn_revnum_t revision, void *baton, apr_pool_t *pool) { struct verify_fs_notify_func_baton_t *notify_baton =3D baton; notify_baton->notify->revision =3D revision; notify_baton->notify_func(notify_baton->notify_baton, notify_baton->notify, pool); } static svn_error_t * report_error(svn_revnum_t revision, svn_error_t *verify_err, svn_repos_verify_callback_t verify_callback, void *verify_baton, apr_pool_t *pool) { if (verify_callback) { svn_error_t *cb_err; /* The caller provided us with a callback, so make him responsible for what's going to happen with the error. */ cb_err =3D verify_callback(verify_baton, revision, verify_err, = pool); svn_error_clear(verify_err); SVN_ERR(cb_err); return SVN_NO_ERROR; } else { /* No callback -- no second guessing. Just return the error. */ return svn_error_trace(verify_err); } } svn_error_t * svn_repos_verify_fs3(svn_repos_t *repos, svn_revnum_t start_rev, svn_revnum_t end_rev, svn_boolean_t check_normalization, svn_boolean_t metadata_only, svn_repos_notify_func_t notify_func, void *notify_baton, svn_repos_verify_callback_t verify_callback, void *verify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) { svn_fs_t *fs =3D svn_repos_fs(repos); svn_revnum_t youngest; svn_revnum_t rev; apr_pool_t *iterpool =3D svn_pool_create(pool); svn_repos_notify_t *notify; svn_fs_progress_notify_func_t verify_notify =3D NULL; struct verify_fs_notify_func_baton_t *verify_notify_baton =3D NULL; svn_error_t *err; /* Make sure we catch up on the latest revprop changes. This is the = only * time we will refresh the revprop data in this query. */ SVN_ERR(svn_fs_refresh_revision_props(fs, pool)); /* Determine the current youngest revision of the filesystem. */ SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); /* Use default vals if necessary. */ if (! SVN_IS_VALID_REVNUM(start_rev)) start_rev =3D 0; if (! SVN_IS_VALID_REVNUM(end_rev)) end_rev =3D youngest; /* Validate the revisions. */ if (start_rev > end_rev) return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, _("Start revision %ld" " is greater than end revision %ld"), start_rev, end_rev); if (end_rev > youngest) return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, _("End revision %ld is invalid " "(youngest revision is %ld)"), end_rev, youngest); /* Create a notify object that we can reuse within the loop and a forwarding structure for notifications from inside svn_fs_verify(). = */ if (notify_func) { notify =3D = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool); verify_notify =3D verify_fs_notify_func; verify_notify_baton =3D apr_palloc(pool, = sizeof(*verify_notify_baton)); verify_notify_baton->notify_func =3D notify_func; verify_notify_baton->notify_baton =3D notify_baton; verify_notify_baton->notify =3D = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); } /* Verify global metadata and backend-specific data first. */ err =3D svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), start_rev, end_rev, verify_notify, verify_notify_baton, cancel_func, cancel_baton, pool); if (err && err->apr_err =3D=3D SVN_ERR_CANCELLED) { return svn_error_trace(err); } else if (err) { SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback, verify_baton, iterpool)); } if (!metadata_only) for (rev =3D start_rev; rev <=3D end_rev; rev++) { svn_pool_clear(iterpool); /* Wrapper function to catch the possible errors. */ err =3D verify_one_revision(fs, rev, notify_func, notify_baton, start_rev, check_normalization, cancel_func, cancel_baton, iterpool); if (err && err->apr_err =3D=3D SVN_ERR_CANCELLED) { return svn_error_trace(err); } else if (err) { SVN_ERR(report_error(rev, err, verify_callback, = verify_baton, iterpool)); } else if (notify_func) { /* Tell the caller that we're done with this revision. */ notify->revision =3D rev; notify_func(notify_baton, notify, iterpool); } } /* We're done. */ if (notify_func) { notify =3D svn_repos_notify_create(svn_repos_notify_verify_end, = iterpool); notify_func(notify_baton, notify, iterpool); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } ------=_NextPart_000_000B_01D2755C.0AF560E0 Content-Type: text/plain; name="svnadmin.c" Content-Transfer-Encoding: quoted-printable Content-Disposition: attachment; filename="svnadmin.c" /* * svnadmin.c: Subversion server administration tool main file. * * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D * 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. * = =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D */ #include #include "svn_hash.h" #include "svn_pools.h" #include "svn_cmdline.h" #include "svn_error.h" #include "svn_opt.h" #include "svn_utf.h" #include "svn_subst.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_config.h" #include "svn_repos.h" #include "svn_cache_config.h" #include "svn_version.h" #include "svn_props.h" #include "svn_sorts.h" #include "svn_time.h" #include "svn_user.h" #include "svn_xml.h" #include "private/svn_cmdline_private.h" #include "private/svn_opt_private.h" #include "private/svn_sorts_private.h" #include "private/svn_subr_private.h" #include "private/svn_cmdline_private.h" #include "svn_private_config.h" =0C /*** Code. ***/ /* FSFS format 7's "block-read" feature performs poorly with small = caches. * Enable it only if caches above this threshold have been configured. * The current threshold is 64MB. */ #define BLOCK_READ_CACHE_THRESHOLD (0x40 * 0x100000) static svn_cancel_func_t check_cancel =3D NULL; /* Custom filesystem warning function. */ static void warning_func(void *baton, svn_error_t *err) { if (! err) return; svn_handle_warning2(stderr, err, "svnadmin: "); } /* Version compatibility check */ static svn_error_t * check_lib_versions(void) { static const svn_version_checklist_t checklist[] =3D { { "svn_subr", svn_subr_version }, { "svn_repos", svn_repos_version }, { "svn_fs", svn_fs_version }, { "svn_delta", svn_delta_version }, { NULL, NULL } }; SVN_VERSION_DEFINE(my_version); return svn_ver_check_list2(&my_version, checklist, svn_ver_equal); } =0C /** Subcommands. **/ static svn_opt_subcommand_t subcommand_crashtest, subcommand_create, subcommand_delrevprop, subcommand_deltify, subcommand_dump, subcommand_dump_revprops, subcommand_freeze, subcommand_help, subcommand_hotcopy, subcommand_info, subcommand_load, subcommand_load_revprops, subcommand_list_dblogs, subcommand_list_unused_dblogs, subcommand_lock, subcommand_lslocks, subcommand_lstxns, subcommand_pack, subcommand_recover, subcommand_rmlocks, subcommand_rmtxns, subcommand_setlog, subcommand_setrevprop, subcommand_setuuid, subcommand_unlock, subcommand_upgrade, subcommand_verify; enum svnadmin__cmdline_options_t { svnadmin__version =3D SVN_OPT_FIRST_LONGOPT_ID, svnadmin__incremental, svnadmin__keep_going, svnadmin__deltas, svnadmin__ignore_uuid, svnadmin__force_uuid, svnadmin__fs_type, svnadmin__parent_dir, svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep, svnadmin__config_dir, svnadmin__bypass_hooks, svnadmin__bypass_prop_validation, svnadmin__ignore_dates, svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook, svnadmin__use_pre_revprop_change_hook, svnadmin__use_post_revprop_change_hook, svnadmin__clean_logs, svnadmin__wait, svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible, svnadmin__pre_1_6_compatible, svnadmin__compatible_version, svnadmin__check_normalization, svnadmin__metadata_only, svnadmin__no_flush_to_disk, svnadmin__pre_1_8_dump }; /* Option codes and descriptions. * * The entire list must be terminated with an entry of nulls. */ static const apr_getopt_option_t options_table[] =3D { {"help", 'h', 0, N_("show help on a subcommand")}, {NULL, '?', 0, N_("show help on a subcommand")}, {"version", svnadmin__version, 0, N_("show program version information")}, {"revision", 'r', 1, N_("specify revision number ARG (or X:Y range)")}, {"transaction", 't', 1, N_("specify transaction name ARG")}, {"incremental", svnadmin__incremental, 0, N_("dump or hotcopy incrementally")}, {"deltas", svnadmin__deltas, 0, N_("use deltas in dump output")}, {"bypass-hooks", svnadmin__bypass_hooks, 0, N_("bypass the repository hook system")}, {"bypass-prop-validation", svnadmin__bypass_prop_validation, 0, N_("bypass property validation logic")}, {"ignore-dates", svnadmin__ignore_dates, 0, N_("ignore revision datestamps found in the stream")}, {"quiet", 'q', 0, N_("no progress (only errors to stderr)")}, {"ignore-uuid", svnadmin__ignore_uuid, 0, N_("ignore any repos UUID found in the stream")}, {"force-uuid", svnadmin__force_uuid, 0, N_("set repos UUID to that found in stream, if any")}, {"fs-type", svnadmin__fs_type, 1, N_("type of repository:\n" " 'fsfs' (default), 'bdb' or = 'fsx'\n" " CAUTION: FSX is for EXPERIMENTAL = use only!")}, {"parent-dir", svnadmin__parent_dir, 1, N_("load at specified directory in repository")}, {"bdb-txn-nosync", svnadmin__bdb_txn_nosync, 0, N_("disable fsync at transaction commit [Berkeley DB]")}, {"bdb-log-keep", svnadmin__bdb_log_keep, 0, N_("disable automatic log file removal [Berkeley DB]")}, {"config-dir", svnadmin__config_dir, 1, N_("read user configuration files from directory ARG")}, {"clean-logs", svnadmin__clean_logs, 0, N_("remove redundant Berkeley DB log files\n" " from source repository [Berkeley = DB]")}, {"use-pre-commit-hook", svnadmin__use_pre_commit_hook, 0, N_("call pre-commit hook before committing revisions")}, {"use-post-commit-hook", svnadmin__use_post_commit_hook, 0, N_("call post-commit hook after committing revisions")}, {"use-pre-revprop-change-hook", = svnadmin__use_pre_revprop_change_hook, 0, N_("call hook before changing revision property")}, {"use-post-revprop-change-hook", = svnadmin__use_post_revprop_change_hook, 0, N_("call hook after changing revision property")}, {"wait", svnadmin__wait, 0, N_("wait instead of exit if the repository is in\n" " use by another process")}, {"pre-1.4-compatible", svnadmin__pre_1_4_compatible, 0, N_("deprecated; see --compatible-version")}, {"pre-1.5-compatible", svnadmin__pre_1_5_compatible, 0, N_("deprecated; see --compatible-version")}, {"pre-1.6-compatible", svnadmin__pre_1_6_compatible, 0, N_("deprecated; see --compatible-version")}, {"keep-going", svnadmin__keep_going, 0, N_("continue verification after detecting a corruption")}, {"memory-cache-size", 'M', 1, N_("size of the extra in-memory cache in MB used to\n" " minimize redundant operations. = Default: 16.\n" " [used for FSFS repositories = only]")}, {"compatible-version", svnadmin__compatible_version, 1, N_("use repository format compatible with Subversion\n" " version ARG (\"1.5.5\", \"1.7\", = etc.)")}, {"file", 'F', 1, N_("read repository paths from file ARG")}, {"check-normalization", svnadmin__check_normalization, 0, N_("report any names within the same directory or\n" " svn:mergeinfo property value that = differ only\n" " in character representation, but = are otherwise\n" " identical")}, {"metadata-only", svnadmin__metadata_only, 0, N_("verify metadata only (ignored for BDB),\n" " checking against external = corruption in\n" " Subversion 1.9+ format = repositories.\n")}, {"no-flush-to-disk", svnadmin__no_flush_to_disk, 0, N_("disable flushing to disk during the operation\n" " (faster, but unsafe on power = off)")}, {"pre-1.8-dump", svnadmin__pre_1_8_dump, 0, N_("the dump file will order certain keys in version 1.8 or = older\n" " format.\n")}, {NULL} }; /* Array of available subcommands. * The entire list must be terminated with an entry of nulls. */ static const svn_opt_subcommand_desc2_t cmd_table[] =3D { {"crashtest", subcommand_crashtest, {0}, N_ ("usage: svnadmin crashtest REPOS_PATH\n\n" "Open the repository at REPOS_PATH, then abort, thus simulating\n" "a process that crashes while holding an open repository = handle.\n"), {0} }, {"create", subcommand_create, {0}, N_ ("usage: svnadmin create REPOS_PATH\n\n" "Create a new, empty repository at REPOS_PATH.\n"), {svnadmin__bdb_txn_nosync, svnadmin__bdb_log_keep, svnadmin__config_dir, svnadmin__fs_type, = svnadmin__compatible_version, svnadmin__pre_1_4_compatible, svnadmin__pre_1_5_compatible, svnadmin__pre_1_6_compatible } }, {"delrevprop", subcommand_delrevprop, {0}, N_ ("usage: 1. svnadmin delrevprop REPOS_PATH -r REVISION NAME\n" " 2. svnadmin delrevprop REPO_PATH -t TXN = NAME\n\n" "1. Delete the property NAME on revision REVISION.\n\n" "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook = to\n" "trigger the revision property-related hooks (for example, if you = want\n" "an email notification sent from your post-revprop-change = hook).\n\n" "NOTE: Revision properties are not versioned, so this command = will\n" "irreversibly destroy the previous value of the property.\n\n" "2. Delete the property NAME on transaction TXN.\n"), {'r', 't', svnadmin__use_pre_revprop_change_hook, svnadmin__use_post_revprop_change_hook} }, {"deltify", subcommand_deltify, {0}, N_ ("usage: svnadmin deltify [-r LOWER[:UPPER]] REPOS_PATH\n\n" "Run over the requested revision range, performing predecessor = delti-\n" "fication on the paths changed in those revisions. Deltification = in\n" "essence compresses the repository by only storing the differences = or\n" "delta from the preceding revision. If no revisions are = specified,\n" "this will simply deltify the HEAD revision.\n"), {'r', 'q', 'M'} }, {"dump", subcommand_dump, {0}, N_ ("usage: svnadmin dump REPOS_PATH [-r LOWER[:UPPER] [--incremental]] = [--pre-1.8-dump]\n\n" "Dump the contents of filesystem to stdout in a 'dumpfile'\n" "portable format, sending feedback to stderr. Dump revisions\n" "LOWER rev through UPPER rev. If no revisions are given, dump = all\n" "revision trees. If only LOWER is given, dump that one revision = tree.\n" "If --incremental is passed, the first revision dumped will = describe\n" "only the paths changed in that revision; otherwise it will = describe\n" "every path present in the repository as of that revision. (In = either\n" "case, the second and subsequent revisions, if any, describe only = paths\n" "changed in those revisions.)\n" "--pre-1.8-dump preserves the node key order as defined in 1.8 = and\n" "and earlier releases\n"), {'r', svnadmin__incremental, svnadmin__deltas, 'q', 'M', 'F'}, {{'F', N_("write to file ARG instead of stdout")}} }, {"dump-revprops", subcommand_dump_revprops, {0}, N_ ("usage: svnadmin dump-revprops REPOS_PATH [-r LOWER[:UPPER]]\n\n" "Dump the revision properties of filesystem to stdout in a = 'dumpfile'\n" "portable format, sending feedback to stderr. Dump revisions\n" "LOWER rev through UPPER rev. If no revisions are given, dump = the\n" "properties for all revisions. If only LOWER is given, dump the\n" "properties for that one revision.\n"), {'r', 'q', 'F'}, {{'F', N_("write to file ARG instead of stdout")}} }, {"freeze", subcommand_freeze, {0}, N_ ("usage: 1. svnadmin freeze REPOS_PATH PROGRAM [ARG...]\n" " 2. svnadmin freeze -F FILE PROGRAM [ARG...]\n\n" "1. Run PROGRAM passing ARGS while holding a write-lock on = REPOS_PATH.\n" " Allows safe use of third-party backup tools on a live = repository.\n" "\n" "2. Like 1 except all repositories listed in FILE are locked. The = file\n" " format is repository paths separated by newlines. Repositories = are\n" " locked in the same order as they are listed in the file.\n"), {'F'}, {{'F', N_("read repository paths from file ARG")}} }, {"help", subcommand_help, {"?", "h"}, N_ ("usage: svnadmin help [SUBCOMMAND...]\n\n" "Describe the usage of this program or its subcommands.\n"), {0} }, {"hotcopy", subcommand_hotcopy, {0}, N_ ("usage: svnadmin hotcopy REPOS_PATH NEW_REPOS_PATH\n\n" "Make a hot copy of a repository.\n" "If --incremental is passed, data which already exists at the = destination\n" "is not copied again. Incremental mode is implemented for FSFS = repositories.\n"), {svnadmin__clean_logs, svnadmin__incremental, 'q'} }, {"info", subcommand_info, {0}, N_ ("usage: svnadmin info REPOS_PATH\n\n" "Print information about the repository at REPOS_PATH.\n"), {0} }, {"list-dblogs", subcommand_list_dblogs, {0}, N_ ("usage: svnadmin list-dblogs REPOS_PATH\n\n" "List all Berkeley DB log files.\n\n" "WARNING: Modifying or deleting logfiles which are still in use\n" "will cause your repository to be corrupted.\n"), {0} }, {"list-unused-dblogs", subcommand_list_unused_dblogs, {0}, N_ ("usage: svnadmin list-unused-dblogs REPOS_PATH\n\n" "List unused Berkeley DB log files.\n\n"), {0} }, {"load", subcommand_load, {0}, N_ ("usage: svnadmin load REPOS_PATH\n\n" "Read a 'dumpfile'-formatted stream from stdin, committing\n" "new revisions into the repository's filesystem. If the = repository\n" "was previously empty, its UUID will, by default, be changed to = the\n" "one specified in the stream. Progress feedback is sent to = stdout.\n" "If --revision is specified, limit the loaded revisions to only = those\n" "in the dump stream whose revision numbers match the specified = range.\n"), {'q', 'r', svnadmin__ignore_uuid, svnadmin__force_uuid, svnadmin__ignore_dates, svnadmin__use_pre_commit_hook, svnadmin__use_post_commit_hook, svnadmin__parent_dir, svnadmin__bypass_prop_validation, 'M', svnadmin__no_flush_to_disk, 'F'}, {{'F', N_("read from file ARG instead of stdin")}} }, {"load-revprops", subcommand_load_revprops, {0}, N_ ("usage: svnadmin load-revprops REPOS_PATH\n\n" "Read a 'dumpfile'-formatted stream from stdin, setting the = revision\n" "properties in the repository's filesystem. Revisions not found in = the\n" "repository will cause an error. Progress feedback is sent to = stdout.\n" "If --revision is specified, limit the loaded revisions to only = those\n" "in the dump stream whose revision numbers match the specified = range.\n"), {'q', 'r', svnadmin__force_uuid, svnadmin__bypass_prop_validation, svnadmin__no_flush_to_disk, 'F'}, {{'F', N_("read from file ARG instead of stdin")}} }, {"lock", subcommand_lock, {0}, N_ ("usage: svnadmin lock REPOS_PATH PATH USERNAME COMMENT-FILE = [TOKEN]\n\n" "Lock PATH by USERNAME setting comments from COMMENT-FILE.\n" "If provided, use TOKEN as lock token. Use --bypass-hooks to = avoid\n" "triggering the pre-lock and post-lock hook scripts.\n"), {svnadmin__bypass_hooks} }, {"lslocks", subcommand_lslocks, {0}, N_ ("usage: svnadmin lslocks REPOS_PATH [PATH-IN-REPOS]\n\n" "Print descriptions of all locks on or under PATH-IN-REPOS = (which,\n" "if not provided, is the root of the repository).\n"), {0} }, {"lstxns", subcommand_lstxns, {0}, N_ ("usage: svnadmin lstxns REPOS_PATH\n\n" "Print the names of uncommitted transactions. With -rN skip the = output\n" "of those that have a base revision more recent than rN. = Transactions\n" "with base revisions much older than HEAD are likely to have been\n" "abandonded and are candidates to be removed.\n"), {'r'}, { {'r', "transaction base revision ARG"} } }, {"pack", subcommand_pack, {0}, N_ ("usage: svnadmin pack REPOS_PATH\n\n" "Possibly compact the repository into a more efficient storage = model.\n" "This may not apply to all repositories, in which case, exit.\n"), {'q', 'M'} }, {"recover", subcommand_recover, {0}, N_ ("usage: svnadmin recover REPOS_PATH\n\n" "Run the recovery procedure on a repository. Do this if you've\n" "been getting errors indicating that recovery ought to be run.\n" "Berkeley DB recovery requires exclusive access and will\n" "exit if the repository is in use by another process.\n"), {svnadmin__wait} }, {"rmlocks", subcommand_rmlocks, {0}, N_ ("usage: svnadmin rmlocks REPOS_PATH LOCKED_PATH...\n\n" "Unconditionally remove lock from each LOCKED_PATH.\n"), {0} }, {"rmtxns", subcommand_rmtxns, {0}, N_ ("usage: svnadmin rmtxns REPOS_PATH TXN_NAME...\n\n" "Delete the named transaction(s).\n"), {'q'} }, {"setlog", subcommand_setlog, {0}, N_ ("usage: svnadmin setlog REPOS_PATH -r REVISION FILE\n\n" "Set the log-message on revision REVISION to the contents of FILE. = Use\n" "--bypass-hooks to avoid triggering the revision-property-related = hooks\n" "(for example, if you do not want an email notification sent\n" "from your post-revprop-change hook, or because the modification = of\n" "revision properties has not been enabled in the = pre-revprop-change\n" "hook).\n\n" "NOTE: Revision properties are not versioned, so this command = will\n" "overwrite the previous log message.\n"), {'r', svnadmin__bypass_hooks} }, {"setrevprop", subcommand_setrevprop, {0}, N_ ("usage: 1. svnadmin setrevprop REPOS_PATH -r REVISION NAME FILE\n" " 2. svnadmin setrevprop REPOS_PATH -t TXN NAME = FILE\n\n" "1. Set the property NAME on revision REVISION to the contents of = FILE.\n\n" "Use --use-pre-revprop-change-hook/--use-post-revprop-change-hook = to\n" "trigger the revision property-related hooks (for example, if you = want\n" "an email notification sent from your post-revprop-change = hook).\n\n" "NOTE: Revision properties are not versioned, so this command = will\n" "overwrite the previous value of the property.\n\n" "2. Set the property NAME on transaction TXN to the contents of = FILE.\n"), {'r', 't', svnadmin__use_pre_revprop_change_hook, svnadmin__use_post_revprop_change_hook} }, {"setuuid", subcommand_setuuid, {0}, N_ ("usage: svnadmin setuuid REPOS_PATH [NEW_UUID]\n\n" "Reset the repository UUID for the repository located at REPOS_PATH. = If\n" "NEW_UUID is provided, use that as the new repository UUID; = otherwise,\n" "generate a brand new UUID for the repository.\n"), {0} }, {"unlock", subcommand_unlock, {0}, N_ ("usage: svnadmin unlock REPOS_PATH LOCKED_PATH USERNAME TOKEN\n\n" "Unlock LOCKED_PATH (as USERNAME) after verifying that the token\n" "associated with the lock matches TOKEN. Use --bypass-hooks to = avoid\n" "triggering the pre-unlock and post-unlock hook scripts.\n"), {svnadmin__bypass_hooks} }, {"upgrade", subcommand_upgrade, {0}, N_ ("usage: svnadmin upgrade REPOS_PATH\n\n" "Upgrade the repository located at REPOS_PATH to the latest = supported\n" "schema version.\n\n" "This functionality is provided as a convenience for repository\n" "administrators who wish to make use of new Subversion = functionality\n" "without having to undertake a potentially costly full repository = dump\n" "and load operation. As such, the upgrade performs only the = minimum\n" "amount of work needed to accomplish this while still maintaining = the\n" "integrity of the repository. It does not guarantee the most = optimized\n" "repository state as a dump and subsequent load would.\n"), {0} }, {"verify", subcommand_verify, {0}, N_ ("usage: svnadmin verify REPOS_PATH\n\n" "Verify the data stored in the repository.\n"), {'t', 'r', 'q', svnadmin__keep_going, 'M', svnadmin__check_normalization, svnadmin__metadata_only} }, { NULL, NULL, {0}, NULL, {0} } }; /* Baton for passing option/argument state to a subcommand function. */ struct svnadmin_opt_state { const char *repository_path; const char *fs_type; /* --fs-type */ svn_version_t *compatible_version; /* = --compatible-version */ svn_opt_revision_t start_revision, end_revision; /* -r X[:Y] */ const char *txn_id; /* -t TXN */ svn_boolean_t help; /* --help or -? */ svn_boolean_t version; /* --version */ svn_boolean_t incremental; /* --incremental */ svn_boolean_t use_deltas; /* --deltas */ svn_boolean_t use_pre_commit_hook; /* = --use-pre-commit-hook */ svn_boolean_t use_post_commit_hook; /* = --use-post-commit-hook */ svn_boolean_t use_pre_revprop_change_hook; /* = --use-pre-revprop-change-hook */ svn_boolean_t use_post_revprop_change_hook; /* = --use-post-revprop-change-hook */ svn_boolean_t quiet; /* --quiet */ svn_boolean_t bdb_txn_nosync; /* --bdb-txn-nosync = */ svn_boolean_t bdb_log_keep; /* --bdb-log-keep */ svn_boolean_t clean_logs; /* --clean-logs */ svn_boolean_t bypass_hooks; /* --bypass-hooks */ svn_boolean_t wait; /* --wait */ svn_boolean_t keep_going; /* --keep-going */ svn_boolean_t check_normalization; /* = --check-normalization */ svn_boolean_t metadata_only; /* --metadata-only = */ svn_boolean_t bypass_prop_validation; /* = --bypass-prop-validation */ svn_boolean_t ignore_dates; /* --ignore-dates */ svn_boolean_t no_flush_to_disk; /* = --no-flush-to-disk */ svn_boolean_t pre_1_8_dump; /* --pre-1.8-dump */ enum svn_repos_load_uuid uuid_action; /* --ignore-uuid, --force-uuid */ apr_uint64_t memory_cache_size; /* = --memory-cache-size M */ const char *parent_dir; /* --parent-dir */ const char *file; /* --file */ const char *config_dir; /* Overriding Configuration Directory */ }; /* Helper to open a repository and set a warning func (so we don't * SEGFAULT when libsvn_fs's default handler gets run). */ static svn_error_t * open_repos(svn_repos_t **repos, const char *path, struct svnadmin_opt_state *opt_state, apr_pool_t *pool) { /* Enable the "block-read" feature (where it applies)? */ svn_boolean_t use_block_read =3D svn_cache_config_get()->cache_size > BLOCK_READ_CACHE_THRESHOLD; /* construct FS configuration parameters: enable caches for r/o data = */ apr_hash_t *fs_config =3D apr_hash_make(pool); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NODEPROPS, "1"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2"); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, svn_uuid_generate(pool)); svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, use_block_read ? "1" : "0"); svn_hash_sets(fs_config, SVN_FS_CONFIG_NO_FLUSH_TO_DISK, opt_state->no_flush_to_disk ? "1" : "0"); /* now, open the requested repository */ SVN_ERR(svn_repos_open3(repos, path, fs_config, pool, pool)); svn_fs_set_warning_func(svn_repos_fs(*repos), warning_func, NULL); return SVN_NO_ERROR; } /* Set *REVNUM to the revision specified by REVISION (or to SVN_INVALID_REVNUM if that has the type 'unspecified'), possibly making use of the YOUNGEST revision number in REPOS. */ static svn_error_t * get_revnum(svn_revnum_t *revnum, const svn_opt_revision_t *revision, svn_revnum_t youngest, svn_repos_t *repos, apr_pool_t *pool) { if (revision->kind =3D=3D svn_opt_revision_number) *revnum =3D revision->value.number; else if (revision->kind =3D=3D svn_opt_revision_head) *revnum =3D youngest; else if (revision->kind =3D=3D svn_opt_revision_date) SVN_ERR(svn_repos_dated_revision(revnum, repos, = revision->value.date, pool)); else if (revision->kind =3D=3D svn_opt_revision_unspecified) *revnum =3D SVN_INVALID_REVNUM; else return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Invalid revision specifier")); if (*revnum > youngest) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Revisions must not be greater than the youngest revision = (%ld)"), youngest); return SVN_NO_ERROR; } /* Set *PATH to an internal-style, UTF8-encoded, local dirent path allocated from POOL and parsed from raw command-line argument ARG. */ static svn_error_t * target_arg_to_dirent(const char **dirent, const char *arg, apr_pool_t *pool) { const char *path; SVN_ERR(svn_utf_cstring_to_utf8(&path, arg, pool)); if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Path '%s' is not a local path"), path); *dirent =3D svn_dirent_internal_style(path, pool); return SVN_NO_ERROR; } /* Parse the remaining command-line arguments from OS, returning them in a new array *ARGS (allocated from POOL) and optionally verifying that we got the expected number thereof. If MIN_EXPECTED is not negative, return an error if the function would return fewer than MIN_EXPECTED arguments. If MAX_EXPECTED is not negative, return an error if the function would return more than MAX_EXPECTED arguments. As a special case, when MIN_EXPECTED and MAX_EXPECTED are both 0, allow ARGS to be NULL. */ static svn_error_t * parse_args(apr_array_header_t **args, apr_getopt_t *os, int min_expected, int max_expected, apr_pool_t *pool) { int num_args =3D os ? (os->argc - os->ind) : 0; if (min_expected || max_expected) SVN_ERR_ASSERT(args); if ((min_expected >=3D 0) && (num_args < min_expected)) return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, 0, _("Not enough arguments")); if ((max_expected >=3D 0) && (num_args > max_expected)) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, _("Too many arguments")); if (args) { *args =3D apr_array_make(pool, num_args, sizeof(const char *)); if (num_args) while (os->ind < os->argc) APR_ARRAY_PUSH(*args, const char *) =3D apr_pstrdup(pool, os->argv[os->ind++]); } return SVN_NO_ERROR; } /* This implements 'svn_error_malfunction_handler_t. */ static svn_error_t * crashtest_malfunction_handler(svn_boolean_t can_return, const char *file, int line, const char *expr) { abort(); return SVN_NO_ERROR; /* Not reached. */ } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_crashtest(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; = (void)svn_error_set_malfunction_handler(crashtest_malfunction_handler); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); SVN_ERR(svn_cmdline_printf(pool, _("Successfully opened repository '%s'.\n" "Will now crash to simulate a crashing " "server process.\n"), = svn_dirent_local_style(opt_state->repository_path, pool))); SVN_ERR_MALFUNCTION(); /* merely silence a compiler warning (this will never be executed) */ return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_create(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; apr_hash_t *fs_config =3D apr_hash_make(pool); /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_TXN_NOSYNC, (opt_state->bdb_txn_nosync ? "1" :"0")); svn_hash_sets(fs_config, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE, (opt_state->bdb_log_keep ? "0" :"1")); if (opt_state->fs_type) { /* With 1.8 we are announcing that BDB is deprecated. No support * has been removed and it will continue to work until some future * date. The purpose here is to discourage people from creating * new BDB repositories which they will need to dump/load into * FSFS or some new FS type in the future. */ if (0 =3D=3D strcmp(opt_state->fs_type, SVN_FS_TYPE_BDB)) { SVN_ERR(svn_cmdline_fprintf( stderr, pool, _("%swarning:" " The \"%s\" repository back-end is deprecated," " consider using \"%s\" instead.\n"), "svnadmin: ", SVN_FS_TYPE_BDB, SVN_FS_TYPE_FSFS)); fflush(stderr); } svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, = opt_state->fs_type); } if (opt_state->compatible_version) { if (! svn_version__at_least(opt_state->compatible_version, 1, 4, = 0)) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "1"); if (! svn_version__at_least(opt_state->compatible_version, 1, 5, = 0)) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE, "1"); if (! svn_version__at_least(opt_state->compatible_version, 1, 6, = 0)) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE, "1"); if (! svn_version__at_least(opt_state->compatible_version, 1, 8, = 0)) svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_8_COMPATIBLE, "1"); /* In 1.9, we figured out that we didn't have to keep extending = this madness indefinitely. */ svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION, apr_psprintf(pool, "%d.%d.%d%s%s", opt_state->compatible_version->major, opt_state->compatible_version->minor, opt_state->compatible_version->patch, opt_state->compatible_version->tag ? "-" : "", opt_state->compatible_version->tag ? opt_state->compatible_version->tag : = "")); } if (opt_state->compatible_version) { if (! svn_version__at_least(opt_state->compatible_version, 1, 1, = 0) /* ### TODO: this NULL check hard-codes knowledge of the = library's default fs-type value */ && (opt_state->fs_type =3D=3D NULL || !strcmp(opt_state->fs_type, SVN_FS_TYPE_FSFS))) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Repositories compatible with 1.0.x = must " "use --fs-type=3Dbdb")); } if (! svn_version__at_least(opt_state->compatible_version, 1, 9, = 0) && opt_state->fs_type && !strcmp(opt_state->fs_type, = SVN_FS_TYPE_FSX)) { return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Repositories compatible with 1.8.x = or " "earlier cannot use = --fs-type=3D%s"), SVN_FS_TYPE_FSX); } } SVN_ERR(svn_repos_create(&repos, opt_state->repository_path, NULL, NULL, NULL, fs_config, pool)); svn_fs_set_warning_func(svn_repos_fs(repos), warning_func, NULL); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_deltify(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_revnum_t start =3D SVN_INVALID_REVNUM, end =3D SVN_INVALID_REVNUM; svn_revnum_t youngest, revision; apr_pool_t *subpool =3D svn_pool_create(pool); /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); /* Find the revision numbers at which to start and end. */ SVN_ERR(get_revnum(&start, &opt_state->start_revision, youngest, repos, pool)); SVN_ERR(get_revnum(&end, &opt_state->end_revision, youngest, repos, pool)); /* Fill in implied revisions if necessary. */ if (start =3D=3D SVN_INVALID_REVNUM) start =3D youngest; if (end =3D=3D SVN_INVALID_REVNUM) end =3D start; if (start > end) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("First revision cannot be higher than second")); /* Loop over the requested revision range, performing the predecessor deltification on paths changed in each. */ for (revision =3D start; revision <=3D end; revision++) { svn_pool_clear(subpool); SVN_ERR(check_cancel(NULL)); if (! opt_state->quiet) SVN_ERR(svn_cmdline_printf(subpool, _("Deltifying revision = %ld..."), revision)); SVN_ERR(svn_fs_deltify_revision(fs, revision, subpool)); if (! opt_state->quiet) SVN_ERR(svn_cmdline_printf(subpool, _("done.\n"))); } svn_pool_destroy(subpool); return SVN_NO_ERROR; } /* Structure for errors encountered during 'svnadmin verify = --keep-going'. */ struct verification_error { svn_revnum_t rev; svn_error_t *err; }; /* Pool cleanup function to clear an svn_error_t *. */ static apr_status_t err_cleanup(void *data) { svn_error_t *err =3D data; svn_error_clear(err); return APR_SUCCESS; } struct repos_verify_callback_baton { /* Should we continue after receiving a first verification error? */ svn_boolean_t keep_going; /* List of errors encountered during 'svnadmin verify --keep-going'. = */ apr_array_header_t *error_summary; /* Pool for data collected during callback invocations. */ apr_pool_t *result_pool; }; /* Implementation of svn_repos_verify_callback_t to handle errors coming from svn_repos_verify_fs3(). */ static svn_error_t * repos_verify_callback(void *baton, svn_revnum_t revision, svn_error_t *verify_err, apr_pool_t *scratch_pool) { struct repos_verify_callback_baton *b =3D baton; if (revision =3D=3D SVN_INVALID_REVNUM) { SVN_ERR(svn_cmdline_fputs(_("* Error verifying repository = metadata.\n"), stderr, scratch_pool)); } else { SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, _("* Error verifying revision = %ld.\n"), revision)); } if (b->keep_going) { struct verification_error *verr; svn_handle_error2(verify_err, stderr, FALSE, "svnadmin: "); /* Remember the error in B->ERROR_SUMMARY. */ verr =3D apr_palloc(b->result_pool, sizeof(*verr)); verr->rev =3D revision; verr->err =3D svn_error_dup(verify_err); apr_pool_cleanup_register(b->result_pool, verr->err, err_cleanup, apr_pool_cleanup_null); APR_ARRAY_PUSH(b->error_summary, struct verification_error *) =3D = verr; return SVN_NO_ERROR; } else return svn_error_trace(svn_error_dup(verify_err)); } /* Implementation of svn_repos_notify_func_t to wrap the output to a response stream for svn_repos_dump_fs2(), svn_repos_verify_fs(), svn_repos_hotcopy3() and others. */ static void repos_notify_handler(void *baton, const svn_repos_notify_t *notify, apr_pool_t *scratch_pool) { svn_stream_t *feedback_stream =3D baton; switch (notify->action) { case svn_repos_notify_warning: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, "WARNING 0x%04x: %s\n", = notify->warning, notify->warning_str)); return; case svn_repos_notify_dump_rev_end: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("* Dumped revision %ld.\n"), notify->revision)); return; case svn_repos_notify_verify_rev_end: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("* Verified revision %ld.\n"), notify->revision)); return; case svn_repos_notify_verify_rev_structure: if (notify->revision =3D=3D SVN_INVALID_REVNUM) svn_error_clear(svn_stream_puts(feedback_stream, _("* Verifying repository metadata = ...\n"))); else svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("* Verifying metadata at revision %ld ...\n"), notify->revision)); return; case svn_repos_notify_pack_shard_start: { const char *shardstr =3D apr_psprintf(scratch_pool, "%" APR_INT64_T_FMT, notify->shard); svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Packing revisions in shard = %s..."), shardstr)); } return; case svn_repos_notify_pack_shard_end: svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n"))); return; case svn_repos_notify_pack_shard_start_revprop: { const char *shardstr =3D apr_psprintf(scratch_pool, "%" APR_INT64_T_FMT, notify->shard); svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Packing revprops in shard = %s..."), shardstr)); } return; case svn_repos_notify_pack_shard_end_revprop: svn_error_clear(svn_stream_puts(feedback_stream, _("done.\n"))); return; case svn_repos_notify_load_txn_committed: if (notify->old_revision =3D=3D SVN_INVALID_REVNUM) { svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _("\n------- Committed revision %ld = >>>\n\n"), notify->new_revision)); } else { svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _("\n------- Committed new rev %ld" " (loaded from original rev %ld" ") >>>\n\n"), notify->new_revision, notify->old_revision)); } return; case svn_repos_notify_load_node_start: { switch (notify->node_action) { case svn_node_action_change: svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _(" * editing path : %s ..."), notify->path)); break; case svn_node_action_delete: svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _(" * deleting path : %s ..."), notify->path)); break; case svn_node_action_add: svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _(" * adding path : %s ..."), notify->path)); break; case svn_node_action_replace: svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _(" * replacing path : %s ..."), notify->path)); break; } } return; case svn_repos_notify_load_node_done: svn_error_clear(svn_stream_puts(feedback_stream, _(" done.\n"))); return; case svn_repos_notify_load_copied_node: svn_error_clear(svn_stream_puts(feedback_stream, "COPIED...")); return; case svn_repos_notify_load_txn_start: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("<<< Started new transaction, based on = " "original revision %ld\n"), notify->old_revision)); return; case svn_repos_notify_load_skipped_rev: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("<<< Skipped original revision = %ld\n"), notify->old_revision)); return; case svn_repos_notify_load_normalized_mergeinfo: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _(" removing '\\r' from %s ..."), SVN_PROP_MERGEINFO)); return; case svn_repos_notify_mutex_acquired: svn_cmdline__setup_cancellation_handler(); return; case svn_repos_notify_recover_start: svn_error_clear(svn_stream_puts(feedback_stream, _("Repository lock acquired.\n" "Please wait; recovering the" " repository may take some time...\n"))); return; case svn_repos_notify_upgrade_start: svn_error_clear(svn_stream_puts(feedback_stream, _("Repository lock acquired.\n" "Please wait; upgrading the" " repository may take some time...\n"))); return; case svn_repos_notify_pack_revprops: { const char *shardstr =3D apr_psprintf(scratch_pool, "%" APR_INT64_T_FMT, notify->shard); svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Packed revision properties in shard = %s\n"), shardstr)); return; } case svn_repos_notify_cleanup_revprops: { const char *shardstr =3D apr_psprintf(scratch_pool, "%" APR_INT64_T_FMT, notify->shard); svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Removed non-packed revision properties" " in shard %s\n"), shardstr)); return; } case svn_repos_notify_format_bumped: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Bumped repository format to %ld\n"), notify->revision)); return; case svn_repos_notify_hotcopy_rev_range: if (notify->start_revision =3D=3D notify->end_revision) { svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _("* Copied revision = %ld.\n"), notify->start_revision)); } else { svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _("* Copied revisions from %ld to = %ld.\n"), notify->start_revision, = notify->end_revision)); } return; case svn_repos_notify_pack_noop: /* For best backward compatibility, we keep silent if there were = just no more shards to pack. */ if (notify->shard =3D=3D -1) { svn_error_clear(svn_stream_printf(feedback_stream, = scratch_pool, _("svnadmin: Warning - this repository is not = sharded." " Packing has no effect.\n"))); } return; case svn_repos_notify_load_revprop_set: svn_error_clear(svn_stream_printf(feedback_stream, scratch_pool, _("Properties set on revision %ld.\n"), notify->new_revision)); return; default: return; } } /* Baton for recode_write(). */ struct recode_write_baton { apr_pool_t *pool; FILE *out; }; /* This implements the 'svn_write_fn_t' interface. Write DATA to ((struct recode_write_baton *) BATON)->out, in the console encoding, using svn_cmdline_fprintf(). DATA is a UTF8-encoded C string, therefore ignore LEN. ### This recoding mechanism might want to be abstracted into ### svn_io.h or svn_cmdline.h, if it proves useful elsewhere. */ static svn_error_t *recode_write(void *baton, const char *data, apr_size_t *len) { struct recode_write_baton *rwb =3D baton; svn_pool_clear(rwb->pool); return svn_cmdline_fputs(data, rwb->out, rwb->pool); } /* Create a stream, to write to STD_STREAM, that uses recode_write() to perform UTF-8 to console encoding translation. */ static svn_stream_t * recode_stream_create(FILE *std_stream, apr_pool_t *pool) { struct recode_write_baton *std_stream_rwb =3D apr_palloc(pool, sizeof(struct recode_write_baton)); svn_stream_t *rw_stream =3D svn_stream_create(std_stream_rwb, pool); std_stream_rwb->pool =3D svn_pool_create(pool); std_stream_rwb->out =3D std_stream; svn_stream_set_write(rw_stream, recode_write); return rw_stream; } /* Read the min / max revision from the OPT_STATE, verify them against = REPOS and return them in *LOWER and *UPPER, respectively. Use SCRATCH_POOL for temporary allocations. */ static svn_error_t * get_dump_range(svn_revnum_t *lower, svn_revnum_t *upper, svn_repos_t *repos, struct svnadmin_opt_state *opt_state, apr_pool_t *scratch_pool) { svn_fs_t *fs; svn_revnum_t youngest; *lower =3D SVN_INVALID_REVNUM; *upper =3D SVN_INVALID_REVNUM; fs =3D svn_repos_fs(repos); SVN_ERR(svn_fs_youngest_rev(&youngest, fs, scratch_pool)); /* Find the revision numbers at which to start and end. */ SVN_ERR(get_revnum(lower, &opt_state->start_revision, youngest, repos, scratch_pool)); SVN_ERR(get_revnum(upper, &opt_state->end_revision, youngest, repos, scratch_pool)); /* Fill in implied revisions if necessary. */ if (*lower =3D=3D SVN_INVALID_REVNUM) { *lower =3D 0; *upper =3D youngest; } else if (*upper =3D=3D SVN_INVALID_REVNUM) { *upper =3D *lower; } if (*lower > *upper) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("First revision cannot be higher than second")); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_dump(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_stream_t *out_stream; svn_revnum_t lower, upper; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool)); /* Open the file or STDOUT, depending on whether -F was specified. */ if (opt_state->file) { apr_file_t *file; /* Overwrite existing files, same as with > redirection. */ SVN_ERR(svn_io_file_open(&file, opt_state->file, APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED, APR_OS_DEFAULT, pool)); out_stream =3D svn_stream_from_aprfile2(file, FALSE, pool); } else SVN_ERR(svn_stream_for_stdout(&out_stream, pool)); /* Progress feedback goes to STDERR, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stderr, pool); SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper, opt_state->incremental, = opt_state->use_deltas, TRUE, TRUE, opt_state->pre_1_8_dump, !opt_state->quiet ? repos_notify_handler : = NULL, feedback_stream, check_cancel, NULL, = pool)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_dump_revprops(apr_getopt_t *os, void *baton, apr_pool_t = *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_stream_t *out_stream; svn_revnum_t lower, upper; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); SVN_ERR(get_dump_range(&lower, &upper, repos, opt_state, pool)); /* Open the file or STDOUT, depending on whether -F was specified. */ if (opt_state->file) { apr_file_t *file; /* Overwrite existing files, same as with > redirection. */ SVN_ERR(svn_io_file_open(&file, opt_state->file, APR_WRITE | APR_CREATE | APR_TRUNCATE | APR_BUFFERED, APR_OS_DEFAULT, pool)); out_stream =3D svn_stream_from_aprfile2(file, FALSE, pool); } else SVN_ERR(svn_stream_for_stdout(&out_stream, pool)); /* Progress feedback goes to STDERR, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stderr, pool); SVN_ERR(svn_repos_dump_fs4(repos, out_stream, lower, upper, FALSE, FALSE, TRUE, FALSE, !opt_state->quiet ? repos_notify_handler : = NULL, feedback_stream, check_cancel, NULL, = pool)); return SVN_NO_ERROR; } struct freeze_baton_t { const char *command; const char **args; int status; }; /* Implements svn_repos_freeze_func_t */ static svn_error_t * freeze_body(void *baton, apr_pool_t *pool) { struct freeze_baton_t *b =3D baton; apr_status_t apr_err; apr_file_t *infile, *outfile, *errfile; apr_err =3D apr_file_open_stdin(&infile, pool); if (apr_err) return svn_error_wrap_apr(apr_err, "Can't open stdin"); apr_err =3D apr_file_open_stdout(&outfile, pool); if (apr_err) return svn_error_wrap_apr(apr_err, "Can't open stdout"); apr_err =3D apr_file_open_stderr(&errfile, pool); if (apr_err) return svn_error_wrap_apr(apr_err, "Can't open stderr"); SVN_ERR(svn_io_run_cmd(NULL, b->command, b->args, &b->status, NULL, TRUE, infile, outfile, errfile, pool)); return SVN_NO_ERROR; } static svn_error_t * subcommand_freeze(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *paths; apr_array_header_t *args; int i; struct freeze_baton_t b; SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); if (!args->nelts) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, _("No program provided")); if (!opt_state->file) { /* One repository on the command line. */ paths =3D apr_array_make(pool, 1, sizeof(const char *)); APR_ARRAY_PUSH(paths, const char *) =3D = opt_state->repository_path; } else { svn_stringbuf_t *buf; const char *utf8; /* Read repository paths from the -F file. */ SVN_ERR(svn_stringbuf_from_file2(&buf, opt_state->file, pool)); SVN_ERR(svn_utf_cstring_to_utf8(&utf8, buf->data, pool)); paths =3D svn_cstring_split(utf8, "\r\n", FALSE, pool); } b.command =3D APR_ARRAY_IDX(args, 0, const char *); b.args =3D apr_palloc(pool, sizeof(char *) * (args->nelts + 1)); for (i =3D 0; i < args->nelts; ++i) b.args[i] =3D APR_ARRAY_IDX(args, i, const char *); b.args[args->nelts] =3D NULL; SVN_ERR(svn_repos_freeze(paths, freeze_body, &b, pool)); /* Make any non-zero status visible to the user. */ if (b.status) exit(b.status); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; const char *header =3D _("general usage: svnadmin SUBCOMMAND REPOS_PATH [ARGS & OPTIONS = ...]\n" "Subversion repository administration tool.\n" "Type 'svnadmin help ' for help on a specific = subcommand.\n" "Type 'svnadmin --version' to see the program version and FS = modules.\n" "\n" "Available subcommands:\n"); const char *fs_desc_start =3D _("The following repository back-end (FS) modules are = available:\n\n"); svn_stringbuf_t *version_footer; version_footer =3D svn_stringbuf_create(fs_desc_start, pool); SVN_ERR(svn_fs_print_modules(version_footer, pool)); SVN_ERR(svn_opt_print_help4(os, "svnadmin", opt_state ? opt_state->version : FALSE, opt_state ? opt_state->quiet : FALSE, /*###opt_state ? opt_state->verbose :*/ = FALSE, version_footer->data, header, cmd_table, options_table, NULL, = NULL, pool)); return SVN_NO_ERROR; } /* Set *REVNUM to the revision number of a numeric REV, or to SVN_INVALID_REVNUM if REV is unspecified. */ static svn_error_t * optrev_to_revnum(svn_revnum_t *revnum, const svn_opt_revision_t = *opt_rev) { if (opt_rev->kind =3D=3D svn_opt_revision_number) { *revnum =3D opt_rev->value.number; if (! SVN_IS_VALID_REVNUM(*revnum)) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Invalid revision number (%ld) = specified"), *revnum); } else if (opt_rev->kind =3D=3D svn_opt_revision_unspecified) { *revnum =3D SVN_INVALID_REVNUM; } else { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Non-numeric revision specified")); } return SVN_NO_ERROR; } /* Read the min / max revision from the OPT_STATE, verify them and = return them in *LOWER and *UPPER, respectively. */ static svn_error_t * get_load_range(svn_revnum_t *lower, svn_revnum_t *upper, struct svnadmin_opt_state *opt_state) { /* Find the revision numbers at which to start and end. We only support a limited set of revision kinds: number and unspecified. */ SVN_ERR(optrev_to_revnum(lower, &opt_state->start_revision)); SVN_ERR(optrev_to_revnum(upper, &opt_state->end_revision)); /* Fill in implied revisions if necessary. */ if ((*upper =3D=3D SVN_INVALID_REVNUM) && (*lower !=3D = SVN_INVALID_REVNUM)) { *upper =3D *lower; } else if ((*upper !=3D SVN_INVALID_REVNUM) && (*lower =3D=3D = SVN_INVALID_REVNUM)) { *lower =3D *upper; } /* Ensure correct range ordering. */ if (*lower > *upper) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("First revision cannot be higher than = second")); } return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_load(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_error_t *err; struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_revnum_t lower, upper; svn_stream_t *in_stream; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); /* Find the revision numbers at which to start and end. We only support a limited set of revision kinds: number and unspecified. */ SVN_ERR(get_load_range(&lower, &upper, opt_state)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); /* Open the file or STDIN, depending on whether -F was specified. */ if (opt_state->file) SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file, pool, pool)); else SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool)); /* Progress feedback goes to STDOUT, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stdout, pool); err =3D svn_repos_load_fs5(repos, in_stream, lower, upper, opt_state->uuid_action, = opt_state->parent_dir, opt_state->use_pre_commit_hook, opt_state->use_post_commit_hook, !opt_state->bypass_prop_validation, opt_state->ignore_dates, opt_state->quiet ? NULL : = repos_notify_handler, feedback_stream, check_cancel, NULL, pool); if (err && err->apr_err =3D=3D SVN_ERR_BAD_PROPERTY_VALUE) return svn_error_quick_wrap(err, _("Invalid property value found in " "dumpstream; consider repairing the = source " "or using --bypass-prop-validation = while " "loading.")); return err; } static svn_error_t * subcommand_load_revprops(apr_getopt_t *os, void *baton, apr_pool_t = *pool) { svn_error_t *err; struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_revnum_t lower, upper; svn_stream_t *in_stream; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); /* Find the revision numbers at which to start and end. We only support a limited set of revision kinds: number and unspecified. */ SVN_ERR(get_load_range(&lower, &upper, opt_state)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); /* Open the file or STDIN, depending on whether -F was specified. */ if (opt_state->file) SVN_ERR(svn_stream_open_readonly(&in_stream, opt_state->file, pool, pool)); else SVN_ERR(svn_stream_for_stdin2(&in_stream, TRUE, pool)); /* Progress feedback goes to STDOUT, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stdout, pool); err =3D svn_repos_load_fs_revprops(repos, in_stream, lower, upper, !opt_state->bypass_prop_validation, opt_state->ignore_dates, opt_state->quiet ? NULL : = repos_notify_handler, feedback_stream, check_cancel, NULL, = pool); if (err && err->apr_err =3D=3D SVN_ERR_BAD_PROPERTY_VALUE) return svn_error_quick_wrap(err, _("Invalid property value found in " "dumpstream; consider repairing the = source " "or using --bypass-prop-validation = while " "loading.")); return err; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_lstxns(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; apr_array_header_t *txns; apr_pool_t *iterpool; svn_revnum_t youngest, limit; int i; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); if (opt_state->end_revision.kind !=3D svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Revision range is not allowed")); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); SVN_ERR(svn_fs_list_transactions(&txns, fs, pool)); SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); SVN_ERR(get_revnum(&limit, &opt_state->start_revision, youngest, = repos, pool)); =20 iterpool =3D svn_pool_create(pool); for (i =3D 0; i < txns->nelts; i++) { const char *name =3D APR_ARRAY_IDX(txns, i, const char *); svn_boolean_t show =3D TRUE; svn_pool_clear(iterpool); if (limit !=3D SVN_INVALID_REVNUM) { svn_fs_txn_t *txn; svn_revnum_t base; SVN_ERR(svn_fs_open_txn(&txn, fs, name, iterpool)); base =3D svn_fs_txn_base_revision(txn); if (base > limit) show =3D FALSE; } if (show) SVN_ERR(svn_cmdline_printf(pool, "%s\n", name)); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_recover(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_revnum_t youngest_rev; svn_repos_t *repos; svn_error_t *err; struct svnadmin_opt_state *opt_state =3D baton; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool)); /* Restore default signal handlers until after we have acquired the * exclusive lock so that the user interrupt before we actually * touch the repository. */ svn_cmdline__disable_cancellation_handler(); err =3D svn_repos_recover4(opt_state->repository_path, TRUE, repos_notify_handler, feedback_stream, check_cancel, NULL, pool); if (err) { if (! APR_STATUS_IS_EAGAIN(err->apr_err)) return err; svn_error_clear(err); if (! opt_state->wait) return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL, _("Failed to get exclusive repository " "access; perhaps another process\n" "such as httpd, svnserve or svn " "has it open?")); SVN_ERR(svn_cmdline_printf(pool, _("Waiting on repository lock; perhaps" " another process has it open?\n"))); SVN_ERR(svn_cmdline_fflush(stdout)); SVN_ERR(svn_repos_recover4(opt_state->repository_path, FALSE, repos_notify_handler, feedback_stream, check_cancel, NULL, pool)); } SVN_ERR(svn_cmdline_printf(pool, _("\nRecovery completed.\n"))); /* Since db transactions may have been replayed, it's nice to tell people what the latest revision is. It also proves that the recovery actually worked. */ SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); SVN_ERR(svn_fs_youngest_rev(&youngest_rev, svn_repos_fs(repos), = pool)); SVN_ERR(svn_cmdline_printf(pool, _("The latest repos revision is = %ld.\n"), youngest_rev)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * list_dblogs(apr_getopt_t *os, void *baton, svn_boolean_t only_unused, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *logfiles; int i; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(svn_repos_db_logfiles(&logfiles, opt_state->repository_path, only_unused, pool)); /* Loop, printing log files. We append the log paths to the repository path, making sure to return everything to the native style before printing. */ for (i =3D 0; i < logfiles->nelts; i++) { const char *log_utf8; log_utf8 =3D svn_dirent_join(opt_state->repository_path, APR_ARRAY_IDX(logfiles, i, const char = *), pool); log_utf8 =3D svn_dirent_local_style(log_utf8, pool); SVN_ERR(svn_cmdline_printf(pool, "%s\n", log_utf8)); } return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_list_dblogs(apr_getopt_t *os, void *baton, apr_pool_t *pool) { SVN_ERR(list_dblogs(os, baton, FALSE, pool)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_list_unused_dblogs(apr_getopt_t *os, void *baton, apr_pool_t = *pool) { /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(list_dblogs(os, baton, TRUE, pool)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_rmtxns(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_fs_txn_t *txn; apr_array_header_t *args; int i; apr_pool_t *subpool =3D svn_pool_create(pool); SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); /* All the rest of the arguments are transaction names. */ for (i =3D 0; i < args->nelts; i++) { const char *txn_name =3D APR_ARRAY_IDX(args, i, const char *); const char *txn_name_utf8; svn_error_t *err; svn_pool_clear(subpool); SVN_ERR(svn_utf_cstring_to_utf8(&txn_name_utf8, txn_name, = subpool)); /* Try to open the txn. If that succeeds, try to abort it. */ err =3D svn_fs_open_txn(&txn, fs, txn_name_utf8, subpool); if (! err) err =3D svn_fs_abort_txn(txn, subpool); /* If either the open or the abort of the txn fails because that transaction is dead, just try to purge the thing. Else, there was either an error worth reporting, or not error at all. */ if (err && (err->apr_err =3D=3D SVN_ERR_FS_TRANSACTION_DEAD)) { svn_error_clear(err); err =3D svn_fs_purge_txn(fs, txn_name_utf8, subpool); } /* If we had a real from the txn open, abort, or purge, we clear that error and just report to the user that we had an issue with this particular txn. */ if (err) { svn_handle_error2(err, stderr, FALSE /* non-fatal */, = "svnadmin: "); svn_error_clear(err); } else if (! opt_state->quiet) { SVN_ERR(svn_cmdline_printf(subpool, _("Transaction '%s' = removed.\n"), txn_name)); } } svn_pool_destroy(subpool); return SVN_NO_ERROR; } /* A helper for the 'setrevprop' and 'setlog' commands. Expects OPT_STATE->txn_id, OPT_STATE->use_pre_revprop_change_hook and OPT_STATE->use_post_revprop_change_hook to be set appropriately. If FILENAME is NULL, delete property PROP_NAME. */ static svn_error_t * set_revprop(const char *prop_name, const char *filename, struct svnadmin_opt_state *opt_state, apr_pool_t *pool) { svn_repos_t *repos; svn_string_t *prop_value; if (filename) { svn_stringbuf_t *file_contents; SVN_ERR(svn_stringbuf_from_file2(&file_contents, filename, pool)); prop_value =3D svn_string_create_empty(pool); prop_value->data =3D file_contents->data; prop_value->len =3D file_contents->len; SVN_ERR(svn_subst_translate_string2(&prop_value, NULL, NULL, = prop_value, NULL, FALSE, pool, pool)); } else { prop_value =3D NULL; } /* Open the filesystem */ SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); if (opt_state->txn_id) { svn_fs_t *fs =3D svn_repos_fs(repos); svn_fs_txn_t *txn; SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool)); SVN_ERR(svn_fs_change_txn_prop(txn, prop_name, prop_value, pool)); } else SVN_ERR(svn_repos_fs_change_rev_prop4( repos, opt_state->start_revision.value.number, NULL, prop_name, NULL, prop_value, opt_state->use_pre_revprop_change_hook, opt_state->use_post_revprop_change_hook, NULL, NULL, pool)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_setrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *args; const char *prop_name, *filename; /* Expect two more arguments: NAME FILE */ SVN_ERR(parse_args(&args, os, 2, 2, pool)); prop_name =3D APR_ARRAY_IDX(args, 0, const char *); filename =3D APR_ARRAY_IDX(args, 1, const char *); SVN_ERR(target_arg_to_dirent(&filename, filename, pool)); if (opt_state->txn_id) { if (opt_state->start_revision.kind !=3D = svn_opt_revision_unspecified || opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("--revision (-r) and --transaction = (-t) " "are mutually exclusive")); if (opt_state->use_pre_revprop_change_hook || opt_state->use_post_revprop_change_hook) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Calling hooks is incompatible with " "--transaction (-t)")); } else if (opt_state->start_revision.kind !=3D svn_opt_revision_number) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Missing revision")); else if (opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Only one revision allowed")); return set_revprop(prop_name, filename, opt_state, pool); } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_setuuid(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *args; svn_repos_t *repos; svn_fs_t *fs; const char *uuid =3D NULL; /* Expect zero or one more arguments: [UUID] */ SVN_ERR(parse_args(&args, os, 0, 1, pool)); if (args->nelts =3D=3D 1) uuid =3D APR_ARRAY_IDX(args, 0, const char *); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); return svn_fs_set_uuid(fs, uuid, pool); } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_setlog(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *args; const char *filename; /* Expect one more argument: FILE */ SVN_ERR(parse_args(&args, os, 1, 1, pool)); filename =3D APR_ARRAY_IDX(args, 0, const char *); SVN_ERR(target_arg_to_dirent(&filename, filename, pool)); if (opt_state->start_revision.kind !=3D svn_opt_revision_number) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Missing revision")); else if (opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Only one revision allowed")); /* set_revprop() responds only to pre-/post-revprop-change opts. */ if (!opt_state->bypass_hooks) { opt_state->use_pre_revprop_change_hook =3D TRUE; opt_state->use_post_revprop_change_hook =3D TRUE; } return set_revprop(SVN_PROP_REVISION_LOG, filename, opt_state, pool); } /* This implements 'svn_opt_subcommand_t'. */ static svn_error_t * subcommand_pack(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); /* Progress feedback goes to STDOUT, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stdout, pool); return svn_error_trace( svn_repos_fs_pack2(repos, !opt_state->quiet ? repos_notify_handler : = NULL, feedback_stream, check_cancel, NULL, pool)); } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_verify(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_revnum_t youngest, lower, upper; svn_stream_t *feedback_stream =3D NULL; struct repos_verify_callback_baton verify_baton =3D { 0 }; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); if (opt_state->txn_id && (opt_state->start_revision.kind !=3D = svn_opt_revision_unspecified || opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified)) { return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("--revision (-r) and --transaction (-t) = " "are mutually exclusive")); } SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); /* Usage 2. */ if (opt_state->txn_id) { svn_fs_txn_t *txn; svn_fs_root_t *root; SVN_ERR(svn_fs_open_txn(&txn, fs, opt_state->txn_id, pool)); SVN_ERR(svn_fs_txn_root(&root, txn, pool)); SVN_ERR(svn_fs_verify_root(root, pool)); return SVN_NO_ERROR; } else /* Usage 1. */ ; /* Find the revision numbers at which to start and end. */ SVN_ERR(get_revnum(&lower, &opt_state->start_revision, youngest, repos, pool)); SVN_ERR(get_revnum(&upper, &opt_state->end_revision, youngest, repos, pool)); if (upper =3D=3D SVN_INVALID_REVNUM) { upper =3D lower; } if (!opt_state->quiet) feedback_stream =3D recode_stream_create(stdout, pool); verify_baton.keep_going =3D opt_state->keep_going; verify_baton.error_summary =3D apr_array_make(pool, 0, sizeof(struct verification_error *)); verify_baton.result_pool =3D pool; SVN_ERR(svn_repos_verify_fs3(repos, lower, upper, opt_state->check_normalization, opt_state->metadata_only, !opt_state->quiet ? repos_notify_handler : NULL, feedback_stream, repos_verify_callback, &verify_baton, check_cancel, NULL, pool)); /* Show the --keep-going error summary. */ if (!opt_state->quiet && opt_state->keep_going && verify_baton.error_summary->nelts > 0) { int rev_maxlength; svn_revnum_t end_revnum; apr_pool_t *iterpool; int i; svn_error_clear( svn_stream_puts(feedback_stream, _("\n-----Summary of corrupt = revisions-----\n"))); /* The standard column width for the revision number is 6 = characters. If the revision number can potentially be larger (i.e. if = end_revnum is larger than 1000000), we increase the column width as = needed. */ rev_maxlength =3D 6; end_revnum =3D APR_ARRAY_IDX(verify_baton.error_summary, verify_baton.error_summary->nelts - 1, struct verification_error *)->rev; while (end_revnum >=3D 1000000) { rev_maxlength++; end_revnum =3D end_revnum / 10; } iterpool =3D svn_pool_create(pool); for (i =3D 0; i < verify_baton.error_summary->nelts; i++) { struct verification_error *verr; svn_error_t *err; const char *rev_str; svn_pool_clear(iterpool); verr =3D APR_ARRAY_IDX(verify_baton.error_summary, i, struct verification_error *); if (verr->rev !=3D SVN_INVALID_REVNUM) { rev_str =3D apr_psprintf(iterpool, "r%ld", verr->rev); rev_str =3D apr_psprintf(iterpool, "%*s", rev_maxlength, = rev_str); for (err =3D svn_error_purge_tracing(verr->err); err !=3D SVN_NO_ERROR; err =3D err->child) { char buf[512]; const char *message; message =3D svn_err_best_message(err, buf, = sizeof(buf)); svn_error_clear(svn_stream_printf(feedback_stream, = iterpool, "%s: E%06d: %s\n", rev_str, = err->apr_err, message)); } } } svn_pool_destroy(iterpool); } if (verify_baton.error_summary->nelts > 0) { return svn_error_createf(SVN_ERR_CL_REPOS_VERIFY_FAILED, NULL, _("Failed to verify repository '%s'"), svn_dirent_local_style( opt_state->repository_path, pool)); } return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ svn_error_t * subcommand_hotcopy(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_stream_t *feedback_stream =3D NULL; apr_array_header_t *targets; const char *new_repos_path; /* Expect one more argument: NEW_REPOS_PATH */ SVN_ERR(parse_args(&targets, os, 1, 1, pool)); new_repos_path =3D APR_ARRAY_IDX(targets, 0, const char *); SVN_ERR(target_arg_to_dirent(&new_repos_path, new_repos_path, pool)); /* Progress feedback goes to STDOUT, unless they asked to suppress it. = */ if (! opt_state->quiet) feedback_stream =3D recode_stream_create(stdout, pool); return svn_repos_hotcopy3(opt_state->repository_path, new_repos_path, opt_state->clean_logs, = opt_state->incremental, !opt_state->quiet ? repos_notify_handler : = NULL, feedback_stream, check_cancel, NULL, pool); } svn_error_t * subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; int fs_format; const char *uuid; svn_revnum_t head_rev; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); SVN_ERR(svn_cmdline_printf(pool, _("Path: %s\n"), = svn_dirent_local_style(svn_repos_path(repos, pool), pool))); SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); SVN_ERR(svn_cmdline_printf(pool, _("UUID: %s\n"), uuid)); SVN_ERR(svn_fs_youngest_rev(&head_rev, fs, pool)); SVN_ERR(svn_cmdline_printf(pool, _("Revisions: %ld\n"), head_rev)); { int repos_format, minor; svn_version_t *repos_version, *fs_version; SVN_ERR(svn_repos_info_format(&repos_format, &repos_version, repos, pool, pool)); SVN_ERR(svn_cmdline_printf(pool, _("Repository Format: %d\n"), repos_format)); SVN_ERR(svn_fs_info_format(&fs_format, &fs_version, fs, pool, pool)); /* fs_format will be printed later. */ SVN_ERR_ASSERT(repos_version->major =3D=3D SVN_VER_MAJOR); SVN_ERR_ASSERT(fs_version->major =3D=3D SVN_VER_MAJOR); SVN_ERR_ASSERT(repos_version->patch =3D=3D 0); SVN_ERR_ASSERT(fs_version->patch =3D=3D 0); minor =3D (repos_version->minor > fs_version->minor) ? repos_version->minor : fs_version->minor; SVN_ERR(svn_cmdline_printf(pool, _("Compatible With Version: = %d.%d.0\n"), SVN_VER_MAJOR, minor)); } { apr_hash_t *capabilities_set; apr_array_header_t *capabilities; int i; SVN_ERR(svn_repos_capabilities(&capabilities_set, repos, pool, = pool)); capabilities =3D svn_sort__hash(capabilities_set, svn_sort_compare_items_lexically, pool); for (i =3D 0; i < capabilities->nelts; i++) { svn_sort__item_t *item =3D &APR_ARRAY_IDX(capabilities, i, svn_sort__item_t); const char *capability =3D item->key; SVN_ERR(svn_cmdline_printf(pool, _("Repository Capability: = %s\n"), capability)); } } { const svn_fs_info_placeholder_t *info; SVN_ERR(svn_fs_info(&info, fs, pool, pool)); SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Type: %s\n"), info->fs_type)); SVN_ERR(svn_cmdline_printf(pool, _("Filesystem Format: %d\n"), fs_format)); if (!strcmp(info->fs_type, SVN_FS_TYPE_FSFS)) { const svn_fs_fsfs_info_t *fsfs_info =3D (const void *)info; if (fsfs_info->shard_size) SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: yes\n"))); else SVN_ERR(svn_cmdline_printf(pool, _("FSFS Sharded: no\n"))); if (fsfs_info->shard_size) SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shard Size: %d\n"), fsfs_info->shard_size)); /* Print packing statistics, if enabled on the FS. */ if (fsfs_info->shard_size) { const int shard_size =3D fsfs_info->shard_size; const long shards_packed =3D fsfs_info->min_unpacked_rev / = shard_size; const long shards_full =3D (head_rev + 1) / shard_size; SVN_ERR(svn_cmdline_printf(pool, _("FSFS Shards Packed: = %ld/%ld\n"), shards_packed, shards_full)); } if (fsfs_info->log_addressing) SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: = yes\n"))); else SVN_ERR(svn_cmdline_printf(pool, _("FSFS Logical Addressing: = no\n"))); } else if (!strcmp(info->fs_type, SVN_FS_TYPE_FSX)) { const svn_fs_fsx_info_t *fsx_info =3D (const void *)info; const int shard_size =3D fsx_info->shard_size; const long shards_packed =3D fsx_info->min_unpacked_rev / = shard_size; long shards_full =3D (head_rev + 1) / shard_size; SVN_ERR(svn_cmdline_printf(pool, _("FSX Shard Size: %d\n"), shard_size)); SVN_ERR(svn_cmdline_printf(pool, _("FSX Shards Packed: = %ld/%ld\n"), shards_packed, shards_full)); } } { apr_array_header_t *files; int i; SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool)); for (i =3D 0; i < files->nelts; i++) SVN_ERR(svn_cmdline_printf(pool, _("Configuration File: %s\n"), svn_dirent_local_style( APR_ARRAY_IDX(files, i, const char = *), pool))); } /* 'svn info' prints an extra newline here, to support multiple = targets. We'll do the same. */ SVN_ERR(svn_cmdline_printf(pool, "\n")); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_fs_access_t *access; apr_array_header_t *args; const char *username; const char *lock_path; const char *comment_file_name; svn_stringbuf_t *file_contents; const char *lock_path_utf8; svn_lock_t *lock; const char *lock_token =3D NULL; /* Expect three more arguments: PATH USERNAME COMMENT-FILE */ SVN_ERR(parse_args(&args, os, 3, 4, pool)); lock_path =3D APR_ARRAY_IDX(args, 0, const char *); username =3D APR_ARRAY_IDX(args, 1, const char *); comment_file_name =3D APR_ARRAY_IDX(args, 2, const char *); /* Expect one more optional argument: TOKEN */ if (args->nelts =3D=3D 4) lock_token =3D APR_ARRAY_IDX(args, 3, const char *); SVN_ERR(target_arg_to_dirent(&comment_file_name, comment_file_name, = pool)); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); /* Create an access context describing the user. */ SVN_ERR(svn_fs_create_access(&access, username, pool)); /* Attach the access context to the filesystem. */ SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_stringbuf_from_file2(&file_contents, comment_file_name, = pool)); SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool)); if (opt_state->bypass_hooks) SVN_ERR(svn_fs_lock(&lock, fs, lock_path_utf8, lock_token, file_contents->data, /* comment */ 0, /* is_dav_comment */ 0, /* no expiration time. */ SVN_INVALID_REVNUM, FALSE, pool)); else SVN_ERR(svn_repos_fs_lock(&lock, repos, lock_path_utf8, lock_token, file_contents->data, /* comment */ 0, /* is_dav_comment */ 0, /* no expiration = time. */ SVN_INVALID_REVNUM, FALSE, pool)); SVN_ERR(svn_cmdline_printf(pool, _("'%s' locked by user '%s'.\n"), lock_path, username)); return SVN_NO_ERROR; } static svn_error_t * subcommand_lslocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *targets; svn_repos_t *repos; const char *fs_path =3D "/"; apr_hash_t *locks; apr_hash_index_t *hi; apr_pool_t *iterpool =3D svn_pool_create(pool); SVN_ERR(svn_opt__args_to_target_array(&targets, os, apr_array_make(pool, 0, sizeof(const char = *)), pool)); if (targets->nelts > 1) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, _("Too many arguments given")); if (targets->nelts) fs_path =3D APR_ARRAY_IDX(targets, 0, const char *); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); /* Fetch all locks on or below the root directory. */ SVN_ERR(svn_repos_fs_get_locks2(&locks, repos, fs_path, = svn_depth_infinity, NULL, NULL, pool)); for (hi =3D apr_hash_first(pool, locks); hi; hi =3D apr_hash_next(hi)) { const char *cr_date, *exp_date =3D ""; const char *path =3D apr_hash_this_key(hi); svn_lock_t *lock =3D apr_hash_this_val(hi); int comment_lines =3D 0; svn_pool_clear(iterpool); SVN_ERR(check_cancel(NULL)); cr_date =3D svn_time_to_human_cstring(lock->creation_date, = iterpool); if (lock->expiration_date) exp_date =3D svn_time_to_human_cstring(lock->expiration_date, = iterpool); if (lock->comment) comment_lines =3D svn_cstring_count_newlines(lock->comment) + 1; SVN_ERR(svn_cmdline_printf(iterpool, _("Path: %s\n"), path)); SVN_ERR(svn_cmdline_printf(iterpool, _("UUID Token: %s\n"), = lock->token)); SVN_ERR(svn_cmdline_printf(iterpool, _("Owner: %s\n"), = lock->owner)); SVN_ERR(svn_cmdline_printf(iterpool, _("Created: %s\n"), = cr_date)); SVN_ERR(svn_cmdline_printf(iterpool, _("Expires: %s\n"), = exp_date)); SVN_ERR(svn_cmdline_printf(iterpool, Q_("Comment (%i line):\n%s\n\n", "Comment (%i lines):\n%s\n\n", comment_lines), comment_lines, lock->comment ? lock->comment : "")); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; } static svn_error_t * subcommand_rmlocks(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_fs_access_t *access; svn_error_t *err; apr_array_header_t *args; int i; const char *username; apr_pool_t *subpool =3D svn_pool_create(pool); SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); /* svn_fs_unlock() demands that some username be associated with the filesystem, so just use the UID of the person running 'svnadmin'.*/ username =3D svn_user_get_name(pool); if (! username) username =3D "administrator"; /* Create an access context describing the current user. */ SVN_ERR(svn_fs_create_access(&access, username, pool)); /* Attach the access context to the filesystem. */ SVN_ERR(svn_fs_set_access(fs, access)); /* Parse out any options. */ SVN_ERR(svn_opt_parse_all_args(&args, os, pool)); /* Our usage requires at least one FS path. */ if (args->nelts =3D=3D 0) return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, 0, _("No paths to unlock provided")); /* All the rest of the arguments are paths from which to remove locks. = */ for (i =3D 0; i < args->nelts; i++) { const char *lock_path =3D APR_ARRAY_IDX(args, i, const char *); const char *lock_path_utf8; svn_lock_t *lock; SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, = subpool)); /* Fetch the path's svn_lock_t. */ err =3D svn_fs_get_lock(&lock, fs, lock_path_utf8, subpool); if (err) goto move_on; if (! lock) { SVN_ERR(svn_cmdline_printf(subpool, _("Path '%s' isn't locked.\n"), lock_path)); continue; } /* Now forcibly destroy the lock. */ err =3D svn_fs_unlock(fs, lock_path_utf8, lock->token, 1 /* force */, subpool); if (err) goto move_on; SVN_ERR(svn_cmdline_printf(subpool, _("Removed lock on '%s'.\n"), = lock->path)); move_on: if (err) { /* Print the error, but move on to the next lock. */ svn_handle_error2(err, stderr, FALSE /* non-fatal */, = "svnadmin: "); svn_error_clear(err); } svn_pool_clear(subpool); } svn_pool_destroy(subpool); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_unlock(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; svn_repos_t *repos; svn_fs_t *fs; svn_fs_access_t *access; apr_array_header_t *args; const char *username; const char *lock_path; const char *lock_path_utf8; const char *lock_token =3D NULL; /* Expect three more arguments: PATH USERNAME TOKEN */ SVN_ERR(parse_args(&args, os, 3, 3, pool)); lock_path =3D APR_ARRAY_IDX(args, 0, const char *); username =3D APR_ARRAY_IDX(args, 1, const char *); lock_token =3D APR_ARRAY_IDX(args, 2, const char *); /* Open the repos/FS, and associate an access context containing USERNAME. */ SVN_ERR(open_repos(&repos, opt_state->repository_path, opt_state, = pool)); fs =3D svn_repos_fs(repos); SVN_ERR(svn_fs_create_access(&access, username, pool)); SVN_ERR(svn_fs_set_access(fs, access)); SVN_ERR(svn_utf_cstring_to_utf8(&lock_path_utf8, lock_path, pool)); if (opt_state->bypass_hooks) SVN_ERR(svn_fs_unlock(fs, lock_path_utf8, lock_token, FALSE, pool)); else SVN_ERR(svn_repos_fs_unlock(repos, lock_path_utf8, lock_token, FALSE, pool)); SVN_ERR(svn_cmdline_printf(pool, _("'%s' unlocked by user '%s'.\n"), lock_path, username)); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_upgrade(apr_getopt_t *os, void *baton, apr_pool_t *pool) { svn_error_t *err; struct svnadmin_opt_state *opt_state =3D baton; svn_stream_t *feedback_stream =3D NULL; /* Expect no more arguments. */ SVN_ERR(parse_args(NULL, os, 0, 0, pool)); SVN_ERR(svn_stream_for_stdout(&feedback_stream, pool)); /* Restore default signal handlers. */ svn_cmdline__disable_cancellation_handler(); err =3D svn_repos_upgrade2(opt_state->repository_path, TRUE, repos_notify_handler, feedback_stream, pool); if (err) { if (APR_STATUS_IS_EAGAIN(err->apr_err)) { svn_error_clear(err); err =3D SVN_NO_ERROR; if (! opt_state->wait) return svn_error_create(SVN_ERR_REPOS_LOCKED, NULL, _("Failed to get exclusive = repository " "access; perhaps another = process\n" "such as httpd, svnserve or svn " "has it open?")); SVN_ERR(svn_cmdline_printf(pool, _("Waiting on repository lock; = perhaps" " another process has it = open?\n"))); SVN_ERR(svn_cmdline_fflush(stdout)); SVN_ERR(svn_repos_upgrade2(opt_state->repository_path, FALSE, repos_notify_handler, = feedback_stream, pool)); } else if (err->apr_err =3D=3D SVN_ERR_FS_UNSUPPORTED_UPGRADE) { return svn_error_quick_wrap(err, _("Upgrade of this repository's underlying versioned = " "filesystem is not supported; consider " "dumping and loading the data elsewhere")); } else if (err->apr_err =3D=3D SVN_ERR_REPOS_UNSUPPORTED_UPGRADE) { return svn_error_quick_wrap(err, _("Upgrade of this repository is not supported; = consider " "dumping and loading the data elsewhere")); } } SVN_ERR(err); SVN_ERR(svn_cmdline_printf(pool, _("\nUpgrade completed.\n"))); return SVN_NO_ERROR; } /* This implements `svn_opt_subcommand_t'. */ static svn_error_t * subcommand_delrevprop(apr_getopt_t *os, void *baton, apr_pool_t *pool) { struct svnadmin_opt_state *opt_state =3D baton; apr_array_header_t *args; const char *prop_name; /* Expect one more argument: NAME */ SVN_ERR(parse_args(&args, os, 1, 1, pool)); prop_name =3D APR_ARRAY_IDX(args, 0, const char *); if (opt_state->txn_id) { if (opt_state->start_revision.kind !=3D = svn_opt_revision_unspecified || opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("--revision (-r) and --transaction = (-t) " "are mutually exclusive")); if (opt_state->use_pre_revprop_change_hook || opt_state->use_post_revprop_change_hook) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Calling hooks is incompatible with " "--transaction (-t)")); } else if (opt_state->start_revision.kind !=3D svn_opt_revision_number) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Missing revision")); else if (opt_state->end_revision.kind !=3D = svn_opt_revision_unspecified) return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Only one revision allowed")); return set_revprop(prop_name, NULL, opt_state, pool); } =0C /** Main. **/ /* * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On = error, * either return an error to be displayed, or set *EXIT_CODE to non-zero = and * return SVN_NO_ERROR. */ static svn_error_t * sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool) { svn_error_t *err; apr_status_t apr_err; const svn_opt_subcommand_desc2_t *subcommand =3D NULL; struct svnadmin_opt_state opt_state =3D { 0 }; apr_getopt_t *os; int opt_id; apr_array_header_t *received_opts; int i; svn_boolean_t dash_F_arg =3D FALSE; received_opts =3D apr_array_make(pool, SVN_OPT_MAX_OPTIONS, = sizeof(int)); /* Check library versions */ SVN_ERR(check_lib_versions()); /* Initialize the FS library. */ SVN_ERR(svn_fs_initialize(pool)); if (argc <=3D 1) { SVN_ERR(subcommand_help(NULL, NULL, pool)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } /* Initialize opt_state. */ opt_state.start_revision.kind =3D svn_opt_revision_unspecified; opt_state.end_revision.kind =3D svn_opt_revision_unspecified; opt_state.memory_cache_size =3D svn_cache_config_get()->cache_size; /* Parse options. */ SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool)); os->interleave =3D 1; while (1) { const char *opt_arg; const char *utf8_opt_arg; /* Parse the next option. */ apr_err =3D apr_getopt_long(os, options_table, &opt_id, &opt_arg); if (APR_STATUS_IS_EOF(apr_err)) break; else if (apr_err) { SVN_ERR(subcommand_help(NULL, NULL, pool)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } /* Stash the option code in an array before parsing it. */ APR_ARRAY_PUSH(received_opts, int) =3D opt_id; switch (opt_id) { case 'r': { if (opt_state.start_revision.kind !=3D = svn_opt_revision_unspecified) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, = NULL, _("Multiple revision arguments encountered; " "try '-r N:M' instead of '-r N -r M'")); } if (svn_opt_parse_revision(&(opt_state.start_revision), &(opt_state.end_revision), opt_arg, pool) !=3D 0) { SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, = pool)); return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, = NULL, _("Syntax error in revision argument '%s'"), utf8_opt_arg); } } break; case 't': opt_state.txn_id =3D opt_arg; break; case 'q': opt_state.quiet =3D TRUE; break; case 'h': case '?': opt_state.help =3D TRUE; break; case 'M': opt_state.memory_cache_size =3D 0x100000 * apr_strtoi64(opt_arg, NULL, 0); break; case 'F': SVN_ERR(svn_utf_cstring_to_utf8(&(opt_state.file), opt_arg, = pool)); dash_F_arg =3D TRUE; break; case svnadmin__version: opt_state.version =3D TRUE; break; case svnadmin__incremental: opt_state.incremental =3D TRUE; break; case svnadmin__deltas: opt_state.use_deltas =3D TRUE; break; case svnadmin__ignore_uuid: opt_state.uuid_action =3D svn_repos_load_uuid_ignore; break; case svnadmin__force_uuid: opt_state.uuid_action =3D svn_repos_load_uuid_force; break; case svnadmin__pre_1_4_compatible: opt_state.compatible_version =3D apr_pcalloc(pool, = sizeof(svn_version_t)); opt_state.compatible_version->major =3D 1; opt_state.compatible_version->minor =3D 3; break; case svnadmin__pre_1_5_compatible: opt_state.compatible_version =3D apr_pcalloc(pool, = sizeof(svn_version_t)); opt_state.compatible_version->major =3D 1; opt_state.compatible_version->minor =3D 4; break; case svnadmin__pre_1_6_compatible: opt_state.compatible_version =3D apr_pcalloc(pool, = sizeof(svn_version_t)); opt_state.compatible_version->major =3D 1; opt_state.compatible_version->minor =3D 5; break; case svnadmin__compatible_version: { svn_version_t latest =3D { SVN_VER_MAJOR, SVN_VER_MINOR, SVN_VER_PATCH, NULL }; svn_version_t *compatible_version; /* Parse the version string which carries our target compatibility. */ SVN_ERR(svn_version__parse_version_string(&compatible_version, opt_arg, pool)); /* We can't create repository with a version older than 1.0.0. = */ if (! svn_version__at_least(compatible_version, 1, 0, 0)) { return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, = NULL, _("Cannot create = pre-1.0-compatible " "repositories")); } /* We can't create repository with a version newer than what the running version of Subversion supports. */ if (! svn_version__at_least(&latest, compatible_version->major, compatible_version->minor, compatible_version->patch)) { return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, = NULL, _("Cannot guarantee compatibility = " "beyond the current running = version " "(%s)"), SVN_VER_NUM); } opt_state.compatible_version =3D compatible_version; } break; case svnadmin__keep_going: opt_state.keep_going =3D TRUE; break; case svnadmin__check_normalization: opt_state.check_normalization =3D TRUE; break; case svnadmin__metadata_only: opt_state.metadata_only =3D TRUE; break; case svnadmin__fs_type: SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.fs_type, opt_arg, = pool)); break; case svnadmin__parent_dir: SVN_ERR(svn_utf_cstring_to_utf8(&opt_state.parent_dir, opt_arg, pool)); opt_state.parent_dir =3D svn_dirent_internal_style(opt_state.parent_dir, pool); break; case svnadmin__use_pre_commit_hook: opt_state.use_pre_commit_hook =3D TRUE; break; case svnadmin__use_post_commit_hook: opt_state.use_post_commit_hook =3D TRUE; break; case svnadmin__use_pre_revprop_change_hook: opt_state.use_pre_revprop_change_hook =3D TRUE; break; case svnadmin__use_post_revprop_change_hook: opt_state.use_post_revprop_change_hook =3D TRUE; break; case svnadmin__bdb_txn_nosync: opt_state.bdb_txn_nosync =3D TRUE; break; case svnadmin__bdb_log_keep: opt_state.bdb_log_keep =3D TRUE; break; case svnadmin__bypass_hooks: opt_state.bypass_hooks =3D TRUE; break; case svnadmin__bypass_prop_validation: opt_state.bypass_prop_validation =3D TRUE; break; case svnadmin__ignore_dates: opt_state.ignore_dates =3D TRUE; break; case svnadmin__clean_logs: opt_state.clean_logs =3D TRUE; break; case svnadmin__config_dir: SVN_ERR(svn_utf_cstring_to_utf8(&utf8_opt_arg, opt_arg, pool)); opt_state.config_dir =3D apr_pstrdup(pool, svn_dirent_canonicalize(utf8_opt_arg, = pool)); break; case svnadmin__wait: opt_state.wait =3D TRUE; break; case svnadmin__no_flush_to_disk: opt_state.no_flush_to_disk =3D TRUE; break; case svnadmin__pre_1_8_dump: opt_state.pre_1_8_dump =3D TRUE; break; default: { SVN_ERR(subcommand_help(NULL, NULL, pool)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } } /* close `switch' */ } /* close `while' */ /* If the user asked for help, then the rest of the arguments are the names of subcommands to get help on (if any), or else they're just typos/mistakes. Whatever the case, the subcommand to actually run is subcommand_help(). */ if (opt_state.help) subcommand =3D svn_opt_get_canonical_subcommand2(cmd_table, "help"); /* If we're not running the `help' subcommand, then look for a subcommand in the first argument. */ if (subcommand =3D=3D NULL) { if (os->ind >=3D os->argc) { if (opt_state.version) { /* Use the "help" subcommand to handle the "--version" = option. */ static const svn_opt_subcommand_desc2_t pseudo_cmd =3D { "--version", subcommand_help, {0}, "", {svnadmin__version, /* must accept its own option */ 'q', /* --quiet */ } }; subcommand =3D &pseudo_cmd; } else { svn_error_clear(svn_cmdline_fprintf(stderr, pool, _("subcommand argument = required\n"))); SVN_ERR(subcommand_help(NULL, NULL, pool)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } } else { const char *first_arg =3D os->argv[os->ind++]; subcommand =3D svn_opt_get_canonical_subcommand2(cmd_table, = first_arg); if (subcommand =3D=3D NULL) { const char *first_arg_utf8; SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg, pool)); svn_error_clear( svn_cmdline_fprintf(stderr, pool, _("Unknown subcommand: '%s'\n"), first_arg_utf8)); SVN_ERR(subcommand_help(NULL, NULL, pool)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } } } /* Every subcommand except `help' and `freeze' with '-F' require a second argument -- the repository path. Parse it out here and store it in opt_state. */ if (!(subcommand->cmd_func =3D=3D subcommand_help || (subcommand->cmd_func =3D=3D subcommand_freeze && = dash_F_arg))) { const char *repos_path =3D NULL; if (os->ind >=3D os->argc) { return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("Repository argument required")); } SVN_ERR(svn_utf_cstring_to_utf8(&repos_path, os->argv[os->ind++], = pool)); if (svn_path_is_url(repos_path)) { return svn_error_createf(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, _("'%s' is a URL when it should be a = " "local path"), repos_path); } opt_state.repository_path =3D = svn_dirent_internal_style(repos_path, pool); } /* Check that the subcommand wasn't passed any inappropriate options. = */ for (i =3D 0; i < received_opts->nelts; i++) { opt_id =3D APR_ARRAY_IDX(received_opts, i, int); /* All commands implicitly accept --help, so just skip over this when we see it. Note that we don't want to include this option in their "accepted options" list because it would be awfully redundant to display it in every commands' help text. */ if (opt_id =3D=3D 'h' || opt_id =3D=3D '?') continue; if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL)) { const char *optstr; const apr_getopt_option_t *badopt =3D svn_opt_get_option_from_code2(opt_id, options_table, = subcommand, pool); svn_opt_format_option(&optstr, badopt, FALSE, pool); if (subcommand->name[0] =3D=3D '-') SVN_ERR(subcommand_help(NULL, NULL, pool)); else svn_error_clear(svn_cmdline_fprintf(stderr, pool , _("Subcommand '%s' doesn't accept option = '%s'\n" "Type 'svnadmin help %s' for usage.\n"), subcommand->name, optstr, subcommand->name)); *exit_code =3D EXIT_FAILURE; return SVN_NO_ERROR; } } check_cancel =3D svn_cmdline__setup_cancellation_handler(); /* Configure FSFS caches for maximum efficiency with svnadmin. * Also, apply the respective command line parameters, if given. */ { svn_cache_config_t settings =3D *svn_cache_config_get(); settings.cache_size =3D opt_state.memory_cache_size; settings.single_threaded =3D TRUE; svn_cache_config_set(&settings); } /* Run the subcommand. */ err =3D (*subcommand->cmd_func)(os, &opt_state, pool); if (err) { /* For argument-related problems, suggest using the 'help' subcommand. */ if (err->apr_err =3D=3D SVN_ERR_CL_INSUFFICIENT_ARGS || err->apr_err =3D=3D SVN_ERR_CL_ARG_PARSING_ERROR) { err =3D svn_error_quick_wrap(err, _("Try 'svnadmin help' for more = info")); } return err; } return SVN_NO_ERROR; } int main(int argc, const char *argv[]) { apr_pool_t *pool; int exit_code =3D EXIT_SUCCESS; svn_error_t *err; /* Initialize the app. */ if (svn_cmdline_init("svnadmin", stderr) !=3D EXIT_SUCCESS) return EXIT_FAILURE; /* Create our top-level pool. Use a separate mutexless allocator, * given this application is single threaded. */ pool =3D apr_allocator_owner_get(svn_pool_create_allocator(FALSE)); err =3D sub_main(&exit_code, argc, argv, pool); /* Flush stdout and report if it fails. It would be flushed on exit = anyway but this makes sure that output is not silently lost if it fails. = */ err =3D svn_error_compose_create(err, svn_cmdline_fflush(stdout)); if (err) { exit_code =3D EXIT_FAILURE; svn_cmdline_handle_exit_error(err, NULL, "svnadmin: "); } svn_pool_destroy(pool); svn_cmdline__cancellation_exit(); return exit_code; } ------=_NextPart_000_000B_01D2755C.0AF560E0--