1251881Speter/* 2251881Speter * replay.c: an editor driver for changes made in a given revision 3251881Speter * or transaction 4251881Speter * 5251881Speter * ==================================================================== 6251881Speter * Licensed to the Apache Software Foundation (ASF) under one 7251881Speter * or more contributor license agreements. See the NOTICE file 8251881Speter * distributed with this work for additional information 9251881Speter * regarding copyright ownership. The ASF licenses this file 10251881Speter * to you under the Apache License, Version 2.0 (the 11251881Speter * "License"); you may not use this file except in compliance 12251881Speter * with the License. You may obtain a copy of the License at 13251881Speter * 14251881Speter * http://www.apache.org/licenses/LICENSE-2.0 15251881Speter * 16251881Speter * Unless required by applicable law or agreed to in writing, 17251881Speter * software distributed under the License is distributed on an 18251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19251881Speter * KIND, either express or implied. See the License for the 20251881Speter * specific language governing permissions and limitations 21251881Speter * under the License. 22251881Speter * ==================================================================== 23251881Speter */ 24251881Speter 25251881Speter 26251881Speter#include <apr_hash.h> 27251881Speter 28251881Speter#include "svn_types.h" 29251881Speter#include "svn_delta.h" 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_fs.h" 32251881Speter#include "svn_checksum.h" 33251881Speter#include "svn_repos.h" 34251881Speter#include "svn_sorts.h" 35251881Speter#include "svn_props.h" 36251881Speter#include "svn_pools.h" 37251881Speter#include "svn_path.h" 38251881Speter#include "svn_private_config.h" 39251881Speter#include "private/svn_fspath.h" 40251881Speter#include "private/svn_repos_private.h" 41251881Speter#include "private/svn_delta_private.h" 42289180Speter#include "private/svn_sorts_private.h" 43251881Speter 44251881Speter 45251881Speter/*** Backstory ***/ 46251881Speter 47251881Speter/* The year was 2003. Subversion usage was rampant in the world, and 48251881Speter there was a rapidly growing issues database to prove it. To make 49251881Speter matters worse, svn_repos_dir_delta() had simply outgrown itself. 50251881Speter No longer content to simply describe the differences between two 51251881Speter trees, the function had been slowly bearing the added 52251881Speter responsibility of representing the actions that had been taken to 53251881Speter cause those differences -- a burden it was never meant to bear. 54251881Speter Now grown into a twisted mess of razor-sharp metal and glass, and 55251881Speter trembling with a sort of momentarily stayed spring force, 56251881Speter svn_repos_dir_delta was a timebomb poised for total annihilation of 57251881Speter the American Midwest. 58251881Speter 59251881Speter Subversion needed a change. 60251881Speter 61251881Speter Changes, in fact. And not just in the literary segue sense. What 62251881Speter Subversion desperately needed was a new mechanism solely 63251881Speter responsible for replaying repository actions back to some 64251881Speter interested party -- to translate and retransmit the contents of the 65251881Speter Berkeley 'changes' database file. */ 66251881Speter 67251881Speter/*** Overview ***/ 68251881Speter 69251881Speter/* The filesystem keeps a record of high-level actions that affect the 70251881Speter files and directories in itself. The 'changes' table records 71251881Speter additions, deletions, textual and property modifications, and so 72251881Speter on. The goal of the functions in this file is to examine those 73251881Speter change records, and use them to drive an editor interface in such a 74251881Speter way as to effectively replay those actions. 75251881Speter 76251881Speter This is critically different than what svn_repos_dir_delta() was 77251881Speter designed to do. That function describes, in the simplest way it 78251881Speter can, how to transform one tree into another. It doesn't care 79251881Speter whether or not this was the same way a user might have done this 80251881Speter transformation. More to the point, it doesn't care if this is how 81251881Speter those differences *did* come into being. And it is for this reason 82251881Speter that it cannot be relied upon for tasks such as the repository 83251881Speter dumpfile-generation code, which is supposed to represent not 84251881Speter changes, but actions that cause changes. 85251881Speter 86251881Speter So, what's the plan here? 87251881Speter 88251881Speter First, we fetch the changes for a particular revision or 89251881Speter transaction. We get these as an array, sorted chronologically. 90251881Speter From this array we will build a hash, keyed on the path associated 91251881Speter with each change item, and whose values are arrays of changes made 92251881Speter to that path, again preserving the chronological ordering. 93251881Speter 94251881Speter Once our hash is built, we then sort all the keys of the hash (the 95251881Speter paths) using a depth-first directory sort routine. 96251881Speter 97251881Speter Finally, we drive an editor, moving down our list of sorted paths, 98251881Speter and manufacturing any intermediate editor calls (directory openings 99251881Speter and closures) needed to navigate between each successive path. For 100251881Speter each path, we replay the sorted actions that occurred at that path. 101251881Speter 102251881Speter When we've finished the editor drive, we should have fully replayed 103251881Speter the filesystem events that occurred in that revision or transaction 104251881Speter (though not necessarily in the same order in which they 105251881Speter occurred). */ 106251881Speter 107251881Speter/* #define USE_EV2_IMPL */ 108251881Speter 109251881Speter 110251881Speter/*** Helper functions. ***/ 111251881Speter 112251881Speter 113251881Speter/* Information for an active copy, that is a directory which we are currently 114251881Speter working on and which was added with history. */ 115251881Speterstruct copy_info 116251881Speter{ 117251881Speter /* Destination relpath (relative to the root of the . */ 118251881Speter const char *path; 119251881Speter 120251881Speter /* Copy source path (expressed as an absolute FS path) or revision. 121251881Speter NULL and SVN_INVALID_REVNUM if this is an add without history, 122251881Speter nested inside an add with history. */ 123251881Speter const char *copyfrom_path; 124251881Speter svn_revnum_t copyfrom_rev; 125251881Speter}; 126251881Speter 127251881Speterstruct path_driver_cb_baton 128251881Speter{ 129251881Speter /* The root of the revision we're replaying. */ 130251881Speter svn_fs_root_t *root; 131251881Speter 132251881Speter /* The root of the previous revision. If this is non-NULL it means that 133251881Speter we are supposed to generate props and text deltas relative to it. */ 134251881Speter svn_fs_root_t *compare_root; 135251881Speter 136251881Speter apr_hash_t *changed_paths; 137251881Speter 138251881Speter svn_repos_authz_func_t authz_read_func; 139251881Speter void *authz_read_baton; 140251881Speter 141251881Speter const char *base_path; /* relpath */ 142251881Speter 143251881Speter svn_revnum_t low_water_mark; 144251881Speter /* Stack of active copy operations. */ 145251881Speter apr_array_header_t *copies; 146251881Speter 147251881Speter /* The global pool for this replay operation. */ 148251881Speter apr_pool_t *pool; 149251881Speter}; 150251881Speter 151251881Speter/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting 152251881Speter the appropriate editor calls to add it and its children without any 153251881Speter history. This is meant to be used when either a subset of the tree 154251881Speter has been ignored and we need to copy something from that subset to 155251881Speter the part of the tree we do care about, or if a subset of the tree is 156251881Speter unavailable because of authz and we need to use it as the source of 157251881Speter a copy. */ 158251881Speterstatic svn_error_t * 159251881Speteradd_subdir(svn_fs_root_t *source_root, 160251881Speter svn_fs_root_t *target_root, 161251881Speter const svn_delta_editor_t *editor, 162251881Speter void *edit_baton, 163251881Speter const char *edit_path, 164251881Speter void *parent_baton, 165251881Speter const char *source_fspath, 166251881Speter svn_repos_authz_func_t authz_read_func, 167251881Speter void *authz_read_baton, 168251881Speter apr_hash_t *changed_paths, 169251881Speter apr_pool_t *pool, 170251881Speter void **dir_baton) 171251881Speter{ 172251881Speter apr_pool_t *subpool = svn_pool_create(pool); 173251881Speter apr_hash_index_t *hi, *phi; 174251881Speter apr_hash_t *dirents; 175251881Speter apr_hash_t *props; 176251881Speter 177251881Speter SVN_ERR(editor->add_directory(edit_path, parent_baton, NULL, 178251881Speter SVN_INVALID_REVNUM, pool, dir_baton)); 179251881Speter 180251881Speter SVN_ERR(svn_fs_node_proplist(&props, target_root, edit_path, pool)); 181251881Speter 182251881Speter for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 183251881Speter { 184289180Speter const char *key = apr_hash_this_key(phi); 185289180Speter svn_string_t *val = apr_hash_this_val(phi); 186251881Speter 187251881Speter svn_pool_clear(subpool); 188251881Speter SVN_ERR(editor->change_dir_prop(*dir_baton, key, val, subpool)); 189251881Speter } 190251881Speter 191251881Speter /* We have to get the dirents from the source path, not the target, 192251881Speter because we want nested copies from *readable* paths to be handled by 193251881Speter path_driver_cb_func, not add_subdir (in order to preserve history). */ 194251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, pool)); 195251881Speter 196251881Speter for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi)) 197251881Speter { 198362181Sdim svn_fs_path_change3_t *change; 199251881Speter svn_boolean_t readable = TRUE; 200289180Speter svn_fs_dirent_t *dent = apr_hash_this_val(hi); 201251881Speter const char *copyfrom_path = NULL; 202251881Speter svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 203251881Speter const char *new_edit_path; 204251881Speter 205251881Speter svn_pool_clear(subpool); 206251881Speter 207251881Speter new_edit_path = svn_relpath_join(edit_path, dent->name, subpool); 208251881Speter 209251881Speter /* If a file or subdirectory of the copied directory is listed as a 210251881Speter changed path (because it was modified after the copy but before the 211251881Speter commit), we remove it from the changed_paths hash so that future 212251881Speter calls to path_driver_cb_func will ignore it. */ 213251881Speter change = svn_hash_gets(changed_paths, new_edit_path); 214251881Speter if (change) 215251881Speter { 216251881Speter svn_hash_sets(changed_paths, new_edit_path, NULL); 217251881Speter 218251881Speter /* If it's a delete, skip this entry. */ 219251881Speter if (change->change_kind == svn_fs_path_change_delete) 220251881Speter continue; 221251881Speter 222251881Speter /* If it's a replacement, check for copyfrom info (if we 223251881Speter don't have it already. */ 224251881Speter if (change->change_kind == svn_fs_path_change_replace) 225251881Speter { 226251881Speter if (! change->copyfrom_known) 227251881Speter { 228251881Speter SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, 229251881Speter &change->copyfrom_path, 230251881Speter target_root, new_edit_path, pool)); 231251881Speter change->copyfrom_known = TRUE; 232251881Speter } 233251881Speter copyfrom_path = change->copyfrom_path; 234251881Speter copyfrom_rev = change->copyfrom_rev; 235251881Speter } 236251881Speter } 237251881Speter 238251881Speter if (authz_read_func) 239251881Speter SVN_ERR(authz_read_func(&readable, target_root, new_edit_path, 240251881Speter authz_read_baton, pool)); 241251881Speter 242251881Speter if (! readable) 243251881Speter continue; 244251881Speter 245251881Speter if (dent->kind == svn_node_dir) 246251881Speter { 247251881Speter svn_fs_root_t *new_source_root; 248251881Speter const char *new_source_fspath; 249251881Speter void *new_dir_baton; 250251881Speter 251251881Speter if (copyfrom_path) 252251881Speter { 253251881Speter svn_fs_t *fs = svn_fs_root_fs(source_root); 254251881Speter SVN_ERR(svn_fs_revision_root(&new_source_root, fs, 255251881Speter copyfrom_rev, pool)); 256251881Speter new_source_fspath = copyfrom_path; 257251881Speter } 258251881Speter else 259251881Speter { 260251881Speter new_source_root = source_root; 261251881Speter new_source_fspath = svn_fspath__join(source_fspath, dent->name, 262251881Speter subpool); 263251881Speter } 264251881Speter 265251881Speter /* ### authz considerations? 266251881Speter * 267251881Speter * I think not; when path_driver_cb_func() calls add_subdir(), it 268251881Speter * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. 269251881Speter */ 270251881Speter if (change && change->change_kind == svn_fs_path_change_replace 271251881Speter && copyfrom_path == NULL) 272251881Speter { 273251881Speter SVN_ERR(editor->add_directory(new_edit_path, *dir_baton, 274251881Speter NULL, SVN_INVALID_REVNUM, 275251881Speter subpool, &new_dir_baton)); 276251881Speter } 277251881Speter else 278251881Speter { 279251881Speter SVN_ERR(add_subdir(new_source_root, target_root, 280251881Speter editor, edit_baton, new_edit_path, 281251881Speter *dir_baton, new_source_fspath, 282251881Speter authz_read_func, authz_read_baton, 283251881Speter changed_paths, subpool, &new_dir_baton)); 284251881Speter } 285251881Speter 286251881Speter SVN_ERR(editor->close_directory(new_dir_baton, subpool)); 287251881Speter } 288251881Speter else if (dent->kind == svn_node_file) 289251881Speter { 290251881Speter svn_txdelta_window_handler_t delta_handler; 291251881Speter void *delta_handler_baton, *file_baton; 292251881Speter svn_txdelta_stream_t *delta_stream; 293251881Speter svn_checksum_t *checksum; 294251881Speter 295251881Speter SVN_ERR(editor->add_file(new_edit_path, *dir_baton, NULL, 296251881Speter SVN_INVALID_REVNUM, pool, &file_baton)); 297251881Speter 298251881Speter SVN_ERR(svn_fs_node_proplist(&props, target_root, 299251881Speter new_edit_path, subpool)); 300251881Speter 301251881Speter for (phi = apr_hash_first(pool, props); phi; phi = apr_hash_next(phi)) 302251881Speter { 303289180Speter const char *key = apr_hash_this_key(phi); 304289180Speter svn_string_t *val = apr_hash_this_val(phi); 305251881Speter 306251881Speter SVN_ERR(editor->change_file_prop(file_baton, key, val, subpool)); 307251881Speter } 308251881Speter 309251881Speter SVN_ERR(editor->apply_textdelta(file_baton, NULL, pool, 310251881Speter &delta_handler, 311251881Speter &delta_handler_baton)); 312251881Speter 313251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, NULL, NULL, 314251881Speter target_root, new_edit_path, 315251881Speter pool)); 316251881Speter 317251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, 318251881Speter delta_handler, 319251881Speter delta_handler_baton, 320251881Speter pool)); 321251881Speter 322251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, target_root, 323251881Speter new_edit_path, TRUE, pool)); 324251881Speter SVN_ERR(editor->close_file(file_baton, 325251881Speter svn_checksum_to_cstring(checksum, pool), 326251881Speter pool)); 327251881Speter } 328251881Speter else 329251881Speter SVN_ERR_MALFUNCTION(); 330251881Speter } 331251881Speter 332251881Speter svn_pool_destroy(subpool); 333251881Speter 334251881Speter return SVN_NO_ERROR; 335251881Speter} 336251881Speter 337251881Speter/* Given PATH deleted under ROOT, return in READABLE whether the path was 338251881Speter readable prior to the deletion. Consult COPIES (a stack of 'struct 339251881Speter copy_info') and AUTHZ_READ_FUNC. */ 340251881Speterstatic svn_error_t * 341251881Speterwas_readable(svn_boolean_t *readable, 342251881Speter svn_fs_root_t *root, 343251881Speter const char *path, 344251881Speter apr_array_header_t *copies, 345251881Speter svn_repos_authz_func_t authz_read_func, 346251881Speter void *authz_read_baton, 347251881Speter apr_pool_t *result_pool, 348251881Speter apr_pool_t *scratch_pool) 349251881Speter{ 350251881Speter svn_fs_root_t *inquire_root; 351251881Speter const char *inquire_path; 352251881Speter struct copy_info *info = NULL; 353251881Speter const char *relpath; 354251881Speter 355251881Speter /* Short circuit. */ 356251881Speter if (! authz_read_func) 357251881Speter { 358251881Speter *readable = TRUE; 359251881Speter return SVN_NO_ERROR; 360251881Speter } 361251881Speter 362251881Speter if (copies->nelts != 0) 363251881Speter info = APR_ARRAY_IDX(copies, copies->nelts - 1, struct copy_info *); 364251881Speter 365251881Speter /* Are we under a copy? */ 366251881Speter if (info && (relpath = svn_relpath_skip_ancestor(info->path, path))) 367251881Speter { 368251881Speter SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 369251881Speter info->copyfrom_rev, scratch_pool)); 370251881Speter inquire_path = svn_fspath__join(info->copyfrom_path, relpath, 371251881Speter scratch_pool); 372251881Speter } 373251881Speter else 374251881Speter { 375251881Speter /* Compute the revision that ROOT is based on. (Note that ROOT is not 376251881Speter r0's root, since this function is only called for deletions.) 377251881Speter ### Need a more succinct way to express this */ 378251881Speter svn_revnum_t inquire_rev = SVN_INVALID_REVNUM; 379251881Speter if (svn_fs_is_txn_root(root)) 380251881Speter inquire_rev = svn_fs_txn_root_base_revision(root); 381251881Speter if (svn_fs_is_revision_root(root)) 382251881Speter inquire_rev = svn_fs_revision_root_revision(root)-1; 383251881Speter SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(inquire_rev)); 384251881Speter 385251881Speter SVN_ERR(svn_fs_revision_root(&inquire_root, svn_fs_root_fs(root), 386251881Speter inquire_rev, scratch_pool)); 387251881Speter inquire_path = path; 388251881Speter } 389251881Speter 390251881Speter SVN_ERR(authz_read_func(readable, inquire_root, inquire_path, 391251881Speter authz_read_baton, result_pool)); 392251881Speter 393251881Speter return SVN_NO_ERROR; 394251881Speter} 395251881Speter 396251881Speter/* Initialize COPYFROM_ROOT, COPYFROM_PATH, and COPYFROM_REV with the 397251881Speter revision root, fspath, and revnum of the copyfrom of CHANGE, which 398251881Speter corresponds to PATH under ROOT. If the copyfrom info is valid 399251881Speter (i.e., is not (NULL, SVN_INVALID_REVNUM)), then initialize SRC_READABLE 400251881Speter too, consulting AUTHZ_READ_FUNC and AUTHZ_READ_BATON if provided. 401251881Speter 402251881Speter NOTE: If the copyfrom information in CHANGE is marked as unknown 403251881Speter (meaning, its ->copyfrom_rev and ->copyfrom_path cannot be 404251881Speter trusted), this function will also update those members of the 405251881Speter CHANGE structure to carry accurate copyfrom information. */ 406251881Speterstatic svn_error_t * 407251881Speterfill_copyfrom(svn_fs_root_t **copyfrom_root, 408251881Speter const char **copyfrom_path, 409251881Speter svn_revnum_t *copyfrom_rev, 410251881Speter svn_boolean_t *src_readable, 411251881Speter svn_fs_root_t *root, 412362181Sdim svn_fs_path_change3_t *change, 413251881Speter svn_repos_authz_func_t authz_read_func, 414251881Speter void *authz_read_baton, 415251881Speter const char *path, 416251881Speter apr_pool_t *result_pool, 417251881Speter apr_pool_t *scratch_pool) 418251881Speter{ 419251881Speter if (! change->copyfrom_known) 420251881Speter { 421251881Speter SVN_ERR(svn_fs_copied_from(&(change->copyfrom_rev), 422251881Speter &(change->copyfrom_path), 423251881Speter root, path, result_pool)); 424251881Speter change->copyfrom_known = TRUE; 425251881Speter } 426251881Speter *copyfrom_rev = change->copyfrom_rev; 427251881Speter *copyfrom_path = change->copyfrom_path; 428251881Speter 429251881Speter if (*copyfrom_path && SVN_IS_VALID_REVNUM(*copyfrom_rev)) 430251881Speter { 431251881Speter SVN_ERR(svn_fs_revision_root(copyfrom_root, 432251881Speter svn_fs_root_fs(root), 433251881Speter *copyfrom_rev, result_pool)); 434251881Speter 435251881Speter if (authz_read_func) 436251881Speter { 437251881Speter SVN_ERR(authz_read_func(src_readable, *copyfrom_root, 438251881Speter *copyfrom_path, 439251881Speter authz_read_baton, result_pool)); 440251881Speter } 441251881Speter else 442251881Speter *src_readable = TRUE; 443251881Speter } 444251881Speter else 445251881Speter { 446251881Speter *copyfrom_root = NULL; 447251881Speter /* SRC_READABLE left uninitialized */ 448251881Speter } 449251881Speter return SVN_NO_ERROR; 450251881Speter} 451251881Speter 452251881Speterstatic svn_error_t * 453251881Speterpath_driver_cb_func(void **dir_baton, 454362181Sdim const svn_delta_editor_t *editor, 455362181Sdim void *edit_baton, 456251881Speter void *parent_baton, 457251881Speter void *callback_baton, 458251881Speter const char *edit_path, 459251881Speter apr_pool_t *pool) 460251881Speter{ 461251881Speter struct path_driver_cb_baton *cb = callback_baton; 462251881Speter svn_fs_root_t *root = cb->root; 463362181Sdim svn_fs_path_change3_t *change; 464251881Speter svn_boolean_t do_add = FALSE, do_delete = FALSE; 465251881Speter void *file_baton = NULL; 466251881Speter svn_revnum_t copyfrom_rev; 467251881Speter const char *copyfrom_path; 468251881Speter svn_fs_root_t *source_root = cb->compare_root; 469251881Speter const char *source_fspath = NULL; 470251881Speter const char *base_path = cb->base_path; 471251881Speter 472251881Speter *dir_baton = NULL; 473251881Speter 474251881Speter /* Initialize SOURCE_FSPATH. */ 475251881Speter if (source_root) 476251881Speter source_fspath = svn_fspath__canonicalize(edit_path, pool); 477251881Speter 478251881Speter /* First, flush the copies stack so it only contains ancestors of path. */ 479251881Speter while (cb->copies->nelts > 0 480251881Speter && ! svn_dirent_is_ancestor(APR_ARRAY_IDX(cb->copies, 481251881Speter cb->copies->nelts - 1, 482251881Speter struct copy_info *)->path, 483251881Speter edit_path)) 484251881Speter apr_array_pop(cb->copies); 485251881Speter 486251881Speter change = svn_hash_gets(cb->changed_paths, edit_path); 487251881Speter if (! change) 488251881Speter { 489251881Speter /* This can only happen if the path was removed from cb->changed_paths 490251881Speter by an earlier call to add_subdir, which means the path was already 491251881Speter handled and we should simply ignore it. */ 492251881Speter return SVN_NO_ERROR; 493251881Speter } 494251881Speter switch (change->change_kind) 495251881Speter { 496251881Speter case svn_fs_path_change_add: 497251881Speter do_add = TRUE; 498251881Speter break; 499251881Speter 500251881Speter case svn_fs_path_change_delete: 501251881Speter do_delete = TRUE; 502251881Speter break; 503251881Speter 504251881Speter case svn_fs_path_change_replace: 505251881Speter do_add = TRUE; 506251881Speter do_delete = TRUE; 507251881Speter break; 508251881Speter 509251881Speter case svn_fs_path_change_modify: 510251881Speter default: 511251881Speter /* do nothing */ 512251881Speter break; 513251881Speter } 514251881Speter 515251881Speter /* Handle any deletions. */ 516251881Speter if (do_delete) 517251881Speter { 518251881Speter svn_boolean_t readable; 519251881Speter 520251881Speter /* Issue #4121: delete under under a copy, of a path that was unreadable 521251881Speter at its pre-copy location. */ 522251881Speter SVN_ERR(was_readable(&readable, root, edit_path, cb->copies, 523251881Speter cb->authz_read_func, cb->authz_read_baton, 524251881Speter pool, pool)); 525251881Speter if (readable) 526251881Speter SVN_ERR(editor->delete_entry(edit_path, SVN_INVALID_REVNUM, 527251881Speter parent_baton, pool)); 528251881Speter } 529251881Speter 530251881Speter /* Fetch the node kind if it makes sense to do so. */ 531251881Speter if (! do_delete || do_add) 532251881Speter { 533251881Speter if (change->node_kind == svn_node_unknown) 534251881Speter SVN_ERR(svn_fs_check_path(&(change->node_kind), root, edit_path, pool)); 535251881Speter if ((change->node_kind != svn_node_dir) && 536251881Speter (change->node_kind != svn_node_file)) 537251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 538251881Speter _("Filesystem path '%s' is neither a file " 539251881Speter "nor a directory"), edit_path); 540251881Speter } 541251881Speter 542251881Speter /* Handle any adds/opens. */ 543251881Speter if (do_add) 544251881Speter { 545251881Speter svn_boolean_t src_readable; 546251881Speter svn_fs_root_t *copyfrom_root; 547251881Speter 548289180Speter /* E.g. when verifying corrupted repositories, their changed path 549289180Speter lists may contain an ADD for "/". The delta path driver will 550289180Speter call us with a NULL parent in that case. */ 551289180Speter if (*edit_path == 0) 552289180Speter return svn_error_create(SVN_ERR_FS_ALREADY_EXISTS, NULL, 553289180Speter _("Root directory already exists.")); 554289180Speter 555251881Speter /* Was this node copied? */ 556251881Speter SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev, 557251881Speter &src_readable, root, change, 558251881Speter cb->authz_read_func, cb->authz_read_baton, 559251881Speter edit_path, pool, pool)); 560251881Speter 561251881Speter /* If we have a copyfrom path, and we can't read it or we're just 562251881Speter ignoring it, or the copyfrom rev is prior to the low water mark 563251881Speter then we just null them out and do a raw add with no history at 564251881Speter all. */ 565251881Speter if (copyfrom_path 566251881Speter && ((! src_readable) 567251881Speter || (svn_relpath_skip_ancestor(base_path, copyfrom_path + 1) == NULL) 568251881Speter || (cb->low_water_mark > copyfrom_rev))) 569251881Speter { 570251881Speter copyfrom_path = NULL; 571251881Speter copyfrom_rev = SVN_INVALID_REVNUM; 572251881Speter } 573251881Speter 574251881Speter /* Do the right thing based on the path KIND. */ 575251881Speter if (change->node_kind == svn_node_dir) 576251881Speter { 577251881Speter /* If this is a copy, but we can't represent it as such, 578251881Speter then we just do a recursive add of the source path 579251881Speter contents. */ 580251881Speter if (change->copyfrom_path && ! copyfrom_path) 581251881Speter { 582251881Speter SVN_ERR(add_subdir(copyfrom_root, root, editor, edit_baton, 583251881Speter edit_path, parent_baton, change->copyfrom_path, 584251881Speter cb->authz_read_func, cb->authz_read_baton, 585251881Speter cb->changed_paths, pool, dir_baton)); 586251881Speter } 587251881Speter else 588251881Speter { 589251881Speter SVN_ERR(editor->add_directory(edit_path, parent_baton, 590251881Speter copyfrom_path, copyfrom_rev, 591251881Speter pool, dir_baton)); 592251881Speter } 593251881Speter } 594251881Speter else 595251881Speter { 596251881Speter SVN_ERR(editor->add_file(edit_path, parent_baton, copyfrom_path, 597251881Speter copyfrom_rev, pool, &file_baton)); 598251881Speter } 599251881Speter 600251881Speter /* If we represent this as a copy... */ 601251881Speter if (copyfrom_path) 602251881Speter { 603251881Speter /* If it is a directory, make sure descendants get the correct 604251881Speter delta source by remembering that we are operating inside a 605251881Speter (possibly nested) copy operation. */ 606251881Speter if (change->node_kind == svn_node_dir) 607251881Speter { 608251881Speter struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 609251881Speter 610251881Speter info->path = apr_pstrdup(cb->pool, edit_path); 611251881Speter info->copyfrom_path = apr_pstrdup(cb->pool, copyfrom_path); 612251881Speter info->copyfrom_rev = copyfrom_rev; 613251881Speter 614251881Speter APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 615251881Speter } 616251881Speter 617251881Speter /* Save the source so that we can use it later, when we 618251881Speter need to generate text and prop deltas. */ 619251881Speter source_root = copyfrom_root; 620251881Speter source_fspath = copyfrom_path; 621251881Speter } 622251881Speter else 623251881Speter /* Else, we are an add without history... */ 624251881Speter { 625251881Speter /* If an ancestor is added with history, we need to forget about 626251881Speter that here, go on with life and repeat all the mistakes of our 627251881Speter past... */ 628251881Speter if (change->node_kind == svn_node_dir && cb->copies->nelts > 0) 629251881Speter { 630251881Speter struct copy_info *info = apr_pcalloc(cb->pool, sizeof(*info)); 631251881Speter 632251881Speter info->path = apr_pstrdup(cb->pool, edit_path); 633251881Speter info->copyfrom_path = NULL; 634251881Speter info->copyfrom_rev = SVN_INVALID_REVNUM; 635251881Speter 636251881Speter APR_ARRAY_PUSH(cb->copies, struct copy_info *) = info; 637251881Speter } 638251881Speter source_root = NULL; 639251881Speter source_fspath = NULL; 640251881Speter } 641251881Speter } 642251881Speter else if (! do_delete) 643251881Speter { 644251881Speter /* Do the right thing based on the path KIND (and the presence 645251881Speter of a PARENT_BATON). */ 646251881Speter if (change->node_kind == svn_node_dir) 647251881Speter { 648251881Speter if (parent_baton) 649251881Speter { 650251881Speter SVN_ERR(editor->open_directory(edit_path, parent_baton, 651251881Speter SVN_INVALID_REVNUM, 652251881Speter pool, dir_baton)); 653251881Speter } 654251881Speter else 655251881Speter { 656251881Speter SVN_ERR(editor->open_root(edit_baton, SVN_INVALID_REVNUM, 657251881Speter pool, dir_baton)); 658251881Speter } 659251881Speter } 660251881Speter else 661251881Speter { 662251881Speter SVN_ERR(editor->open_file(edit_path, parent_baton, SVN_INVALID_REVNUM, 663251881Speter pool, &file_baton)); 664251881Speter } 665251881Speter /* If we are inside an add with history, we need to adjust the 666251881Speter delta source. */ 667251881Speter if (cb->copies->nelts > 0) 668251881Speter { 669251881Speter struct copy_info *info = APR_ARRAY_IDX(cb->copies, 670251881Speter cb->copies->nelts - 1, 671251881Speter struct copy_info *); 672251881Speter if (info->copyfrom_path) 673251881Speter { 674251881Speter const char *relpath = svn_relpath_skip_ancestor(info->path, 675251881Speter edit_path); 676251881Speter SVN_ERR_ASSERT(relpath && *relpath); 677251881Speter SVN_ERR(svn_fs_revision_root(&source_root, 678251881Speter svn_fs_root_fs(root), 679251881Speter info->copyfrom_rev, pool)); 680251881Speter source_fspath = svn_fspath__join(info->copyfrom_path, 681251881Speter relpath, pool); 682251881Speter } 683251881Speter else 684251881Speter { 685251881Speter /* This is an add without history, nested inside an 686251881Speter add with history. We have no delta source in this case. */ 687251881Speter source_root = NULL; 688251881Speter source_fspath = NULL; 689251881Speter } 690251881Speter } 691251881Speter } 692251881Speter 693251881Speter if (! do_delete || do_add) 694251881Speter { 695251881Speter /* Is this a copy that was downgraded to a raw add? (If so, 696251881Speter we'll need to transmit properties and file contents and such 697251881Speter for it regardless of what the CHANGE structure's text_mod 698251881Speter and prop_mod flags say.) */ 699251881Speter svn_boolean_t downgraded_copy = (change->copyfrom_known 700251881Speter && change->copyfrom_path 701251881Speter && (! copyfrom_path)); 702251881Speter 703251881Speter /* Handle property modifications. */ 704251881Speter if (change->prop_mod || downgraded_copy) 705251881Speter { 706251881Speter if (cb->compare_root) 707251881Speter { 708251881Speter apr_array_header_t *prop_diffs; 709251881Speter apr_hash_t *old_props; 710251881Speter apr_hash_t *new_props; 711251881Speter int i; 712251881Speter 713251881Speter if (source_root) 714251881Speter SVN_ERR(svn_fs_node_proplist(&old_props, source_root, 715251881Speter source_fspath, pool)); 716251881Speter else 717251881Speter old_props = apr_hash_make(pool); 718251881Speter 719251881Speter SVN_ERR(svn_fs_node_proplist(&new_props, root, edit_path, pool)); 720251881Speter 721251881Speter SVN_ERR(svn_prop_diffs(&prop_diffs, new_props, old_props, 722251881Speter pool)); 723251881Speter 724251881Speter for (i = 0; i < prop_diffs->nelts; ++i) 725251881Speter { 726251881Speter svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t); 727251881Speter if (change->node_kind == svn_node_dir) 728251881Speter SVN_ERR(editor->change_dir_prop(*dir_baton, pc->name, 729251881Speter pc->value, pool)); 730251881Speter else if (change->node_kind == svn_node_file) 731251881Speter SVN_ERR(editor->change_file_prop(file_baton, pc->name, 732251881Speter pc->value, pool)); 733251881Speter } 734251881Speter } 735251881Speter else 736251881Speter { 737251881Speter /* Just do a dummy prop change to signal that there are *any* 738251881Speter propmods. */ 739251881Speter if (change->node_kind == svn_node_dir) 740251881Speter SVN_ERR(editor->change_dir_prop(*dir_baton, "", NULL, 741251881Speter pool)); 742251881Speter else if (change->node_kind == svn_node_file) 743251881Speter SVN_ERR(editor->change_file_prop(file_baton, "", NULL, 744251881Speter pool)); 745251881Speter } 746251881Speter } 747251881Speter 748251881Speter /* Handle textual modifications. */ 749251881Speter if (change->node_kind == svn_node_file 750251881Speter && (change->text_mod || downgraded_copy)) 751251881Speter { 752251881Speter svn_txdelta_window_handler_t delta_handler; 753251881Speter void *delta_handler_baton; 754251881Speter const char *hex_digest = NULL; 755251881Speter 756251881Speter if (cb->compare_root && source_root && source_fspath) 757251881Speter { 758251881Speter svn_checksum_t *checksum; 759251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 760251881Speter source_root, source_fspath, TRUE, 761251881Speter pool)); 762251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 763251881Speter } 764251881Speter 765251881Speter SVN_ERR(editor->apply_textdelta(file_baton, hex_digest, pool, 766251881Speter &delta_handler, 767251881Speter &delta_handler_baton)); 768251881Speter if (cb->compare_root) 769251881Speter { 770251881Speter svn_txdelta_stream_t *delta_stream; 771251881Speter 772251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, source_root, 773251881Speter source_fspath, root, 774251881Speter edit_path, pool)); 775251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler, 776251881Speter delta_handler_baton, pool)); 777251881Speter } 778251881Speter else 779251881Speter SVN_ERR(delta_handler(NULL, delta_handler_baton)); 780251881Speter } 781251881Speter } 782251881Speter 783251881Speter /* Close the file baton if we opened it. */ 784251881Speter if (file_baton) 785251881Speter { 786251881Speter svn_checksum_t *checksum; 787251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, root, edit_path, 788251881Speter TRUE, pool)); 789251881Speter SVN_ERR(editor->close_file(file_baton, 790251881Speter svn_checksum_to_cstring(checksum, pool), 791251881Speter pool)); 792251881Speter } 793251881Speter 794251881Speter return SVN_NO_ERROR; 795251881Speter} 796251881Speter 797251881Speter#ifdef USE_EV2_IMPL 798251881Speterstatic svn_error_t * 799251881Speterfetch_kind_func(svn_node_kind_t *kind, 800251881Speter void *baton, 801251881Speter const char *path, 802251881Speter svn_revnum_t base_revision, 803251881Speter apr_pool_t *scratch_pool) 804251881Speter{ 805251881Speter svn_fs_root_t *root = baton; 806251881Speter svn_fs_root_t *prev_root; 807251881Speter svn_fs_t *fs = svn_fs_root_fs(root); 808251881Speter 809251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 810251881Speter base_revision = svn_fs_revision_root_revision(root) - 1; 811251881Speter 812251881Speter SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 813251881Speter SVN_ERR(svn_fs_check_path(kind, prev_root, path, scratch_pool)); 814251881Speter 815251881Speter return SVN_NO_ERROR; 816251881Speter} 817251881Speter 818251881Speterstatic svn_error_t * 819251881Speterfetch_props_func(apr_hash_t **props, 820251881Speter void *baton, 821251881Speter const char *path, 822251881Speter svn_revnum_t base_revision, 823251881Speter apr_pool_t *result_pool, 824251881Speter apr_pool_t *scratch_pool) 825251881Speter{ 826251881Speter svn_fs_root_t *root = baton; 827251881Speter svn_fs_root_t *prev_root; 828251881Speter svn_fs_t *fs = svn_fs_root_fs(root); 829251881Speter 830251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 831251881Speter base_revision = svn_fs_revision_root_revision(root) - 1; 832251881Speter 833251881Speter SVN_ERR(svn_fs_revision_root(&prev_root, fs, base_revision, scratch_pool)); 834251881Speter SVN_ERR(svn_fs_node_proplist(props, prev_root, path, result_pool)); 835251881Speter 836251881Speter return SVN_NO_ERROR; 837251881Speter} 838251881Speter#endif 839251881Speter 840251881Speter 841251881Speter 842251881Speter 843362181Sdim/* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC 844362181Sdim and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH. 845362181Sdim 846362181Sdim The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by 847362181Sdim their path. The paths themselves are additionally returned in *PATHS. 848362181Sdim 849362181Sdim Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for 850362181Sdim temporary allocations. 851362181Sdim */ 852362181Sdimstatic svn_error_t * 853362181Sdimget_relevant_changes(apr_hash_t **changed_paths, 854362181Sdim apr_array_header_t **paths, 855362181Sdim svn_fs_root_t *root, 856362181Sdim const char *base_relpath, 857362181Sdim svn_repos_authz_func_t authz_read_func, 858362181Sdim void *authz_read_baton, 859362181Sdim apr_pool_t *result_pool, 860362181Sdim apr_pool_t *scratch_pool) 861362181Sdim{ 862362181Sdim svn_fs_path_change_iterator_t *iterator; 863362181Sdim svn_fs_path_change3_t *change; 864362181Sdim apr_pool_t *iterpool = svn_pool_create(scratch_pool); 865362181Sdim 866362181Sdim /* Fetch the paths changed under ROOT. */ 867362181Sdim SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool)); 868362181Sdim SVN_ERR(svn_fs_path_change_get(&change, iterator)); 869362181Sdim 870362181Sdim /* Make an array from the keys of our CHANGED_PATHS hash, and copy 871362181Sdim the values into a new hash whose keys have no leading slashes. */ 872362181Sdim *paths = apr_array_make(result_pool, 16, sizeof(const char *)); 873362181Sdim *changed_paths = apr_hash_make(result_pool); 874362181Sdim while (change) 875362181Sdim { 876362181Sdim const char *path = change->path.data; 877362181Sdim apr_ssize_t keylen = change->path.len; 878362181Sdim svn_boolean_t allowed = TRUE; 879362181Sdim 880362181Sdim svn_pool_clear(iterpool); 881362181Sdim if (authz_read_func) 882362181Sdim SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton, 883362181Sdim iterpool)); 884362181Sdim 885362181Sdim if (allowed) 886362181Sdim { 887362181Sdim if (path[0] == '/') 888362181Sdim { 889362181Sdim path++; 890362181Sdim keylen--; 891362181Sdim } 892362181Sdim 893362181Sdim /* If the base_path doesn't match the top directory of this path 894362181Sdim we don't want anything to do with it... 895362181Sdim ...unless this was a change to one of the parent directories of 896362181Sdim base_path. */ 897362181Sdim if ( svn_relpath_skip_ancestor(base_relpath, path) 898362181Sdim || svn_relpath_skip_ancestor(path, base_relpath)) 899362181Sdim { 900362181Sdim change = svn_fs_path_change3_dup(change, result_pool); 901362181Sdim path = change->path.data; 902362181Sdim if (path[0] == '/') 903362181Sdim path++; 904362181Sdim 905362181Sdim APR_ARRAY_PUSH(*paths, const char *) = path; 906362181Sdim apr_hash_set(*changed_paths, path, keylen, change); 907362181Sdim } 908362181Sdim } 909362181Sdim 910362181Sdim SVN_ERR(svn_fs_path_change_get(&change, iterator)); 911362181Sdim } 912362181Sdim 913362181Sdim svn_pool_destroy(iterpool); 914362181Sdim return SVN_NO_ERROR; 915362181Sdim} 916362181Sdim 917251881Spetersvn_error_t * 918251881Spetersvn_repos_replay2(svn_fs_root_t *root, 919251881Speter const char *base_path, 920251881Speter svn_revnum_t low_water_mark, 921251881Speter svn_boolean_t send_deltas, 922251881Speter const svn_delta_editor_t *editor, 923251881Speter void *edit_baton, 924251881Speter svn_repos_authz_func_t authz_read_func, 925251881Speter void *authz_read_baton, 926251881Speter apr_pool_t *pool) 927251881Speter{ 928251881Speter#ifndef USE_EV2_IMPL 929251881Speter apr_hash_t *changed_paths; 930251881Speter apr_array_header_t *paths; 931251881Speter struct path_driver_cb_baton cb_baton; 932251881Speter 933251881Speter /* Special-case r0, which we know is an empty revision; if we don't 934251881Speter special-case it we might end up trying to compare it to "r-1". */ 935251881Speter if (svn_fs_is_revision_root(root) && svn_fs_revision_root_revision(root) == 0) 936251881Speter { 937251881Speter SVN_ERR(editor->set_target_revision(edit_baton, 0, pool)); 938251881Speter return SVN_NO_ERROR; 939251881Speter } 940251881Speter 941251881Speter if (! base_path) 942251881Speter base_path = ""; 943251881Speter else if (base_path[0] == '/') 944251881Speter ++base_path; 945251881Speter 946362181Sdim /* Fetch the paths changed under ROOT. */ 947362181Sdim SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path, 948362181Sdim authz_read_func, authz_read_baton, 949362181Sdim pool, pool)); 950251881Speter 951251881Speter /* If we were not given a low water mark, assume that everything is there, 952251881Speter all the way back to revision 0. */ 953251881Speter if (! SVN_IS_VALID_REVNUM(low_water_mark)) 954251881Speter low_water_mark = 0; 955251881Speter 956251881Speter /* Initialize our callback baton. */ 957251881Speter cb_baton.root = root; 958251881Speter cb_baton.changed_paths = changed_paths; 959251881Speter cb_baton.authz_read_func = authz_read_func; 960251881Speter cb_baton.authz_read_baton = authz_read_baton; 961251881Speter cb_baton.base_path = base_path; 962251881Speter cb_baton.low_water_mark = low_water_mark; 963251881Speter cb_baton.compare_root = NULL; 964251881Speter 965251881Speter if (send_deltas) 966251881Speter { 967251881Speter SVN_ERR(svn_fs_revision_root(&cb_baton.compare_root, 968251881Speter svn_fs_root_fs(root), 969251881Speter svn_fs_is_revision_root(root) 970251881Speter ? svn_fs_revision_root_revision(root) - 1 971251881Speter : svn_fs_txn_root_base_revision(root), 972251881Speter pool)); 973251881Speter } 974251881Speter 975251881Speter cb_baton.copies = apr_array_make(pool, 4, sizeof(struct copy_info *)); 976251881Speter cb_baton.pool = pool; 977251881Speter 978251881Speter /* Determine the revision to use throughout the edit, and call 979251881Speter EDITOR's set_target_revision() function. */ 980251881Speter if (svn_fs_is_revision_root(root)) 981251881Speter { 982251881Speter svn_revnum_t revision = svn_fs_revision_root_revision(root); 983251881Speter SVN_ERR(editor->set_target_revision(edit_baton, revision, pool)); 984251881Speter } 985251881Speter 986251881Speter /* Call the path-based editor driver. */ 987362181Sdim return svn_delta_path_driver3(editor, edit_baton, 988251881Speter paths, TRUE, 989251881Speter path_driver_cb_func, &cb_baton, pool); 990251881Speter#else 991251881Speter svn_editor_t *editorv2; 992251881Speter struct svn_delta__extra_baton *exb; 993251881Speter svn_delta__unlock_func_t unlock_func; 994251881Speter svn_boolean_t send_abs_paths; 995251881Speter const char *repos_root = ""; 996251881Speter void *unlock_baton; 997251881Speter 998362181Sdim /* If we were not given a low water mark, assume that everything is there, 999362181Sdim all the way back to revision 0. */ 1000362181Sdim if (! SVN_IS_VALID_REVNUM(low_water_mark)) 1001362181Sdim low_water_mark = 0; 1002362181Sdim 1003251881Speter /* Special-case r0, which we know is an empty revision; if we don't 1004251881Speter special-case it we might end up trying to compare it to "r-1". */ 1005251881Speter if (svn_fs_is_revision_root(root) 1006251881Speter && svn_fs_revision_root_revision(root) == 0) 1007251881Speter { 1008251881Speter SVN_ERR(editor->set_target_revision(edit_baton, 0, pool)); 1009251881Speter return SVN_NO_ERROR; 1010251881Speter } 1011251881Speter 1012251881Speter /* Determine the revision to use throughout the edit, and call 1013251881Speter EDITOR's set_target_revision() function. */ 1014251881Speter if (svn_fs_is_revision_root(root)) 1015251881Speter { 1016251881Speter svn_revnum_t revision = svn_fs_revision_root_revision(root); 1017251881Speter SVN_ERR(editor->set_target_revision(edit_baton, revision, pool)); 1018251881Speter } 1019251881Speter 1020251881Speter if (! base_path) 1021251881Speter base_path = ""; 1022251881Speter else if (base_path[0] == '/') 1023251881Speter ++base_path; 1024251881Speter 1025251881Speter /* Use the shim to convert our editor to an Ev2 editor, and pass it down 1026251881Speter the stack. */ 1027251881Speter SVN_ERR(svn_delta__editor_from_delta(&editorv2, &exb, 1028251881Speter &unlock_func, &unlock_baton, 1029251881Speter editor, edit_baton, 1030251881Speter &send_abs_paths, 1031251881Speter repos_root, "", 1032251881Speter NULL, NULL, 1033251881Speter fetch_kind_func, root, 1034251881Speter fetch_props_func, root, 1035251881Speter pool, pool)); 1036251881Speter 1037251881Speter /* Tell the shim that we're starting the process. */ 1038251881Speter SVN_ERR(exb->start_edit(exb->baton, svn_fs_revision_root_revision(root))); 1039251881Speter 1040251881Speter /* ### We're ignoring SEND_DELTAS here. */ 1041251881Speter SVN_ERR(svn_repos__replay_ev2(root, base_path, low_water_mark, 1042251881Speter editorv2, authz_read_func, authz_read_baton, 1043251881Speter pool)); 1044251881Speter 1045251881Speter return SVN_NO_ERROR; 1046251881Speter#endif 1047251881Speter} 1048251881Speter 1049251881Speter 1050251881Speter/***************************************************************** 1051251881Speter * Ev2 Implementation * 1052251881Speter *****************************************************************/ 1053251881Speter 1054251881Speter/* Recursively traverse EDIT_PATH (as it exists under SOURCE_ROOT) emitting 1055251881Speter the appropriate editor calls to add it and its children without any 1056251881Speter history. This is meant to be used when either a subset of the tree 1057251881Speter has been ignored and we need to copy something from that subset to 1058251881Speter the part of the tree we do care about, or if a subset of the tree is 1059251881Speter unavailable because of authz and we need to use it as the source of 1060251881Speter a copy. */ 1061251881Speterstatic svn_error_t * 1062251881Speteradd_subdir_ev2(svn_fs_root_t *source_root, 1063251881Speter svn_fs_root_t *target_root, 1064251881Speter svn_editor_t *editor, 1065251881Speter const char *repos_relpath, 1066251881Speter const char *source_fspath, 1067251881Speter svn_repos_authz_func_t authz_read_func, 1068251881Speter void *authz_read_baton, 1069251881Speter apr_hash_t *changed_paths, 1070251881Speter apr_pool_t *result_pool, 1071251881Speter apr_pool_t *scratch_pool) 1072251881Speter{ 1073251881Speter apr_pool_t *iterpool = svn_pool_create(scratch_pool); 1074251881Speter apr_hash_index_t *hi; 1075251881Speter apr_hash_t *dirents; 1076251881Speter apr_hash_t *props = NULL; 1077251881Speter apr_array_header_t *children = NULL; 1078251881Speter 1079251881Speter SVN_ERR(svn_fs_node_proplist(&props, target_root, repos_relpath, 1080251881Speter scratch_pool)); 1081251881Speter 1082251881Speter SVN_ERR(svn_editor_add_directory(editor, repos_relpath, children, 1083251881Speter props, SVN_INVALID_REVNUM)); 1084251881Speter 1085251881Speter /* We have to get the dirents from the source path, not the target, 1086251881Speter because we want nested copies from *readable* paths to be handled by 1087251881Speter path_driver_cb_func, not add_subdir (in order to preserve history). */ 1088251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, source_root, source_fspath, 1089251881Speter scratch_pool)); 1090251881Speter 1091251881Speter for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi)) 1092251881Speter { 1093362181Sdim svn_fs_path_change3_t *change; 1094251881Speter svn_boolean_t readable = TRUE; 1095289180Speter svn_fs_dirent_t *dent = apr_hash_this_val(hi); 1096251881Speter const char *copyfrom_path = NULL; 1097251881Speter svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM; 1098251881Speter const char *child_relpath; 1099251881Speter 1100251881Speter svn_pool_clear(iterpool); 1101251881Speter 1102251881Speter child_relpath = svn_relpath_join(repos_relpath, dent->name, iterpool); 1103251881Speter 1104251881Speter /* If a file or subdirectory of the copied directory is listed as a 1105251881Speter changed path (because it was modified after the copy but before the 1106251881Speter commit), we remove it from the changed_paths hash so that future 1107251881Speter calls to path_driver_cb_func will ignore it. */ 1108251881Speter change = svn_hash_gets(changed_paths, child_relpath); 1109251881Speter if (change) 1110251881Speter { 1111251881Speter svn_hash_sets(changed_paths, child_relpath, NULL); 1112251881Speter 1113251881Speter /* If it's a delete, skip this entry. */ 1114251881Speter if (change->change_kind == svn_fs_path_change_delete) 1115251881Speter continue; 1116251881Speter 1117251881Speter /* If it's a replacement, check for copyfrom info (if we 1118251881Speter don't have it already. */ 1119251881Speter if (change->change_kind == svn_fs_path_change_replace) 1120251881Speter { 1121251881Speter if (! change->copyfrom_known) 1122251881Speter { 1123251881Speter SVN_ERR(svn_fs_copied_from(&change->copyfrom_rev, 1124251881Speter &change->copyfrom_path, 1125251881Speter target_root, child_relpath, 1126251881Speter result_pool)); 1127251881Speter change->copyfrom_known = TRUE; 1128251881Speter } 1129251881Speter copyfrom_path = change->copyfrom_path; 1130251881Speter copyfrom_rev = change->copyfrom_rev; 1131251881Speter } 1132251881Speter } 1133251881Speter 1134251881Speter if (authz_read_func) 1135251881Speter SVN_ERR(authz_read_func(&readable, target_root, child_relpath, 1136251881Speter authz_read_baton, iterpool)); 1137251881Speter 1138251881Speter if (! readable) 1139251881Speter continue; 1140251881Speter 1141251881Speter if (dent->kind == svn_node_dir) 1142251881Speter { 1143251881Speter svn_fs_root_t *new_source_root; 1144251881Speter const char *new_source_fspath; 1145251881Speter 1146251881Speter if (copyfrom_path) 1147251881Speter { 1148251881Speter svn_fs_t *fs = svn_fs_root_fs(source_root); 1149251881Speter SVN_ERR(svn_fs_revision_root(&new_source_root, fs, 1150251881Speter copyfrom_rev, result_pool)); 1151251881Speter new_source_fspath = copyfrom_path; 1152251881Speter } 1153251881Speter else 1154251881Speter { 1155251881Speter new_source_root = source_root; 1156251881Speter new_source_fspath = svn_fspath__join(source_fspath, dent->name, 1157251881Speter iterpool); 1158251881Speter } 1159251881Speter 1160251881Speter /* ### authz considerations? 1161251881Speter * 1162251881Speter * I think not; when path_driver_cb_func() calls add_subdir(), it 1163251881Speter * passes SOURCE_ROOT and SOURCE_FSPATH that are unreadable. 1164251881Speter */ 1165251881Speter if (change && change->change_kind == svn_fs_path_change_replace 1166251881Speter && copyfrom_path == NULL) 1167251881Speter { 1168251881Speter SVN_ERR(svn_editor_add_directory(editor, child_relpath, 1169251881Speter children, props, 1170251881Speter SVN_INVALID_REVNUM)); 1171251881Speter } 1172251881Speter else 1173251881Speter { 1174251881Speter SVN_ERR(add_subdir_ev2(new_source_root, target_root, 1175251881Speter editor, child_relpath, 1176251881Speter new_source_fspath, 1177251881Speter authz_read_func, authz_read_baton, 1178251881Speter changed_paths, result_pool, iterpool)); 1179251881Speter } 1180251881Speter } 1181251881Speter else if (dent->kind == svn_node_file) 1182251881Speter { 1183251881Speter svn_checksum_t *checksum; 1184251881Speter svn_stream_t *contents; 1185251881Speter 1186251881Speter SVN_ERR(svn_fs_node_proplist(&props, target_root, 1187251881Speter child_relpath, iterpool)); 1188251881Speter 1189251881Speter SVN_ERR(svn_fs_file_contents(&contents, target_root, 1190251881Speter child_relpath, iterpool)); 1191251881Speter 1192251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1193251881Speter target_root, 1194251881Speter child_relpath, TRUE, iterpool)); 1195251881Speter 1196251881Speter SVN_ERR(svn_editor_add_file(editor, child_relpath, checksum, 1197251881Speter contents, props, SVN_INVALID_REVNUM)); 1198251881Speter } 1199251881Speter else 1200251881Speter SVN_ERR_MALFUNCTION(); 1201251881Speter } 1202251881Speter 1203251881Speter svn_pool_destroy(iterpool); 1204251881Speter 1205251881Speter return SVN_NO_ERROR; 1206251881Speter} 1207251881Speter 1208251881Speterstatic svn_error_t * 1209251881Speterreplay_node(svn_fs_root_t *root, 1210251881Speter const char *repos_relpath, 1211251881Speter svn_editor_t *editor, 1212251881Speter svn_revnum_t low_water_mark, 1213251881Speter const char *base_repos_relpath, 1214251881Speter apr_array_header_t *copies, 1215251881Speter apr_hash_t *changed_paths, 1216251881Speter svn_repos_authz_func_t authz_read_func, 1217251881Speter void *authz_read_baton, 1218251881Speter apr_pool_t *result_pool, 1219251881Speter apr_pool_t *scratch_pool) 1220251881Speter{ 1221362181Sdim svn_fs_path_change3_t *change; 1222251881Speter svn_boolean_t do_add = FALSE; 1223251881Speter svn_boolean_t do_delete = FALSE; 1224251881Speter svn_revnum_t copyfrom_rev; 1225251881Speter const char *copyfrom_path; 1226251881Speter svn_revnum_t replaces_rev; 1227251881Speter 1228251881Speter /* First, flush the copies stack so it only contains ancestors of path. */ 1229251881Speter while (copies->nelts > 0 1230251881Speter && (svn_relpath_skip_ancestor(APR_ARRAY_IDX(copies, 1231251881Speter copies->nelts - 1, 1232251881Speter struct copy_info *)->path, 1233251881Speter repos_relpath) == NULL) ) 1234251881Speter apr_array_pop(copies); 1235251881Speter 1236251881Speter change = svn_hash_gets(changed_paths, repos_relpath); 1237251881Speter if (! change) 1238251881Speter { 1239251881Speter /* This can only happen if the path was removed from changed_paths 1240251881Speter by an earlier call to add_subdir, which means the path was already 1241251881Speter handled and we should simply ignore it. */ 1242251881Speter return SVN_NO_ERROR; 1243251881Speter } 1244251881Speter switch (change->change_kind) 1245251881Speter { 1246251881Speter case svn_fs_path_change_add: 1247251881Speter do_add = TRUE; 1248251881Speter break; 1249251881Speter 1250251881Speter case svn_fs_path_change_delete: 1251251881Speter do_delete = TRUE; 1252251881Speter break; 1253251881Speter 1254251881Speter case svn_fs_path_change_replace: 1255251881Speter do_add = TRUE; 1256251881Speter do_delete = TRUE; 1257251881Speter break; 1258251881Speter 1259251881Speter case svn_fs_path_change_modify: 1260251881Speter default: 1261251881Speter /* do nothing */ 1262251881Speter break; 1263251881Speter } 1264251881Speter 1265251881Speter /* Handle any deletions. */ 1266251881Speter if (do_delete && ! do_add) 1267251881Speter { 1268251881Speter svn_boolean_t readable; 1269251881Speter 1270251881Speter /* Issue #4121: delete under under a copy, of a path that was unreadable 1271251881Speter at its pre-copy location. */ 1272251881Speter SVN_ERR(was_readable(&readable, root, repos_relpath, copies, 1273251881Speter authz_read_func, authz_read_baton, 1274251881Speter scratch_pool, scratch_pool)); 1275251881Speter if (readable) 1276251881Speter SVN_ERR(svn_editor_delete(editor, repos_relpath, SVN_INVALID_REVNUM)); 1277251881Speter 1278251881Speter return SVN_NO_ERROR; 1279251881Speter } 1280251881Speter 1281251881Speter /* Handle replacements. */ 1282251881Speter if (do_delete && do_add) 1283251881Speter replaces_rev = svn_fs_revision_root_revision(root); 1284251881Speter else 1285251881Speter replaces_rev = SVN_INVALID_REVNUM; 1286251881Speter 1287251881Speter /* Fetch the node kind if it makes sense to do so. */ 1288251881Speter if (! do_delete || do_add) 1289251881Speter { 1290251881Speter if (change->node_kind == svn_node_unknown) 1291251881Speter SVN_ERR(svn_fs_check_path(&(change->node_kind), root, repos_relpath, 1292251881Speter scratch_pool)); 1293251881Speter if ((change->node_kind != svn_node_dir) && 1294251881Speter (change->node_kind != svn_node_file)) 1295251881Speter return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 1296251881Speter _("Filesystem path '%s' is neither a file " 1297251881Speter "nor a directory"), repos_relpath); 1298251881Speter } 1299251881Speter 1300251881Speter /* Handle any adds/opens. */ 1301251881Speter if (do_add) 1302251881Speter { 1303251881Speter svn_boolean_t src_readable; 1304251881Speter svn_fs_root_t *copyfrom_root; 1305251881Speter 1306251881Speter /* Was this node copied? */ 1307251881Speter SVN_ERR(fill_copyfrom(©from_root, ©from_path, ©from_rev, 1308251881Speter &src_readable, root, change, 1309251881Speter authz_read_func, authz_read_baton, 1310251881Speter repos_relpath, scratch_pool, scratch_pool)); 1311251881Speter 1312251881Speter /* If we have a copyfrom path, and we can't read it or we're just 1313251881Speter ignoring it, or the copyfrom rev is prior to the low water mark 1314251881Speter then we just null them out and do a raw add with no history at 1315251881Speter all. */ 1316251881Speter if (copyfrom_path 1317251881Speter && ((! src_readable) 1318251881Speter || (svn_relpath_skip_ancestor(base_repos_relpath, 1319251881Speter copyfrom_path + 1) == NULL) 1320251881Speter || (low_water_mark > copyfrom_rev))) 1321251881Speter { 1322251881Speter copyfrom_path = NULL; 1323251881Speter copyfrom_rev = SVN_INVALID_REVNUM; 1324251881Speter } 1325251881Speter 1326251881Speter /* Do the right thing based on the path KIND. */ 1327251881Speter if (change->node_kind == svn_node_dir) 1328251881Speter { 1329251881Speter /* If this is a copy, but we can't represent it as such, 1330251881Speter then we just do a recursive add of the source path 1331251881Speter contents. */ 1332251881Speter if (change->copyfrom_path && ! copyfrom_path) 1333251881Speter { 1334251881Speter SVN_ERR(add_subdir_ev2(copyfrom_root, root, editor, 1335251881Speter repos_relpath, change->copyfrom_path, 1336251881Speter authz_read_func, authz_read_baton, 1337251881Speter changed_paths, result_pool, 1338251881Speter scratch_pool)); 1339251881Speter } 1340251881Speter else 1341251881Speter { 1342251881Speter if (copyfrom_path) 1343251881Speter { 1344251881Speter if (copyfrom_path[0] == '/') 1345251881Speter ++copyfrom_path; 1346251881Speter SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev, 1347251881Speter repos_relpath, replaces_rev)); 1348251881Speter } 1349251881Speter else 1350251881Speter { 1351251881Speter apr_array_header_t *children; 1352251881Speter apr_hash_t *props; 1353251881Speter apr_hash_t *dirents; 1354251881Speter 1355251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, root, repos_relpath, 1356251881Speter scratch_pool)); 1357251881Speter SVN_ERR(svn_hash_keys(&children, dirents, scratch_pool)); 1358251881Speter 1359251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1360251881Speter scratch_pool)); 1361251881Speter 1362251881Speter SVN_ERR(svn_editor_add_directory(editor, repos_relpath, 1363251881Speter children, props, 1364251881Speter replaces_rev)); 1365251881Speter } 1366251881Speter } 1367251881Speter } 1368251881Speter else 1369251881Speter { 1370251881Speter if (copyfrom_path) 1371251881Speter { 1372251881Speter if (copyfrom_path[0] == '/') 1373251881Speter ++copyfrom_path; 1374251881Speter SVN_ERR(svn_editor_copy(editor, copyfrom_path, copyfrom_rev, 1375251881Speter repos_relpath, replaces_rev)); 1376251881Speter } 1377251881Speter else 1378251881Speter { 1379251881Speter apr_hash_t *props; 1380251881Speter svn_checksum_t *checksum; 1381251881Speter svn_stream_t *contents; 1382251881Speter 1383251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1384251881Speter scratch_pool)); 1385251881Speter 1386251881Speter SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath, 1387251881Speter scratch_pool)); 1388251881Speter 1389251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, root, 1390251881Speter repos_relpath, TRUE, scratch_pool)); 1391251881Speter 1392251881Speter SVN_ERR(svn_editor_add_file(editor, repos_relpath, checksum, 1393251881Speter contents, props, replaces_rev)); 1394251881Speter } 1395251881Speter } 1396251881Speter 1397251881Speter /* If we represent this as a copy... */ 1398251881Speter if (copyfrom_path) 1399251881Speter { 1400251881Speter /* If it is a directory, make sure descendants get the correct 1401251881Speter delta source by remembering that we are operating inside a 1402251881Speter (possibly nested) copy operation. */ 1403251881Speter if (change->node_kind == svn_node_dir) 1404251881Speter { 1405251881Speter struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info)); 1406251881Speter 1407251881Speter info->path = apr_pstrdup(result_pool, repos_relpath); 1408251881Speter info->copyfrom_path = apr_pstrdup(result_pool, copyfrom_path); 1409251881Speter info->copyfrom_rev = copyfrom_rev; 1410251881Speter 1411251881Speter APR_ARRAY_PUSH(copies, struct copy_info *) = info; 1412251881Speter } 1413251881Speter } 1414251881Speter else 1415251881Speter /* Else, we are an add without history... */ 1416251881Speter { 1417251881Speter /* If an ancestor is added with history, we need to forget about 1418251881Speter that here, go on with life and repeat all the mistakes of our 1419251881Speter past... */ 1420251881Speter if (change->node_kind == svn_node_dir && copies->nelts > 0) 1421251881Speter { 1422251881Speter struct copy_info *info = apr_pcalloc(result_pool, sizeof(*info)); 1423251881Speter 1424251881Speter info->path = apr_pstrdup(result_pool, repos_relpath); 1425251881Speter info->copyfrom_path = NULL; 1426251881Speter info->copyfrom_rev = SVN_INVALID_REVNUM; 1427251881Speter 1428251881Speter APR_ARRAY_PUSH(copies, struct copy_info *) = info; 1429251881Speter } 1430251881Speter } 1431251881Speter } 1432251881Speter else if (! do_delete) 1433251881Speter { 1434251881Speter /* If we are inside an add with history, we need to adjust the 1435251881Speter delta source. */ 1436251881Speter if (copies->nelts > 0) 1437251881Speter { 1438251881Speter struct copy_info *info = APR_ARRAY_IDX(copies, 1439251881Speter copies->nelts - 1, 1440251881Speter struct copy_info *); 1441251881Speter if (info->copyfrom_path) 1442251881Speter { 1443251881Speter const char *relpath = svn_relpath_skip_ancestor(info->path, 1444251881Speter repos_relpath); 1445251881Speter SVN_ERR_ASSERT(relpath && *relpath); 1446251881Speter repos_relpath = svn_relpath_join(info->copyfrom_path, 1447251881Speter relpath, scratch_pool); 1448251881Speter } 1449251881Speter } 1450251881Speter } 1451251881Speter 1452251881Speter if (! do_delete && !do_add) 1453251881Speter { 1454251881Speter apr_hash_t *props = NULL; 1455251881Speter 1456251881Speter /* Is this a copy that was downgraded to a raw add? (If so, 1457251881Speter we'll need to transmit properties and file contents and such 1458251881Speter for it regardless of what the CHANGE structure's text_mod 1459251881Speter and prop_mod flags say.) */ 1460251881Speter svn_boolean_t downgraded_copy = (change->copyfrom_known 1461251881Speter && change->copyfrom_path 1462251881Speter && (! copyfrom_path)); 1463251881Speter 1464251881Speter /* Handle property modifications. */ 1465251881Speter if (change->prop_mod || downgraded_copy) 1466251881Speter { 1467251881Speter SVN_ERR(svn_fs_node_proplist(&props, root, repos_relpath, 1468251881Speter scratch_pool)); 1469251881Speter } 1470251881Speter 1471251881Speter /* Handle textual modifications. */ 1472251881Speter if (change->node_kind == svn_node_file 1473251881Speter && (change->text_mod || change->prop_mod || downgraded_copy)) 1474251881Speter { 1475251881Speter svn_checksum_t *checksum = NULL; 1476251881Speter svn_stream_t *contents = NULL; 1477251881Speter 1478251881Speter if (change->text_mod) 1479251881Speter { 1480251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1481251881Speter root, repos_relpath, TRUE, 1482251881Speter scratch_pool)); 1483251881Speter 1484251881Speter SVN_ERR(svn_fs_file_contents(&contents, root, repos_relpath, 1485251881Speter scratch_pool)); 1486251881Speter } 1487251881Speter 1488251881Speter SVN_ERR(svn_editor_alter_file(editor, repos_relpath, 1489289180Speter SVN_INVALID_REVNUM, 1490289180Speter checksum, contents, props)); 1491251881Speter } 1492251881Speter 1493251881Speter if (change->node_kind == svn_node_dir 1494251881Speter && (change->prop_mod || downgraded_copy)) 1495251881Speter { 1496251881Speter apr_array_header_t *children = NULL; 1497251881Speter 1498251881Speter SVN_ERR(svn_editor_alter_directory(editor, repos_relpath, 1499251881Speter SVN_INVALID_REVNUM, children, 1500251881Speter props)); 1501251881Speter } 1502251881Speter } 1503251881Speter 1504251881Speter return SVN_NO_ERROR; 1505251881Speter} 1506251881Speter 1507251881Spetersvn_error_t * 1508251881Spetersvn_repos__replay_ev2(svn_fs_root_t *root, 1509251881Speter const char *base_repos_relpath, 1510251881Speter svn_revnum_t low_water_mark, 1511251881Speter svn_editor_t *editor, 1512251881Speter svn_repos_authz_func_t authz_read_func, 1513251881Speter void *authz_read_baton, 1514251881Speter apr_pool_t *scratch_pool) 1515251881Speter{ 1516251881Speter apr_hash_t *changed_paths; 1517251881Speter apr_array_header_t *paths; 1518251881Speter apr_array_header_t *copies; 1519251881Speter apr_pool_t *iterpool; 1520251881Speter svn_error_t *err = SVN_NO_ERROR; 1521251881Speter int i; 1522251881Speter 1523362181Sdim SVN_ERR_ASSERT(svn_relpath_is_canonical(base_repos_relpath)); 1524251881Speter 1525251881Speter /* Special-case r0, which we know is an empty revision; if we don't 1526251881Speter special-case it we might end up trying to compare it to "r-1". */ 1527251881Speter if (svn_fs_is_revision_root(root) 1528251881Speter && svn_fs_revision_root_revision(root) == 0) 1529251881Speter { 1530251881Speter return SVN_NO_ERROR; 1531251881Speter } 1532251881Speter 1533251881Speter /* Fetch the paths changed under ROOT. */ 1534362181Sdim SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, 1535362181Sdim base_repos_relpath, 1536362181Sdim authz_read_func, authz_read_baton, 1537362181Sdim scratch_pool, scratch_pool)); 1538251881Speter 1539251881Speter /* If we were not given a low water mark, assume that everything is there, 1540251881Speter all the way back to revision 0. */ 1541251881Speter if (! SVN_IS_VALID_REVNUM(low_water_mark)) 1542251881Speter low_water_mark = 0; 1543251881Speter 1544251881Speter copies = apr_array_make(scratch_pool, 4, sizeof(struct copy_info *)); 1545251881Speter 1546251881Speter /* Sort the paths. Although not strictly required by the API, this has 1547251881Speter the pleasant side effect of maintaining a consistent ordering of 1548251881Speter dumpfile contents. */ 1549289180Speter svn_sort__array(paths, svn_sort_compare_paths); 1550251881Speter 1551251881Speter /* Now actually handle the various paths. */ 1552251881Speter iterpool = svn_pool_create(scratch_pool); 1553251881Speter for (i = 0; i < paths->nelts; i++) 1554251881Speter { 1555251881Speter const char *repos_relpath = APR_ARRAY_IDX(paths, i, const char *); 1556251881Speter 1557251881Speter svn_pool_clear(iterpool); 1558251881Speter err = replay_node(root, repos_relpath, editor, low_water_mark, 1559251881Speter base_repos_relpath, copies, changed_paths, 1560251881Speter authz_read_func, authz_read_baton, 1561251881Speter scratch_pool, iterpool); 1562251881Speter if (err) 1563251881Speter break; 1564251881Speter } 1565251881Speter 1566251881Speter if (err) 1567251881Speter return svn_error_compose_create(err, svn_editor_abort(editor)); 1568251881Speter else 1569251881Speter SVN_ERR(svn_editor_complete(editor)); 1570251881Speter 1571251881Speter svn_pool_destroy(iterpool); 1572251881Speter return SVN_NO_ERROR; 1573251881Speter} 1574