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