load_editor.c revision 289166
1116742Ssam/* 2116904Ssam * load_editor.c: The svn_delta_editor_t editor used by svnrdump to 3178354Ssam * load revisions. 4116742Ssam * 5116742Ssam * ==================================================================== 6116742Ssam * Licensed to the Apache Software Foundation (ASF) under one 7116742Ssam * or more contributor license agreements. See the NOTICE file 8116742Ssam * distributed with this work for additional information 9116742Ssam * regarding copyright ownership. The ASF licenses this file 10116742Ssam * to you under the Apache License, Version 2.0 (the 11116742Ssam * "License"); you may not use this file except in compliance 12116742Ssam * with the License. You may obtain a copy of the License at 13116742Ssam * 14116742Ssam * http://www.apache.org/licenses/LICENSE-2.0 15116904Ssam * 16116904Ssam * Unless required by applicable law or agreed to in writing, 17116904Ssam * software distributed under the License is distributed on an 18116904Ssam * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 19116904Ssam * KIND, either express or implied. See the License for the 20116904Ssam * specific language governing permissions and limitations 21116904Ssam * under the License. 22116904Ssam * ==================================================================== 23116904Ssam */ 24116904Ssam 25116904Ssam#include "svn_cmdline.h" 26116742Ssam#include "svn_pools.h" 27116742Ssam#include "svn_delta.h" 28116742Ssam#include "svn_repos.h" 29116742Ssam#include "svn_props.h" 30116742Ssam#include "svn_path.h" 31138568Ssam#include "svn_ra.h" 32170530Ssam#include "svn_subst.h" 33116742Ssam#include "svn_io.h" 34138568Ssam#include "svn_private_config.h" 35178354Ssam#include "private/svn_repos_private.h" 36178354Ssam#include "private/svn_ra_private.h" 37178354Ssam#include "private/svn_mergeinfo_private.h" 38178354Ssam#include "private/svn_fspath.h" 39178354Ssam 40178354Ssam#include "svnrdump.h" 41178354Ssam 42178354Ssam#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock" 43178354Ssam 44178354Ssam#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 45138568Ssam 46138568Ssam#if 0 47138568Ssam#define LDR_DBG(x) SVN_DBG(x) 48138568Ssam#else 49138568Ssam#define LDR_DBG(x) while(0) 50138568Ssam#endif 51138568Ssam 52138568Ssam 53138568Ssam 54170530Ssam/** 55138568Ssam * General baton used by the parser functions. 56172211Ssam */ 57172211Ssamstruct parse_baton 58172211Ssam{ 59116742Ssam /* Commit editor and baton used to transfer loaded revisions to 60116742Ssam the target repository. */ 61116742Ssam const svn_delta_editor_t *commit_editor; 62170530Ssam void *commit_edit_baton; 63138568Ssam 64116742Ssam /* RA session(s) for committing to the target repository. */ 65138568Ssam svn_ra_session_t *session; 66138568Ssam svn_ra_session_t *aux_session; 67178354Ssam 68138568Ssam /* To bleep, or not to bleep? (What kind of question is that?) */ 69116742Ssam svn_boolean_t quiet; 70178354Ssam 71178354Ssam /* UUID found in the dumpstream, if any; NULL otherwise. */ 72178354Ssam const char *uuid; 73178354Ssam 74178354Ssam /* Root URL of the target repository. */ 75178354Ssam const char *root_url; 76178354Ssam 77178354Ssam /* The "parent directory" of the target repository in which to load. 78178354Ssam (This is essentially the difference between ROOT_URL and 79178354Ssam SESSION's url, and roughly equivalent to the 'svnadmin load 80178354Ssam --parent-dir' option.) */ 81178354Ssam const char *parent_dir; 82178354Ssam 83178354Ssam /* A mapping of svn_revnum_t * dump stream revisions to their 84178354Ssam corresponding svn_revnum_t * target repository revisions. */ 85178354Ssam /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903 86178354Ssam ### for discussion about improving the memory costs of this mapping. */ 87178354Ssam apr_hash_t *rev_map; 88178354Ssam 89116742Ssam /* The most recent (youngest) revision from the dump stream mapped in 90116742Ssam REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */ 91116742Ssam svn_revnum_t last_rev_mapped; 92116742Ssam 93116742Ssam /* The oldest revision loaded from the dump stream, or 94116742Ssam SVN_INVALID_REVNUM if none have been loaded. */ 95178354Ssam svn_revnum_t oldest_dumpstream_rev; 96178354Ssam}; 97178354Ssam 98178354Ssam/** 99178354Ssam * Use to wrap the dir_context_t in commit.c so we can keep track of 100178354Ssam * depth, relpath and parent for open_directory and close_directory. 101120483Ssam */ 102183252Ssamstruct directory_baton 103183252Ssam{ 104183252Ssam void *baton; 105183252Ssam const char *relpath; 106183252Ssam int depth; 107183252Ssam 108183252Ssam /* The copy-from source of this directory, no matter whether it is 109183252Ssam copied explicitly (the root node of a copy) or implicitly (being an 110183252Ssam existing child of a copied directory). For a node that is newly 111183252Ssam added (without history), even inside a copied parent, these are 112183252Ssam NULL and SVN_INVALID_REVNUM. */ 113183252Ssam const char *copyfrom_path; 114183252Ssam svn_revnum_t copyfrom_rev; 115183255Ssam 116183255Ssam struct directory_baton *parent; 117183256Ssam}; 118183257Ssam 119183257Ssam/** 120183252Ssam * Baton used to represent a node; to be used by the parser 121183252Ssam * functions. Contains a link to the revision baton. 122183252Ssam */ 123170530Ssamstruct node_baton 124170530Ssam{ 125170530Ssam const char *path; 126170530Ssam svn_node_kind_t kind; 127170530Ssam enum svn_node_action action; 128170530Ssam 129170530Ssam /* Is this directory explicitly added? If not, then it already existed 130170530Ssam or is a child of a copy. */ 131183252Ssam svn_boolean_t is_added; 132170530Ssam 133183251Ssam svn_revnum_t copyfrom_rev; 134173273Ssam const char *copyfrom_path; 135170530Ssam const char *copyfrom_url; 136178354Ssam 137172225Ssam void *file_baton; 138172225Ssam const char *base_checksum; 139172225Ssam 140172225Ssam /* (const char *name) -> (svn_prop_t *) */ 141170530Ssam apr_hash_t *prop_changes; 142138568Ssam 143138568Ssam struct revision_baton *rb; 144116742Ssam}; 145116742Ssam 146170530Ssam/** 147178354Ssam * Baton used to represet a revision; used by the parser 148170530Ssam * functions. Contains a link to the parser baton. 149116742Ssam */ 150116742Ssamstruct revision_baton 151170530Ssam{ 152170530Ssam svn_revnum_t rev; 153116742Ssam apr_hash_t *revprop_table; 154116742Ssam apr_int32_t rev_offset; 155138568Ssam 156170530Ssam const svn_string_t *datestamp; 157178354Ssam const svn_string_t *author; 158138568Ssam 159170530Ssam struct parse_baton *pb; 160170530Ssam struct directory_baton *db; 161170530Ssam apr_pool_t *pool; 162170530Ssam}; 163116742Ssam 164170530Ssam 165170530Ssam 166170530Ssam/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that 167178354Ssam anything added to the hash is allocated in the hash's pool. */ 168170530Ssamstatic void 169170530Ssamset_revision_mapping(apr_hash_t *rev_map, 170170530Ssam svn_revnum_t from_rev, 171116742Ssam svn_revnum_t to_rev) 172170530Ssam{ 173170530Ssam svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map), 174170530Ssam sizeof(svn_revnum_t) * 2); 175170530Ssam mapped_revs[0] = from_rev; 176170530Ssam mapped_revs[1] = to_rev; 177170530Ssam apr_hash_set(rev_map, mapped_revs, 178170530Ssam sizeof(svn_revnum_t), mapped_revs + 1); 179170530Ssam} 180170530Ssam 181170530Ssam/* Return the revision to which FROM_REV maps in REV_MAP, or 182170530Ssam SVN_INVALID_REVNUM if no such mapping exists. */ 183170530Ssamstatic svn_revnum_t 184116742Ssamget_revision_mapping(apr_hash_t *rev_map, 185138568Ssam svn_revnum_t from_rev) 186138568Ssam{ 187178354Ssam svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev, 188178354Ssam sizeof(from_rev)); 189138568Ssam return to_rev ? *to_rev : SVN_INVALID_REVNUM; 190178354Ssam} 191178354Ssam 192178354Ssam 193178354Ssam/* Prepend the mergeinfo source paths in MERGEINFO_ORIG with 194116742Ssam PARENT_DIR, and return it in *MERGEINFO_VAL. */ 195138568Ssam/* ### FIXME: Consider somehow sharing code with 196178354Ssam ### libsvn_repos/load-fs-vtable.c:prefix_mergeinfo_paths() */ 197116742Ssamstatic svn_error_t * 198170530Ssamprefix_mergeinfo_paths(svn_string_t **mergeinfo_val, 199173273Ssam const svn_string_t *mergeinfo_orig, 200173273Ssam const char *parent_dir, 201182828Ssam apr_pool_t *pool) 202182828Ssam{ 203183255Ssam apr_hash_t *prefixed_mergeinfo, *mergeinfo; 204183257Ssam apr_hash_index_t *hi; 205183257Ssam void *rangelist; 206170530Ssam 207138568Ssam SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_orig->data, pool)); 208138568Ssam prefixed_mergeinfo = apr_hash_make(pool); 209138568Ssam for (hi = apr_hash_first(pool, mergeinfo); hi; hi = apr_hash_next(hi)) 210138568Ssam { 211138568Ssam const void *key; 212138568Ssam const char *path, *merge_source; 213178354Ssam 214178354Ssam apr_hash_this(hi, &key, NULL, &rangelist); 215178354Ssam merge_source = svn_relpath_canonicalize(key, pool); 216178354Ssam 217178354Ssam /* The svn:mergeinfo property syntax demands a repos abspath */ 218178354Ssam path = svn_fspath__canonicalize(svn_relpath_join(parent_dir, 219178354Ssam merge_source, pool), 220178354Ssam pool); 221178354Ssam svn_hash_sets(prefixed_mergeinfo, path, rangelist); 222178354Ssam } 223178354Ssam return svn_mergeinfo_to_string(mergeinfo_val, prefixed_mergeinfo, pool); 224178354Ssam} 225178354Ssam 226178354Ssam 227178354Ssam/* Examine the mergeinfo in INITIAL_VAL, renumber revisions in rangelists 228178354Ssam as appropriate, and return the (possibly new) mergeinfo in *FINAL_VAL 229178354Ssam (allocated from POOL). */ 230178354Ssam/* ### FIXME: Consider somehow sharing code with 231178354Ssam ### libsvn_repos/load-fs-vtable.c:renumber_mergeinfo_revs() */ 232178354Ssamstatic svn_error_t * 233178354Ssamrenumber_mergeinfo_revs(svn_string_t **final_val, 234178354Ssam const svn_string_t *initial_val, 235178354Ssam struct revision_baton *rb, 236178354Ssam apr_pool_t *pool) 237178354Ssam{ 238178354Ssam apr_pool_t *subpool = svn_pool_create(pool); 239178354Ssam svn_mergeinfo_t mergeinfo, predates_stream_mergeinfo; 240178354Ssam svn_mergeinfo_t final_mergeinfo = apr_hash_make(subpool); 241178354Ssam apr_hash_index_t *hi; 242178354Ssam 243178354Ssam SVN_ERR(svn_mergeinfo_parse(&mergeinfo, initial_val->data, subpool)); 244178354Ssam 245116742Ssam /* Issue #3020 246116742Ssam http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16 247116742Ssam Remove mergeinfo older than the oldest revision in the dump stream 248138568Ssam and adjust its revisions by the difference between the head rev of 249116742Ssam the target repository and the current dump stream rev. */ 250116742Ssam if (rb->pb->oldest_dumpstream_rev > 1) 251116742Ssam { 252116742Ssam SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 253116742Ssam &predates_stream_mergeinfo, mergeinfo, 254116742Ssam rb->pb->oldest_dumpstream_rev - 1, 0, 255138568Ssam TRUE, subpool, subpool)); 256116742Ssam SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 257116742Ssam &mergeinfo, mergeinfo, 258116742Ssam rb->pb->oldest_dumpstream_rev - 1, 0, 259116742Ssam FALSE, subpool, subpool)); 260116742Ssam SVN_ERR(svn_mergeinfo__adjust_mergeinfo_rangelists( 261144618Ssam &predates_stream_mergeinfo, 262144618Ssam predates_stream_mergeinfo, 263144618Ssam -rb->rev_offset, subpool, subpool)); 264178354Ssam } 265178354Ssam else 266178354Ssam { 267127877Ssam predates_stream_mergeinfo = NULL; 268138568Ssam } 269138568Ssam 270138568Ssam for (hi = apr_hash_first(subpool, mergeinfo); hi; hi = apr_hash_next(hi)) 271138568Ssam { 272138568Ssam svn_rangelist_t *rangelist; 273116742Ssam struct parse_baton *pb = rb->pb; 274148302Ssam int i; 275148302Ssam const void *path; 276138568Ssam apr_ssize_t pathlen; 277178354Ssam void *val; 278178354Ssam 279178354Ssam apr_hash_this(hi, &path, &pathlen, &val); 280178354Ssam rangelist = val; 281178354Ssam 282178354Ssam /* Possibly renumber revisions in merge source's rangelist. */ 283148306Ssam for (i = 0; i < rangelist->nelts; i++) 284170530Ssam { 285184274Ssam svn_revnum_t rev_from_map; 286170530Ssam svn_merge_range_t *range = APR_ARRAY_IDX(rangelist, i, 287178354Ssam svn_merge_range_t *); 288178354Ssam rev_from_map = get_revision_mapping(pb->rev_map, range->start); 289138568Ssam if (SVN_IS_VALID_REVNUM(rev_from_map)) 290178354Ssam { 291178354Ssam range->start = rev_from_map; 292178354Ssam } 293178354Ssam else if (range->start == pb->oldest_dumpstream_rev - 1) 294178354Ssam { 295178354Ssam /* Since the start revision of svn_merge_range_t are not 296178354Ssam inclusive there is one possible valid start revision that 297138568Ssam won't be found in the PB->REV_MAP mapping of load stream 298138568Ssam revsions to loaded revisions: The revision immediately 299178354Ssam preceeding the oldest revision from the load stream. 300178354Ssam This is a valid revision for mergeinfo, but not a valid 301178354Ssam copy from revision (which PB->REV_MAP also maps for) so it 302178354Ssam will never be in the mapping. 303138568Ssam 304138568Ssam If that is what we have here, then find the mapping for the 305138568Ssam oldest rev from the load stream and subtract 1 to get the 306138568Ssam renumbered, non-inclusive, start revision. */ 307138568Ssam rev_from_map = get_revision_mapping(pb->rev_map, 308138568Ssam pb->oldest_dumpstream_rev); 309170530Ssam if (SVN_IS_VALID_REVNUM(rev_from_map)) 310170530Ssam range->start = rev_from_map - 1; 311178354Ssam } 312138568Ssam else 313178354Ssam { 314138568Ssam /* If we can't remap the start revision then don't even bother 315138568Ssam trying to remap the end revision. It's possible we might 316138568Ssam actually succeed at the latter, which can result in invalid 317178354Ssam mergeinfo with a start rev > end rev. If that gets into the 318178354Ssam repository then a world of bustage breaks loose anytime that 319178354Ssam bogus mergeinfo is parsed. See 320178354Ssam http://subversion.tigris.org/issues/show_bug.cgi?id=3020#desc16. 321178354Ssam */ 322178354Ssam continue; 323178354Ssam } 324178354Ssam 325178354Ssam rev_from_map = get_revision_mapping(pb->rev_map, range->end); 326178354Ssam if (SVN_IS_VALID_REVNUM(rev_from_map)) 327138568Ssam range->end = rev_from_map; 328144618Ssam } 329138568Ssam apr_hash_set(final_mergeinfo, path, pathlen, rangelist); 330178354Ssam } 331178354Ssam 332178354Ssam if (predates_stream_mergeinfo) 333178354Ssam { 334170530Ssam SVN_ERR(svn_mergeinfo_merge2(final_mergeinfo, predates_stream_mergeinfo, 335178354Ssam subpool, subpool)); 336138568Ssam } 337178354Ssam 338178354Ssam SVN_ERR(svn_mergeinfo__canonicalize_ranges(final_mergeinfo, subpool)); 339178354Ssam 340178354Ssam SVN_ERR(svn_mergeinfo_to_string(final_val, final_mergeinfo, pool)); 341178354Ssam svn_pool_destroy(subpool); 342178354Ssam 343178354Ssam return SVN_NO_ERROR; 344178354Ssam} 345178354Ssam 346178354Ssam 347170530Ssamstatic svn_error_t * 348170530Ssamcommit_callback(const svn_commit_info_t *commit_info, 349138568Ssam void *baton, 350148863Ssam apr_pool_t *pool) 351148863Ssam{ 352170530Ssam struct revision_baton *rb = baton; 353148863Ssam struct parse_baton *pb = rb->pb; 354178354Ssam 355170530Ssam /* ### Don't print directly; generate a notification. */ 356170530Ssam if (! pb->quiet) 357138568Ssam SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n", 358138568Ssam commit_info->revision)); 359178354Ssam 360178354Ssam /* Add the mapping of the dumpstream revision to the committed revision. */ 361138568Ssam set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision); 362138568Ssam 363178354Ssam /* If the incoming dump stream has non-contiguous revisions (e.g. from 364178354Ssam using svndumpfilter --drop-empty-revs without --renumber-revs) then 365178354Ssam we must account for the missing gaps in PB->REV_MAP. Otherwise we 366178354Ssam might not be able to map all mergeinfo source revisions to the correct 367178354Ssam revisions in the target repos. */ 368178354Ssam if ((pb->last_rev_mapped != SVN_INVALID_REVNUM) 369178354Ssam && (rb->rev != pb->last_rev_mapped + 1)) 370178354Ssam { 371178354Ssam svn_revnum_t i; 372178354Ssam 373138568Ssam for (i = pb->last_rev_mapped + 1; i < rb->rev; i++) 374144618Ssam { 375178354Ssam set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped); 376178354Ssam } 377170530Ssam } 378178354Ssam 379178354Ssam /* Update our "last revision mapped". */ 380178354Ssam pb->last_rev_mapped = rb->rev; 381178354Ssam 382178354Ssam return SVN_NO_ERROR; 383178354Ssam} 384178354Ssam 385170530Ssam/* Implements `svn_ra__lock_retry_func_t'. */ 386170530Ssamstatic svn_error_t * 387148863Ssamlock_retry_func(void *baton, 388170530Ssam const svn_string_t *reposlocktoken, 389178354Ssam apr_pool_t *pool) 390178354Ssam{ 391138568Ssam return svn_cmdline_printf(pool, 392148863Ssam _("Failed to get lock on destination " 393170530Ssam "repos, currently held by '%s'\n"), 394138568Ssam reposlocktoken->data); 395116742Ssam} 396144618Ssam 397116742Ssam 398116742Ssamstatic svn_error_t * 399178354Ssamfetch_base_func(const char **filename, 400144618Ssam void *baton, 401138568Ssam const char *path, 402144618Ssam svn_revnum_t base_revision, 403138568Ssam apr_pool_t *result_pool, 404178354Ssam apr_pool_t *scratch_pool) 405178354Ssam{ 406170530Ssam struct revision_baton *rb = baton; 407153073Ssam svn_stream_t *fstream; 408153073Ssam svn_error_t *err; 409153073Ssam 410178354Ssam if (! SVN_IS_VALID_REVNUM(base_revision)) 411148936Ssam base_revision = rb->rev - 1; 412148936Ssam 413178354Ssam SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL, 414178354Ssam svn_io_file_del_on_pool_cleanup, 415178354Ssam result_pool, scratch_pool)); 416178354Ssam 417116742Ssam err = svn_ra_get_file(rb->pb->aux_session, path, base_revision, 418 fstream, NULL, NULL, scratch_pool); 419 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 420 { 421 svn_error_clear(err); 422 SVN_ERR(svn_stream_close(fstream)); 423 424 *filename = NULL; 425 return SVN_NO_ERROR; 426 } 427 else if (err) 428 return svn_error_trace(err); 429 430 SVN_ERR(svn_stream_close(fstream)); 431 432 return SVN_NO_ERROR; 433} 434 435static svn_error_t * 436fetch_props_func(apr_hash_t **props, 437 void *baton, 438 const char *path, 439 svn_revnum_t base_revision, 440 apr_pool_t *result_pool, 441 apr_pool_t *scratch_pool) 442{ 443 struct revision_baton *rb = baton; 444 svn_node_kind_t node_kind; 445 446 if (! SVN_IS_VALID_REVNUM(base_revision)) 447 base_revision = rb->rev - 1; 448 449 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 450 &node_kind, scratch_pool)); 451 452 if (node_kind == svn_node_file) 453 { 454 SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision, 455 NULL, NULL, props, result_pool)); 456 } 457 else if (node_kind == svn_node_dir) 458 { 459 apr_array_header_t *tmp_props; 460 461 SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path, 462 base_revision, 0 /* Dirent fields */, 463 result_pool)); 464 tmp_props = svn_prop_hash_to_array(*props, result_pool); 465 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props, 466 result_pool)); 467 *props = svn_prop_array_to_hash(tmp_props, result_pool); 468 } 469 else 470 { 471 *props = apr_hash_make(result_pool); 472 } 473 474 return SVN_NO_ERROR; 475} 476 477static svn_error_t * 478fetch_kind_func(svn_node_kind_t *kind, 479 void *baton, 480 const char *path, 481 svn_revnum_t base_revision, 482 apr_pool_t *scratch_pool) 483{ 484 struct revision_baton *rb = baton; 485 486 if (! SVN_IS_VALID_REVNUM(base_revision)) 487 base_revision = rb->rev - 1; 488 489 SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision, 490 kind, scratch_pool)); 491 492 return SVN_NO_ERROR; 493} 494 495static svn_delta_shim_callbacks_t * 496get_shim_callbacks(struct revision_baton *rb, 497 apr_pool_t *pool) 498{ 499 svn_delta_shim_callbacks_t *callbacks = 500 svn_delta_shim_callbacks_default(pool); 501 502 callbacks->fetch_props_func = fetch_props_func; 503 callbacks->fetch_kind_func = fetch_kind_func; 504 callbacks->fetch_base_func = fetch_base_func; 505 callbacks->fetch_baton = rb; 506 507 return callbacks; 508} 509 510/* Acquire a lock (of sorts) on the repository associated with the 511 * given RA SESSION. This lock is just a revprop change attempt in a 512 * time-delay loop. This function is duplicated by svnsync in 513 * svnsync/svnsync.c 514 * 515 * ### TODO: Make this function more generic and 516 * expose it through a header for use by other Subversion 517 * applications to avoid duplication. 518 */ 519static svn_error_t * 520get_lock(const svn_string_t **lock_string_p, 521 svn_ra_session_t *session, 522 svn_cancel_func_t cancel_func, 523 void *cancel_baton, 524 apr_pool_t *pool) 525{ 526 svn_boolean_t be_atomic; 527 528 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 529 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 530 pool)); 531 if (! be_atomic) 532 { 533 /* Pre-1.7 servers can't lock without a race condition. (Issue #3546) */ 534 svn_error_t *err = 535 svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 536 _("Target server does not support atomic revision " 537 "property edits; consider upgrading it to 1.7.")); 538 svn_handle_warning2(stderr, err, "svnrdump: "); 539 svn_error_clear(err); 540 } 541 542 return svn_ra__get_operational_lock(lock_string_p, NULL, session, 543 SVNRDUMP_PROP_LOCK, FALSE, 544 10 /* retries */, lock_retry_func, NULL, 545 cancel_func, cancel_baton, pool); 546} 547 548static svn_error_t * 549new_revision_record(void **revision_baton, 550 apr_hash_t *headers, 551 void *parse_baton, 552 apr_pool_t *pool) 553{ 554 struct revision_baton *rb; 555 struct parse_baton *pb; 556 apr_hash_index_t *hi; 557 svn_revnum_t head_rev; 558 559 rb = apr_pcalloc(pool, sizeof(*rb)); 560 pb = parse_baton; 561 rb->pool = svn_pool_create(pool); 562 rb->pb = pb; 563 rb->db = NULL; 564 565 for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi)) 566 { 567 const char *hname = svn__apr_hash_index_key(hi); 568 const char *hval = svn__apr_hash_index_val(hi); 569 570 if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0) 571 rb->rev = atoi(hval); 572 } 573 574 SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool)); 575 576 /* FIXME: This is a lame fallback loading multiple segments of dump in 577 several separate operations. It is highly susceptible to race conditions. 578 Calculate the revision 'offset' for finding copyfrom sources. 579 It might be positive or negative. */ 580 rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1)); 581 582 /* Stash the oldest (non-zero) dumpstream revision seen. */ 583 if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev))) 584 pb->oldest_dumpstream_rev = rb->rev; 585 586 /* Set the commit_editor/ commit_edit_baton to NULL and wait for 587 them to be created in new_node_record */ 588 rb->pb->commit_editor = NULL; 589 rb->pb->commit_edit_baton = NULL; 590 rb->revprop_table = apr_hash_make(rb->pool); 591 592 *revision_baton = rb; 593 return SVN_NO_ERROR; 594} 595 596static svn_error_t * 597magic_header_record(int version, 598 void *parse_baton, 599 apr_pool_t *pool) 600{ 601 return SVN_NO_ERROR; 602} 603 604static svn_error_t * 605uuid_record(const char *uuid, 606 void *parse_baton, 607 apr_pool_t *pool) 608{ 609 struct parse_baton *pb; 610 pb = parse_baton; 611 pb->uuid = apr_pstrdup(pool, uuid); 612 return SVN_NO_ERROR; 613} 614 615/* Push information about another directory onto the linked list RB->db. 616 * 617 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the 618 * repository-relative path of this directory. IS_ADDED is true iff this 619 * directory is being added (with or without history). If added with 620 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else 621 * are NULL/SVN_INVALID_REVNUM. 622 */ 623static void 624push_directory(struct revision_baton *rb, 625 void *child_baton, 626 const char *relpath, 627 svn_boolean_t is_added, 628 const char *copyfrom_path, 629 svn_revnum_t copyfrom_rev) 630{ 631 struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db)); 632 633 SVN_ERR_ASSERT_NO_RETURN( 634 is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM)); 635 636 /* If this node is an existing (not newly added) child of a copied node, 637 calculate where it was copied from. */ 638 if (!is_added 639 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 640 { 641 const char *name = svn_relpath_basename(relpath, NULL); 642 643 copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name, 644 rb->pool); 645 copyfrom_rev = rb->db->copyfrom_rev; 646 } 647 648 child_db->baton = child_baton; 649 child_db->relpath = relpath; 650 child_db->copyfrom_path = copyfrom_path; 651 child_db->copyfrom_rev = copyfrom_rev; 652 child_db->parent = rb->db; 653 rb->db = child_db; 654} 655 656static svn_error_t * 657new_node_record(void **node_baton, 658 apr_hash_t *headers, 659 void *revision_baton, 660 apr_pool_t *pool) 661{ 662 struct revision_baton *rb = revision_baton; 663 const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 664 void *commit_edit_baton = rb->pb->commit_edit_baton; 665 struct node_baton *nb; 666 apr_hash_index_t *hi; 667 void *child_baton; 668 const char *nb_dirname; 669 670 nb = apr_pcalloc(rb->pool, sizeof(*nb)); 671 nb->rb = rb; 672 nb->is_added = FALSE; 673 nb->copyfrom_path = NULL; 674 nb->copyfrom_url = NULL; 675 nb->copyfrom_rev = SVN_INVALID_REVNUM; 676 nb->prop_changes = apr_hash_make(rb->pool); 677 678 /* If the creation of commit_editor is pending, create it now and 679 open_root on it; also create a top-level directory baton. */ 680 681 if (!commit_editor) 682 { 683 /* The revprop_table should have been filled in with important 684 information like svn:log in set_revision_property. We can now 685 use it all this information to create our commit_editor. But 686 first, clear revprops that we aren't allowed to set with the 687 commit_editor. We'll set them separately using the RA API 688 after closing the editor (see close_revision). */ 689 690 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL); 691 svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL); 692 693 SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session, 694 get_shim_callbacks(rb, rb->pool))); 695 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 696 &commit_edit_baton, rb->revprop_table, 697 commit_callback, revision_baton, 698 NULL, FALSE, rb->pool)); 699 700 rb->pb->commit_editor = commit_editor; 701 rb->pb->commit_edit_baton = commit_edit_baton; 702 703 SVN_ERR(commit_editor->open_root(commit_edit_baton, 704 rb->rev - rb->rev_offset - 1, 705 rb->pool, &child_baton)); 706 707 LDR_DBG(("Opened root %p\n", child_baton)); 708 709 /* child_baton corresponds to the root directory baton here */ 710 push_directory(rb, child_baton, "", TRUE /*is_added*/, 711 NULL, SVN_INVALID_REVNUM); 712 } 713 714 for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi)) 715 { 716 const char *hname = svn__apr_hash_index_key(hi); 717 const char *hval = svn__apr_hash_index_val(hi); 718 719 /* Parse the different kinds of headers we can encounter and 720 stuff them into the node_baton for writing later */ 721 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0) 722 nb->path = apr_pstrdup(rb->pool, hval); 723 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0) 724 nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir; 725 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0) 726 { 727 if (strcmp(hval, "add") == 0) 728 nb->action = svn_node_action_add; 729 if (strcmp(hval, "change") == 0) 730 nb->action = svn_node_action_change; 731 if (strcmp(hval, "delete") == 0) 732 nb->action = svn_node_action_delete; 733 if (strcmp(hval, "replace") == 0) 734 nb->action = svn_node_action_replace; 735 } 736 if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0) 737 nb->base_checksum = apr_pstrdup(rb->pool, hval); 738 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0) 739 nb->copyfrom_rev = atoi(hval); 740 if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0) 741 nb->copyfrom_path = apr_pstrdup(rb->pool, hval); 742 } 743 744 nb_dirname = svn_relpath_dirname(nb->path, pool); 745 if (svn_path_compare_paths(nb_dirname, 746 rb->db->relpath) != 0) 747 { 748 char *ancestor_path; 749 apr_size_t residual_close_count; 750 apr_array_header_t *residual_open_path; 751 int i; 752 apr_size_t n; 753 754 /* Before attempting to handle the action, call open_directory 755 for all the path components and set the directory baton 756 accordingly */ 757 ancestor_path = 758 svn_relpath_get_longest_ancestor(nb_dirname, 759 rb->db->relpath, pool); 760 residual_close_count = 761 svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path, 762 rb->db->relpath)); 763 residual_open_path = 764 svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path, 765 nb_dirname), pool); 766 767 /* First close all as many directories as there are after 768 skip_ancestor, and then open fresh directories */ 769 for (n = 0; n < residual_close_count; n ++) 770 { 771 /* Don't worry about destroying the actual rb->db object, 772 since the pool we're using has the lifetime of one 773 revision anyway */ 774 LDR_DBG(("Closing dir %p\n", rb->db->baton)); 775 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 776 rb->db = rb->db->parent; 777 } 778 779 for (i = 0; i < residual_open_path->nelts; i ++) 780 { 781 char *relpath_compose = 782 svn_relpath_join(rb->db->relpath, 783 APR_ARRAY_IDX(residual_open_path, i, const char *), 784 rb->pool); 785 SVN_ERR(commit_editor->open_directory(relpath_compose, 786 rb->db->baton, 787 rb->rev - rb->rev_offset - 1, 788 rb->pool, &child_baton)); 789 LDR_DBG(("Opened dir %p\n", child_baton)); 790 push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/, 791 NULL, SVN_INVALID_REVNUM); 792 } 793 } 794 795 /* Fix up the copyfrom information in light of mapped revisions and 796 non-root load targets, and convert copyfrom path into a full 797 URL. */ 798 if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev)) 799 { 800 svn_revnum_t copyfrom_rev; 801 802 /* Try to find the copyfrom revision in the revision map; 803 failing that, fall back to the revision offset approach. */ 804 copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev); 805 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 806 copyfrom_rev = nb->copyfrom_rev - rb->rev_offset; 807 808 if (! SVN_IS_VALID_REVNUM(copyfrom_rev)) 809 return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL, 810 _("Relative source revision %ld is not" 811 " available in current repository"), 812 copyfrom_rev); 813 814 nb->copyfrom_rev = copyfrom_rev; 815 816 if (rb->pb->parent_dir) 817 nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir, 818 nb->copyfrom_path, rb->pool); 819 nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url, 820 nb->copyfrom_path, 821 rb->pool); 822 } 823 824 825 switch (nb->action) 826 { 827 case svn_node_action_delete: 828 case svn_node_action_replace: 829 LDR_DBG(("Deleting entry %s in %p\n", nb->path, rb->db->baton)); 830 SVN_ERR(commit_editor->delete_entry(nb->path, 831 rb->rev - rb->rev_offset - 1, 832 rb->db->baton, rb->pool)); 833 if (nb->action == svn_node_action_delete) 834 break; 835 else 836 /* FALL THROUGH */; 837 case svn_node_action_add: 838 nb->is_added = TRUE; 839 switch (nb->kind) 840 { 841 case svn_node_file: 842 SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton, 843 nb->copyfrom_url, 844 nb->copyfrom_rev, 845 rb->pool, &(nb->file_baton))); 846 LDR_DBG(("Added file %s to dir %p as %p\n", 847 nb->path, rb->db->baton, nb->file_baton)); 848 break; 849 case svn_node_dir: 850 SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton, 851 nb->copyfrom_url, 852 nb->copyfrom_rev, 853 rb->pool, &child_baton)); 854 LDR_DBG(("Added dir %s to dir %p as %p\n", 855 nb->path, rb->db->baton, child_baton)); 856 push_directory(rb, child_baton, nb->path, TRUE /*is_added*/, 857 nb->copyfrom_path, nb->copyfrom_rev); 858 break; 859 default: 860 break; 861 } 862 break; 863 case svn_node_action_change: 864 switch (nb->kind) 865 { 866 case svn_node_file: 867 SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton, 868 SVN_INVALID_REVNUM, rb->pool, 869 &(nb->file_baton))); 870 break; 871 default: 872 SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton, 873 rb->rev - rb->rev_offset - 1, 874 rb->pool, &child_baton)); 875 push_directory(rb, child_baton, nb->path, FALSE /*is_added*/, 876 NULL, SVN_INVALID_REVNUM); 877 break; 878 } 879 break; 880 } 881 882 *node_baton = nb; 883 return SVN_NO_ERROR; 884} 885 886static svn_error_t * 887set_revision_property(void *baton, 888 const char *name, 889 const svn_string_t *value) 890{ 891 struct revision_baton *rb = baton; 892 893 SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool)); 894 895 SVN_ERR(svn_repos__validate_prop(name, value, rb->pool)); 896 897 if (rb->rev > 0) 898 { 899 svn_hash_sets(rb->revprop_table, 900 apr_pstrdup(rb->pool, name), 901 svn_string_dup(value, rb->pool)); 902 } 903 else if (rb->rev_offset == -1) 904 { 905 /* Special case: set revision 0 properties directly (which is 906 safe because the commit_editor hasn't been created yet), but 907 only when loading into an 'empty' filesystem. */ 908 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0, 909 name, NULL, value, rb->pool)); 910 } 911 912 /* Remember any datestamp/ author that passes through (see comment 913 in close_revision). */ 914 if (!strcmp(name, SVN_PROP_REVISION_DATE)) 915 rb->datestamp = svn_string_dup(value, rb->pool); 916 if (!strcmp(name, SVN_PROP_REVISION_AUTHOR)) 917 rb->author = svn_string_dup(value, rb->pool); 918 919 return SVN_NO_ERROR; 920} 921 922static svn_error_t * 923set_node_property(void *baton, 924 const char *name, 925 const svn_string_t *value) 926{ 927 struct node_baton *nb = baton; 928 apr_pool_t *pool = nb->rb->pool; 929 svn_prop_t *prop; 930 931 if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0) 932 { 933 svn_string_t *renumbered_mergeinfo; 934 svn_string_t prop_val; 935 936 /* Tolerate mergeinfo with "\r\n" line endings because some 937 dumpstream sources might contain as much. If so normalize 938 the line endings to '\n' and make a notification to 939 PARSE_BATON->FEEDBACK_STREAM that we have made this 940 correction. */ 941 if (strstr(value->data, "\r")) 942 { 943 const char *prop_eol_normalized; 944 945 SVN_ERR(svn_subst_translate_cstring2(value->data, 946 &prop_eol_normalized, 947 "\n", /* translate to LF */ 948 FALSE, /* no repair */ 949 NULL, /* no keywords */ 950 FALSE, /* no expansion */ 951 pool)); 952 prop_val.data = prop_eol_normalized; 953 prop_val.len = strlen(prop_eol_normalized); 954 value = &prop_val; 955 956 /* ### TODO: notify? */ 957 } 958 959 /* Renumber mergeinfo as appropriate. */ 960 SVN_ERR(renumber_mergeinfo_revs(&renumbered_mergeinfo, value, 961 nb->rb, pool)); 962 value = renumbered_mergeinfo; 963 964 if (nb->rb->pb->parent_dir) 965 { 966 /* Prefix the merge source paths with PB->parent_dir. */ 967 /* ASSUMPTION: All source paths are included in the dump stream. */ 968 svn_string_t *mergeinfo_val; 969 SVN_ERR(prefix_mergeinfo_paths(&mergeinfo_val, value, 970 nb->rb->pb->parent_dir, pool)); 971 value = mergeinfo_val; 972 } 973 } 974 975 SVN_ERR(svn_rdump__normalize_prop(name, &value, pool)); 976 977 SVN_ERR(svn_repos__validate_prop(name, value, pool)); 978 979 prop = apr_palloc(nb->rb->pool, sizeof (*prop)); 980 prop->name = apr_pstrdup(pool, name); 981 prop->value = value ? svn_string_dup(value, pool) : NULL; 982 svn_hash_sets(nb->prop_changes, prop->name, prop); 983 984 return SVN_NO_ERROR; 985} 986 987static svn_error_t * 988delete_node_property(void *baton, 989 const char *name) 990{ 991 struct node_baton *nb = baton; 992 apr_pool_t *pool = nb->rb->pool; 993 svn_prop_t *prop; 994 995 SVN_ERR(svn_repos__validate_prop(name, NULL, pool)); 996 997 prop = apr_palloc(pool, sizeof (*prop)); 998 prop->name = apr_pstrdup(pool, name); 999 prop->value = NULL; 1000 svn_hash_sets(nb->prop_changes, prop->name, prop); 1001 1002 return SVN_NO_ERROR; 1003} 1004 1005/* Delete all the properties of the node, if any. 1006 * 1007 * The commit editor doesn't have a method to delete a node's properties 1008 * without knowing what they are, so we have to first find out what 1009 * properties the node would have had. If it's copied (explicitly or 1010 * implicitly), we look at the copy source. If it's only being changed, 1011 * we look at the node's current path in the head revision. 1012 */ 1013static svn_error_t * 1014remove_node_props(void *baton) 1015{ 1016 struct node_baton *nb = baton; 1017 struct revision_baton *rb = nb->rb; 1018 apr_pool_t *pool = nb->rb->pool; 1019 apr_hash_index_t *hi; 1020 apr_hash_t *props; 1021 const char *orig_path; 1022 svn_revnum_t orig_rev; 1023 1024 /* Find the path and revision that has the node's original properties */ 1025 if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev)) 1026 { 1027 LDR_DBG(("using nb->copyfrom %s@%ld", nb->copyfrom_path, nb->copyfrom_rev)); 1028 orig_path = nb->copyfrom_path; 1029 orig_rev = nb->copyfrom_rev; 1030 } 1031 else if (!nb->is_added 1032 && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev)) 1033 { 1034 /* If this is a dir, then it's described by rb->db; 1035 if this is a file, then it's a child of the dir in rb->db. */ 1036 LDR_DBG(("using rb->db->copyfrom (k=%d) %s@%ld", 1037 nb->kind, rb->db->copyfrom_path, rb->db->copyfrom_rev)); 1038 orig_path = (nb->kind == svn_node_dir) 1039 ? rb->db->copyfrom_path 1040 : svn_relpath_join(rb->db->copyfrom_path, 1041 svn_relpath_basename(nb->path, NULL), 1042 rb->pool); 1043 orig_rev = rb->db->copyfrom_rev; 1044 } 1045 else 1046 { 1047 LDR_DBG(("using self.path@head %s@%ld", nb->path, SVN_INVALID_REVNUM)); 1048 /* ### Should we query at a known, fixed, "head" revision number 1049 instead of passing SVN_INVALID_REVNUM and getting a moving target? */ 1050 orig_path = nb->path; 1051 orig_rev = SVN_INVALID_REVNUM; 1052 } 1053 LDR_DBG(("Trying %s@%ld", orig_path, orig_rev)); 1054 1055 if ((nb->action == svn_node_action_add 1056 || nb->action == svn_node_action_replace) 1057 && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev)) 1058 /* Add-without-history; no "old" properties to worry about. */ 1059 return SVN_NO_ERROR; 1060 1061 if (nb->kind == svn_node_file) 1062 { 1063 SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session, 1064 orig_path, orig_rev, NULL, NULL, &props, pool)); 1065 } 1066 else /* nb->kind == svn_node_dir */ 1067 { 1068 SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props, 1069 orig_path, orig_rev, 0, pool)); 1070 } 1071 1072 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) 1073 { 1074 const char *name = svn__apr_hash_index_key(hi); 1075 svn_prop_kind_t kind = svn_property_kind2(name); 1076 1077 if (kind == svn_prop_regular_kind) 1078 SVN_ERR(set_node_property(nb, name, NULL)); 1079 } 1080 1081 return SVN_NO_ERROR; 1082} 1083 1084static svn_error_t * 1085set_fulltext(svn_stream_t **stream, 1086 void *node_baton) 1087{ 1088 struct node_baton *nb = node_baton; 1089 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1090 svn_txdelta_window_handler_t handler; 1091 void *handler_baton; 1092 apr_pool_t *pool = nb->rb->pool; 1093 1094 LDR_DBG(("Setting fulltext for %p\n", nb->file_baton)); 1095 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 1096 pool, &handler, &handler_baton)); 1097 *stream = svn_txdelta_target_push(handler, handler_baton, 1098 svn_stream_empty(pool), pool); 1099 return SVN_NO_ERROR; 1100} 1101 1102static svn_error_t * 1103apply_textdelta(svn_txdelta_window_handler_t *handler, 1104 void **handler_baton, 1105 void *node_baton) 1106{ 1107 struct node_baton *nb = node_baton; 1108 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1109 apr_pool_t *pool = nb->rb->pool; 1110 1111 LDR_DBG(("Applying textdelta to %p\n", nb->file_baton)); 1112 SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum, 1113 pool, handler, handler_baton)); 1114 1115 return SVN_NO_ERROR; 1116} 1117 1118static svn_error_t * 1119close_node(void *baton) 1120{ 1121 struct node_baton *nb = baton; 1122 const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor; 1123 apr_pool_t *pool = nb->rb->pool; 1124 apr_hash_index_t *hi; 1125 1126 for (hi = apr_hash_first(pool, nb->prop_changes); 1127 hi; hi = apr_hash_next(hi)) 1128 { 1129 const char *name = svn__apr_hash_index_key(hi); 1130 svn_prop_t *prop = svn__apr_hash_index_val(hi); 1131 1132 switch (nb->kind) 1133 { 1134 case svn_node_file: 1135 SVN_ERR(commit_editor->change_file_prop(nb->file_baton, 1136 name, prop->value, pool)); 1137 break; 1138 case svn_node_dir: 1139 SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton, 1140 name, prop->value, pool)); 1141 break; 1142 default: 1143 break; 1144 } 1145 } 1146 1147 /* Pass a file node closure through to the editor *unless* we 1148 deleted the file (which doesn't require us to open it). */ 1149 if ((nb->kind == svn_node_file) && (nb->file_baton)) 1150 { 1151 LDR_DBG(("Closing file %p\n", nb->file_baton)); 1152 SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool)); 1153 } 1154 1155 /* The svn_node_dir case is handled in close_revision */ 1156 1157 return SVN_NO_ERROR; 1158} 1159 1160static svn_error_t * 1161close_revision(void *baton) 1162{ 1163 struct revision_baton *rb = baton; 1164 const svn_delta_editor_t *commit_editor = rb->pb->commit_editor; 1165 void *commit_edit_baton = rb->pb->commit_edit_baton; 1166 svn_revnum_t committed_rev = SVN_INVALID_REVNUM; 1167 1168 /* Fake revision 0 */ 1169 if (rb->rev == 0) 1170 { 1171 /* ### Don't print directly; generate a notification. */ 1172 if (! rb->pb->quiet) 1173 SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n")); 1174 } 1175 else if (commit_editor) 1176 { 1177 /* Close all pending open directories, and then close the edit 1178 session itself */ 1179 while (rb->db && rb->db->parent) 1180 { 1181 LDR_DBG(("Closing dir %p\n", rb->db->baton)); 1182 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 1183 rb->db = rb->db->parent; 1184 } 1185 /* root dir's baton */ 1186 LDR_DBG(("Closing edit on %p\n", commit_edit_baton)); 1187 SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool)); 1188 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 1189 } 1190 else 1191 { 1192 void *child_baton; 1193 1194 /* Legitimate revision with no node information */ 1195 SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor, 1196 &commit_edit_baton, rb->revprop_table, 1197 commit_callback, baton, 1198 NULL, FALSE, rb->pool)); 1199 1200 SVN_ERR(commit_editor->open_root(commit_edit_baton, 1201 rb->rev - rb->rev_offset - 1, 1202 rb->pool, &child_baton)); 1203 1204 LDR_DBG(("Opened root %p\n", child_baton)); 1205 LDR_DBG(("Closing edit on %p\n", commit_edit_baton)); 1206 SVN_ERR(commit_editor->close_directory(child_baton, rb->pool)); 1207 SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool)); 1208 } 1209 1210 /* svn_fs_commit_txn() rewrites the datestamp and author properties; 1211 we'll rewrite them again by hand after closing the commit_editor. 1212 The only time we don't do this is for revision 0 when loaded into 1213 a non-empty repository. */ 1214 if (rb->rev > 0) 1215 { 1216 committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev); 1217 } 1218 else if (rb->rev_offset == -1) 1219 { 1220 committed_rev = 0; 1221 } 1222 1223 if (SVN_IS_VALID_REVNUM(committed_rev)) 1224 { 1225 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE, 1226 rb->datestamp, rb->pool)); 1227 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1228 SVN_PROP_REVISION_DATE, 1229 NULL, rb->datestamp, rb->pool)); 1230 SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR, 1231 rb->author, rb->pool)); 1232 SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev, 1233 SVN_PROP_REVISION_AUTHOR, 1234 NULL, rb->author, rb->pool)); 1235 } 1236 1237 svn_pool_destroy(rb->pool); 1238 1239 return SVN_NO_ERROR; 1240} 1241 1242svn_error_t * 1243svn_rdump__load_dumpstream(svn_stream_t *stream, 1244 svn_ra_session_t *session, 1245 svn_ra_session_t *aux_session, 1246 svn_boolean_t quiet, 1247 svn_cancel_func_t cancel_func, 1248 void *cancel_baton, 1249 apr_pool_t *pool) 1250{ 1251 svn_repos_parse_fns3_t *parser; 1252 struct parse_baton *parse_baton; 1253 const svn_string_t *lock_string; 1254 svn_boolean_t be_atomic; 1255 svn_error_t *err; 1256 const char *session_url, *root_url, *parent_dir; 1257 1258 SVN_ERR(svn_ra_has_capability(session, &be_atomic, 1259 SVN_RA_CAPABILITY_ATOMIC_REVPROPS, 1260 pool)); 1261 SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool)); 1262 SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool)); 1263 SVN_ERR(svn_ra_get_session_url(session, &session_url, pool)); 1264 SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir, 1265 session_url, pool)); 1266 1267 parser = apr_pcalloc(pool, sizeof(*parser)); 1268 parser->magic_header_record = magic_header_record; 1269 parser->uuid_record = uuid_record; 1270 parser->new_revision_record = new_revision_record; 1271 parser->new_node_record = new_node_record; 1272 parser->set_revision_property = set_revision_property; 1273 parser->set_node_property = set_node_property; 1274 parser->delete_node_property = delete_node_property; 1275 parser->remove_node_props = remove_node_props; 1276 parser->set_fulltext = set_fulltext; 1277 parser->apply_textdelta = apply_textdelta; 1278 parser->close_node = close_node; 1279 parser->close_revision = close_revision; 1280 1281 parse_baton = apr_pcalloc(pool, sizeof(*parse_baton)); 1282 parse_baton->session = session; 1283 parse_baton->aux_session = aux_session; 1284 parse_baton->quiet = quiet; 1285 parse_baton->root_url = root_url; 1286 parse_baton->parent_dir = parent_dir; 1287 parse_baton->rev_map = apr_hash_make(pool); 1288 parse_baton->last_rev_mapped = SVN_INVALID_REVNUM; 1289 parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM; 1290 1291 err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE, 1292 cancel_func, cancel_baton, pool); 1293 1294 /* If all goes well, or if we're cancelled cleanly, don't leave a 1295 stray lock behind. */ 1296 if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED))) 1297 err = svn_error_compose_create( 1298 svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK, 1299 lock_string, pool), 1300 err); 1301 return err; 1302} 1303