1251881Speter/* dump.c --- writing filesystem contents into a portable 'dumpfile' format. 2251881Speter * 3251881Speter * ==================================================================== 4251881Speter * Licensed to the Apache Software Foundation (ASF) under one 5251881Speter * or more contributor license agreements. See the NOTICE file 6251881Speter * distributed with this work for additional information 7251881Speter * regarding copyright ownership. The ASF licenses this file 8251881Speter * to you under the Apache License, Version 2.0 (the 9251881Speter * "License"); you may not use this file except in compliance 10251881Speter * with the License. You may obtain a copy of the License at 11251881Speter * 12251881Speter * http://www.apache.org/licenses/LICENSE-2.0 13251881Speter * 14251881Speter * Unless required by applicable law or agreed to in writing, 15251881Speter * software distributed under the License is distributed on an 16251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17251881Speter * KIND, either express or implied. See the License for the 18251881Speter * specific language governing permissions and limitations 19251881Speter * under the License. 20251881Speter * ==================================================================== 21251881Speter */ 22251881Speter 23251881Speter 24251881Speter#include "svn_private_config.h" 25251881Speter#include "svn_pools.h" 26251881Speter#include "svn_error.h" 27251881Speter#include "svn_fs.h" 28251881Speter#include "svn_hash.h" 29251881Speter#include "svn_iter.h" 30251881Speter#include "svn_repos.h" 31251881Speter#include "svn_string.h" 32251881Speter#include "svn_dirent_uri.h" 33251881Speter#include "svn_path.h" 34251881Speter#include "svn_time.h" 35251881Speter#include "svn_checksum.h" 36251881Speter#include "svn_props.h" 37251881Speter#include "svn_sorts.h" 38251881Speter 39251881Speter#include "private/svn_mergeinfo_private.h" 40251881Speter#include "private/svn_fs_private.h" 41251881Speter 42251881Speter#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 43251881Speter 44251881Speter/*----------------------------------------------------------------------*/ 45251881Speter 46251881Speter 47251881Speter 48251881Speter/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and 49251881Speter store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, 50251881Speter in which case the delta will be computed against an empty file, as 51251881Speter per the svn_fs_get_file_delta_stream docstring. Record the length 52251881Speter of the temporary file in *LEN, and rewind the file before 53251881Speter returning. */ 54251881Speterstatic svn_error_t * 55251881Speterstore_delta(apr_file_t **tempfile, svn_filesize_t *len, 56251881Speter svn_fs_root_t *oldroot, const char *oldpath, 57251881Speter svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) 58251881Speter{ 59251881Speter svn_stream_t *temp_stream; 60251881Speter apr_off_t offset = 0; 61251881Speter svn_txdelta_stream_t *delta_stream; 62251881Speter svn_txdelta_window_handler_t wh; 63251881Speter void *whb; 64251881Speter 65251881Speter /* Create a temporary file and open a stream to it. Note that we need 66251881Speter the file handle in order to rewind it. */ 67251881Speter SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, 68251881Speter svn_io_file_del_on_pool_cleanup, 69251881Speter pool, pool)); 70251881Speter temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); 71251881Speter 72251881Speter /* Compute the delta and send it to the temporary file. */ 73251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, 74251881Speter newroot, newpath, pool)); 75251881Speter svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0, 76251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 77251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); 78251881Speter 79251881Speter /* Get the length of the temporary file and rewind it. */ 80251881Speter SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); 81251881Speter *len = offset; 82251881Speter offset = 0; 83251881Speter return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); 84251881Speter} 85251881Speter 86251881Speter 87251881Speter/*----------------------------------------------------------------------*/ 88251881Speter 89251881Speter/** An editor which dumps node-data in 'dumpfile format' to a file. **/ 90251881Speter 91251881Speter/* Look, mom! No file batons! */ 92251881Speter 93251881Speterstruct edit_baton 94251881Speter{ 95251881Speter /* The relpath which implicitly prepends all full paths coming into 96251881Speter this editor. This will almost always be "". */ 97251881Speter const char *path; 98251881Speter 99251881Speter /* The stream to dump to. */ 100251881Speter svn_stream_t *stream; 101251881Speter 102251881Speter /* Send feedback here, if non-NULL */ 103251881Speter svn_repos_notify_func_t notify_func; 104251881Speter void *notify_baton; 105251881Speter 106251881Speter /* The fs revision root, so we can read the contents of paths. */ 107251881Speter svn_fs_root_t *fs_root; 108251881Speter svn_revnum_t current_rev; 109251881Speter 110251881Speter /* The fs, so we can grab historic information if needed. */ 111251881Speter svn_fs_t *fs; 112251881Speter 113251881Speter /* True if dumped nodes should output deltas instead of full text. */ 114251881Speter svn_boolean_t use_deltas; 115251881Speter 116251881Speter /* True if this "dump" is in fact a verify. */ 117251881Speter svn_boolean_t verify; 118251881Speter 119251881Speter /* The first revision dumped in this dumpstream. */ 120251881Speter svn_revnum_t oldest_dumped_rev; 121251881Speter 122251881Speter /* If not NULL, set to true if any references to revisions older than 123251881Speter OLDEST_DUMPED_REV were found in the dumpstream. */ 124251881Speter svn_boolean_t *found_old_reference; 125251881Speter 126251881Speter /* If not NULL, set to true if any mergeinfo was dumped which contains 127251881Speter revisions older than OLDEST_DUMPED_REV. */ 128251881Speter svn_boolean_t *found_old_mergeinfo; 129251881Speter 130251881Speter /* reusable buffer for writing file contents */ 131251881Speter char buffer[SVN__STREAM_CHUNK_SIZE]; 132251881Speter apr_size_t bufsize; 133251881Speter}; 134251881Speter 135251881Speterstruct dir_baton 136251881Speter{ 137251881Speter struct edit_baton *edit_baton; 138251881Speter struct dir_baton *parent_dir_baton; 139251881Speter 140251881Speter /* is this directory a new addition to this revision? */ 141251881Speter svn_boolean_t added; 142251881Speter 143251881Speter /* has this directory been written to the output stream? */ 144251881Speter svn_boolean_t written_out; 145251881Speter 146251881Speter /* the repository relpath associated with this directory */ 147251881Speter const char *path; 148251881Speter 149251881Speter /* The comparison repository relpath and revision of this directory. 150251881Speter If both of these are valid, use them as a source against which to 151251881Speter compare the directory instead of the default comparison source of 152251881Speter PATH in the previous revision. */ 153251881Speter const char *cmp_path; 154251881Speter svn_revnum_t cmp_rev; 155251881Speter 156251881Speter /* hash of paths that need to be deleted, though some -might- be 157251881Speter replaced. maps const char * paths to this dir_baton. (they're 158251881Speter full paths, because that's what the editor driver gives us. but 159251881Speter really, they're all within this directory.) */ 160251881Speter apr_hash_t *deleted_entries; 161251881Speter 162251881Speter /* pool to be used for deleting the hash items */ 163251881Speter apr_pool_t *pool; 164251881Speter}; 165251881Speter 166251881Speter 167251881Speter/* Make a directory baton to represent the directory was path 168251881Speter (relative to EDIT_BATON's path) is PATH. 169251881Speter 170251881Speter CMP_PATH/CMP_REV are the path/revision against which this directory 171251881Speter should be compared for changes. If either is omitted (NULL for the 172251881Speter path, SVN_INVALID_REVNUM for the rev), just compare this directory 173251881Speter PATH against itself in the previous revision. 174251881Speter 175251881Speter PARENT_DIR_BATON is the directory baton of this directory's parent, 176251881Speter or NULL if this is the top-level directory of the edit. ADDED 177251881Speter indicated if this directory is newly added in this revision. 178251881Speter Perform all allocations in POOL. */ 179251881Speterstatic struct dir_baton * 180251881Spetermake_dir_baton(const char *path, 181251881Speter const char *cmp_path, 182251881Speter svn_revnum_t cmp_rev, 183251881Speter void *edit_baton, 184251881Speter void *parent_dir_baton, 185251881Speter svn_boolean_t added, 186251881Speter apr_pool_t *pool) 187251881Speter{ 188251881Speter struct edit_baton *eb = edit_baton; 189251881Speter struct dir_baton *pb = parent_dir_baton; 190251881Speter struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); 191251881Speter const char *full_path; 192251881Speter 193251881Speter /* A path relative to nothing? I don't think so. */ 194251881Speter SVN_ERR_ASSERT_NO_RETURN(!path || pb); 195251881Speter 196251881Speter /* Construct the full path of this node. */ 197251881Speter if (pb) 198251881Speter full_path = svn_relpath_join(eb->path, path, pool); 199251881Speter else 200251881Speter full_path = apr_pstrdup(pool, eb->path); 201251881Speter 202251881Speter /* Remove leading slashes from copyfrom paths. */ 203251881Speter if (cmp_path) 204251881Speter cmp_path = svn_relpath_canonicalize(cmp_path, pool); 205251881Speter 206251881Speter new_db->edit_baton = eb; 207251881Speter new_db->parent_dir_baton = pb; 208251881Speter new_db->path = full_path; 209251881Speter new_db->cmp_path = cmp_path; 210251881Speter new_db->cmp_rev = cmp_rev; 211251881Speter new_db->added = added; 212251881Speter new_db->written_out = FALSE; 213251881Speter new_db->deleted_entries = apr_hash_make(pool); 214251881Speter new_db->pool = pool; 215251881Speter 216251881Speter return new_db; 217251881Speter} 218251881Speter 219251881Speter 220269847Speter/* If the mergeinfo in MERGEINFO_STR refers to any revisions older than 221269847Speter * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE, 222269847Speter * otherwise leave *FOUND_OLD_MERGEINFO unchanged. 223269847Speter */ 224269847Speterstatic svn_error_t * 225269847Speterverify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo, 226269847Speter const char *mergeinfo_str, 227269847Speter svn_revnum_t oldest_dumped_rev, 228269847Speter svn_repos_notify_func_t notify_func, 229269847Speter void *notify_baton, 230269847Speter apr_pool_t *pool) 231269847Speter{ 232269847Speter svn_mergeinfo_t mergeinfo, old_mergeinfo; 233269847Speter 234269847Speter SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool)); 235269847Speter SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 236269847Speter &old_mergeinfo, mergeinfo, 237269847Speter oldest_dumped_rev - 1, 0, 238269847Speter TRUE, pool, pool)); 239269847Speter 240269847Speter if (apr_hash_count(old_mergeinfo)) 241269847Speter { 242269847Speter svn_repos_notify_t *notify = 243269847Speter svn_repos_notify_create(svn_repos_notify_warning, pool); 244269847Speter 245269847Speter notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 246269847Speter notify->warning_str = apr_psprintf( 247269847Speter pool, 248269847Speter _("Mergeinfo referencing revision(s) prior " 249269847Speter "to the oldest dumped revision (r%ld). " 250269847Speter "Loading this dump may result in invalid " 251269847Speter "mergeinfo."), 252269847Speter oldest_dumped_rev); 253269847Speter 254269847Speter if (found_old_mergeinfo) 255269847Speter *found_old_mergeinfo = TRUE; 256269847Speter notify_func(notify_baton, notify, pool); 257269847Speter } 258269847Speter 259269847Speter return SVN_NO_ERROR; 260269847Speter} 261269847Speter 262269847Speter 263251881Speter/* This helper is the main "meat" of the editor -- it does all the 264251881Speter work of writing a node record. 265251881Speter 266251881Speter Write out a node record for PATH of type KIND under EB->FS_ROOT. 267251881Speter ACTION describes what is happening to the node (see enum svn_node_action). 268251881Speter Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. 269251881Speter 270251881Speter If the node was itself copied, IS_COPY is TRUE and the 271251881Speter path/revision of the copy source are in CMP_PATH/CMP_REV. If 272251881Speter IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part 273251881Speter of a copied subtree. 274251881Speter */ 275251881Speterstatic svn_error_t * 276251881Speterdump_node(struct edit_baton *eb, 277251881Speter const char *path, 278251881Speter svn_node_kind_t kind, 279251881Speter enum svn_node_action action, 280251881Speter svn_boolean_t is_copy, 281251881Speter const char *cmp_path, 282251881Speter svn_revnum_t cmp_rev, 283251881Speter apr_pool_t *pool) 284251881Speter{ 285251881Speter svn_stringbuf_t *propstring; 286251881Speter svn_filesize_t content_length = 0; 287251881Speter apr_size_t len; 288251881Speter svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; 289251881Speter const char *compare_path = path; 290251881Speter svn_revnum_t compare_rev = eb->current_rev - 1; 291251881Speter svn_fs_root_t *compare_root = NULL; 292251881Speter apr_file_t *delta_file = NULL; 293251881Speter 294251881Speter /* Maybe validate the path. */ 295251881Speter if (eb->verify || eb->notify_func) 296251881Speter { 297251881Speter svn_error_t *err = svn_fs__path_valid(path, pool); 298251881Speter 299251881Speter if (err) 300251881Speter { 301251881Speter if (eb->notify_func) 302251881Speter { 303251881Speter char errbuf[512]; /* ### svn_strerror() magic number */ 304251881Speter svn_repos_notify_t *notify; 305251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, pool); 306251881Speter 307251881Speter notify->warning = svn_repos_notify_warning_invalid_fspath; 308251881Speter notify->warning_str = apr_psprintf( 309251881Speter pool, 310251881Speter _("E%06d: While validating fspath '%s': %s"), 311251881Speter err->apr_err, path, 312251881Speter svn_err_best_message(err, errbuf, sizeof(errbuf))); 313251881Speter 314251881Speter eb->notify_func(eb->notify_baton, notify, pool); 315251881Speter } 316251881Speter 317251881Speter /* Return the error in addition to notifying about it. */ 318251881Speter if (eb->verify) 319251881Speter return svn_error_trace(err); 320251881Speter else 321251881Speter svn_error_clear(err); 322251881Speter } 323251881Speter } 324251881Speter 325251881Speter /* Write out metadata headers for this file node. */ 326251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 327251881Speter SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", 328251881Speter path)); 329251881Speter if (kind == svn_node_file) 330251881Speter SVN_ERR(svn_stream_puts(eb->stream, 331251881Speter SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); 332251881Speter else if (kind == svn_node_dir) 333251881Speter SVN_ERR(svn_stream_puts(eb->stream, 334251881Speter SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); 335251881Speter 336251881Speter /* Remove leading slashes from copyfrom paths. */ 337251881Speter if (cmp_path) 338251881Speter cmp_path = svn_relpath_canonicalize(cmp_path, pool); 339251881Speter 340251881Speter /* Validate the comparison path/rev. */ 341251881Speter if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) 342251881Speter { 343251881Speter compare_path = cmp_path; 344251881Speter compare_rev = cmp_rev; 345251881Speter } 346251881Speter 347251881Speter if (action == svn_node_action_change) 348251881Speter { 349251881Speter SVN_ERR(svn_stream_puts(eb->stream, 350251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); 351251881Speter 352251881Speter /* either the text or props changed, or possibly both. */ 353251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 354251881Speter svn_fs_root_fs(eb->fs_root), 355251881Speter compare_rev, pool)); 356251881Speter 357251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 358251881Speter compare_root, compare_path, 359251881Speter eb->fs_root, path, pool)); 360251881Speter if (kind == svn_node_file) 361251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 362251881Speter compare_root, compare_path, 363251881Speter eb->fs_root, path, pool)); 364251881Speter } 365251881Speter else if (action == svn_node_action_replace) 366251881Speter { 367251881Speter if (! is_copy) 368251881Speter { 369251881Speter /* a simple delete+add, implied by a single 'replace' action. */ 370251881Speter SVN_ERR(svn_stream_puts(eb->stream, 371251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION 372251881Speter ": replace\n")); 373251881Speter 374251881Speter /* definitely need to dump all content for a replace. */ 375251881Speter if (kind == svn_node_file) 376251881Speter must_dump_text = TRUE; 377251881Speter must_dump_props = TRUE; 378251881Speter } 379251881Speter else 380251881Speter { 381251881Speter /* more complex: delete original, then add-with-history. */ 382251881Speter 383251881Speter /* the path & kind headers have already been printed; just 384251881Speter add a delete action, and end the current record.*/ 385251881Speter SVN_ERR(svn_stream_puts(eb->stream, 386251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION 387251881Speter ": delete\n\n")); 388251881Speter 389251881Speter /* recurse: print an additional add-with-history record. */ 390251881Speter SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, 391251881Speter is_copy, compare_path, compare_rev, pool)); 392251881Speter 393251881Speter /* we can leave this routine quietly now, don't need to dump 394251881Speter any content; that was already done in the second record. */ 395251881Speter must_dump_text = FALSE; 396251881Speter must_dump_props = FALSE; 397251881Speter } 398251881Speter } 399251881Speter else if (action == svn_node_action_delete) 400251881Speter { 401251881Speter SVN_ERR(svn_stream_puts(eb->stream, 402251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); 403251881Speter 404251881Speter /* we can leave this routine quietly now, don't need to dump 405251881Speter any content. */ 406251881Speter must_dump_text = FALSE; 407251881Speter must_dump_props = FALSE; 408251881Speter } 409251881Speter else if (action == svn_node_action_add) 410251881Speter { 411251881Speter SVN_ERR(svn_stream_puts(eb->stream, 412251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); 413251881Speter 414251881Speter if (! is_copy) 415251881Speter { 416251881Speter /* Dump all contents for a simple 'add'. */ 417251881Speter if (kind == svn_node_file) 418251881Speter must_dump_text = TRUE; 419251881Speter must_dump_props = TRUE; 420251881Speter } 421251881Speter else 422251881Speter { 423251881Speter if (!eb->verify && cmp_rev < eb->oldest_dumped_rev 424251881Speter && eb->notify_func) 425251881Speter { 426251881Speter svn_repos_notify_t *notify = 427251881Speter svn_repos_notify_create(svn_repos_notify_warning, pool); 428251881Speter 429251881Speter notify->warning = svn_repos_notify_warning_found_old_reference; 430251881Speter notify->warning_str = apr_psprintf( 431251881Speter pool, 432251881Speter _("Referencing data in revision %ld," 433251881Speter " which is older than the oldest" 434251881Speter " dumped revision (r%ld). Loading this dump" 435251881Speter " into an empty repository" 436251881Speter " will fail."), 437251881Speter cmp_rev, eb->oldest_dumped_rev); 438251881Speter if (eb->found_old_reference) 439251881Speter *eb->found_old_reference = TRUE; 440251881Speter eb->notify_func(eb->notify_baton, notify, pool); 441251881Speter } 442251881Speter 443251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 444251881Speter SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV 445251881Speter ": %ld\n" 446251881Speter SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH 447251881Speter ": %s\n", 448251881Speter cmp_rev, cmp_path)); 449251881Speter 450251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 451251881Speter svn_fs_root_fs(eb->fs_root), 452251881Speter compare_rev, pool)); 453251881Speter 454251881Speter /* Need to decide if the copied node had any extra textual or 455251881Speter property mods as well. */ 456251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 457251881Speter compare_root, compare_path, 458251881Speter eb->fs_root, path, pool)); 459251881Speter if (kind == svn_node_file) 460251881Speter { 461251881Speter svn_checksum_t *checksum; 462251881Speter const char *hex_digest; 463251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 464251881Speter compare_root, compare_path, 465251881Speter eb->fs_root, path, pool)); 466251881Speter 467251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 468251881Speter compare_root, compare_path, 469251881Speter FALSE, pool)); 470251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 471251881Speter if (hex_digest) 472251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 473251881Speter SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 474251881Speter ": %s\n", hex_digest)); 475251881Speter 476251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 477251881Speter compare_root, compare_path, 478251881Speter FALSE, pool)); 479251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 480251881Speter if (hex_digest) 481251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 482251881Speter SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 483251881Speter ": %s\n", hex_digest)); 484251881Speter } 485251881Speter } 486251881Speter } 487251881Speter 488251881Speter if ((! must_dump_text) && (! must_dump_props)) 489251881Speter { 490251881Speter /* If we're not supposed to dump text or props, so be it, we can 491251881Speter just go home. However, if either one needs to be dumped, 492251881Speter then our dumpstream format demands that at a *minimum*, we 493251881Speter see a lone "PROPS-END" as a divider between text and props 494251881Speter content within the content-block. */ 495251881Speter len = 2; 496251881Speter return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 497251881Speter } 498251881Speter 499251881Speter /*** Start prepping content to dump... ***/ 500251881Speter 501251881Speter /* If we are supposed to dump properties, write out a property 502251881Speter length header and generate a stringbuf that contains those 503251881Speter property values here. */ 504251881Speter if (must_dump_props) 505251881Speter { 506251881Speter apr_hash_t *prophash, *oldhash = NULL; 507251881Speter apr_size_t proplen; 508251881Speter svn_stream_t *propstream; 509251881Speter 510251881Speter SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); 511251881Speter 512251881Speter /* If this is a partial dump, then issue a warning if we dump mergeinfo 513251881Speter properties that refer to revisions older than the first revision 514251881Speter dumped. */ 515251881Speter if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) 516251881Speter { 517251881Speter svn_string_t *mergeinfo_str = svn_hash_gets(prophash, 518251881Speter SVN_PROP_MERGEINFO); 519251881Speter if (mergeinfo_str) 520251881Speter { 521269847Speter /* An error in verifying the mergeinfo must not prevent dumping 522269847Speter the data. Ignore any such error. */ 523269847Speter svn_error_clear(verify_mergeinfo_revisions( 524269847Speter eb->found_old_mergeinfo, 525269847Speter mergeinfo_str->data, eb->oldest_dumped_rev, 526269847Speter eb->notify_func, eb->notify_baton, 527269847Speter pool)); 528251881Speter } 529251881Speter } 530251881Speter 531251881Speter if (eb->use_deltas && compare_root) 532251881Speter { 533251881Speter /* Fetch the old property hash to diff against and output a header 534251881Speter saying that our property contents are a delta. */ 535251881Speter SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, 536251881Speter pool)); 537251881Speter SVN_ERR(svn_stream_puts(eb->stream, 538251881Speter SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); 539251881Speter } 540251881Speter else 541251881Speter oldhash = apr_hash_make(pool); 542251881Speter propstring = svn_stringbuf_create_ensure(0, pool); 543251881Speter propstream = svn_stream_from_stringbuf(propstring, pool); 544251881Speter SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, 545251881Speter "PROPS-END", pool)); 546251881Speter SVN_ERR(svn_stream_close(propstream)); 547251881Speter proplen = propstring->len; 548251881Speter content_length += proplen; 549251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 550251881Speter SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 551251881Speter ": %" APR_SIZE_T_FMT "\n", proplen)); 552251881Speter } 553251881Speter 554251881Speter /* If we are supposed to dump text, write out a text length header 555251881Speter here, and an MD5 checksum (if available). */ 556251881Speter if (must_dump_text && (kind == svn_node_file)) 557251881Speter { 558251881Speter svn_checksum_t *checksum; 559251881Speter const char *hex_digest; 560251881Speter svn_filesize_t textlen; 561251881Speter 562251881Speter if (eb->use_deltas) 563251881Speter { 564251881Speter /* Compute the text delta now and write it into a temporary 565251881Speter file, so that we can find its length. Output a header 566251881Speter saying our text contents are a delta. */ 567251881Speter SVN_ERR(store_delta(&delta_file, &textlen, compare_root, 568251881Speter compare_path, eb->fs_root, path, pool)); 569251881Speter SVN_ERR(svn_stream_puts(eb->stream, 570251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); 571251881Speter 572251881Speter if (compare_root) 573251881Speter { 574251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 575251881Speter compare_root, compare_path, 576251881Speter FALSE, pool)); 577251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 578251881Speter if (hex_digest) 579251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 580251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 581251881Speter ": %s\n", hex_digest)); 582251881Speter 583251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 584251881Speter compare_root, compare_path, 585251881Speter FALSE, pool)); 586251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 587251881Speter if (hex_digest) 588251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 589251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 590251881Speter ": %s\n", hex_digest)); 591251881Speter } 592251881Speter } 593251881Speter else 594251881Speter { 595251881Speter /* Just fetch the length of the file. */ 596251881Speter SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); 597251881Speter } 598251881Speter 599251881Speter content_length += textlen; 600251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 601251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH 602251881Speter ": %" SVN_FILESIZE_T_FMT "\n", textlen)); 603251881Speter 604251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 605251881Speter eb->fs_root, path, FALSE, pool)); 606251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 607251881Speter if (hex_digest) 608251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 609251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 610251881Speter ": %s\n", hex_digest)); 611251881Speter 612251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 613251881Speter eb->fs_root, path, FALSE, pool)); 614251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 615251881Speter if (hex_digest) 616251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 617251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 618251881Speter ": %s\n", hex_digest)); 619251881Speter } 620251881Speter 621251881Speter /* 'Content-length:' is the last header before we dump the content, 622251881Speter and is the sum of the text and prop contents lengths. We write 623251881Speter this only for the benefit of non-Subversion RFC-822 parsers. */ 624251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 625251881Speter SVN_REPOS_DUMPFILE_CONTENT_LENGTH 626251881Speter ": %" SVN_FILESIZE_T_FMT "\n\n", 627251881Speter content_length)); 628251881Speter 629251881Speter /* Dump property content if we're supposed to do so. */ 630251881Speter if (must_dump_props) 631251881Speter { 632251881Speter len = propstring->len; 633251881Speter SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); 634251881Speter } 635251881Speter 636251881Speter /* Dump text content */ 637251881Speter if (must_dump_text && (kind == svn_node_file)) 638251881Speter { 639251881Speter svn_stream_t *contents; 640251881Speter 641251881Speter if (delta_file) 642251881Speter { 643251881Speter /* Make sure to close the underlying file when the stream is 644251881Speter closed. */ 645251881Speter contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); 646251881Speter } 647251881Speter else 648251881Speter SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); 649251881Speter 650251881Speter SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), 651251881Speter NULL, NULL, pool)); 652251881Speter } 653251881Speter 654251881Speter len = 2; 655251881Speter return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 656251881Speter} 657251881Speter 658251881Speter 659251881Speterstatic svn_error_t * 660251881Speteropen_root(void *edit_baton, 661251881Speter svn_revnum_t base_revision, 662251881Speter apr_pool_t *pool, 663251881Speter void **root_baton) 664251881Speter{ 665251881Speter *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 666251881Speter edit_baton, NULL, FALSE, pool); 667251881Speter return SVN_NO_ERROR; 668251881Speter} 669251881Speter 670251881Speter 671251881Speterstatic svn_error_t * 672251881Speterdelete_entry(const char *path, 673251881Speter svn_revnum_t revision, 674251881Speter void *parent_baton, 675251881Speter apr_pool_t *pool) 676251881Speter{ 677251881Speter struct dir_baton *pb = parent_baton; 678251881Speter const char *mypath = apr_pstrdup(pb->pool, path); 679251881Speter 680251881Speter /* remember this path needs to be deleted. */ 681251881Speter svn_hash_sets(pb->deleted_entries, mypath, pb); 682251881Speter 683251881Speter return SVN_NO_ERROR; 684251881Speter} 685251881Speter 686251881Speter 687251881Speterstatic svn_error_t * 688251881Speteradd_directory(const char *path, 689251881Speter void *parent_baton, 690251881Speter const char *copyfrom_path, 691251881Speter svn_revnum_t copyfrom_rev, 692251881Speter apr_pool_t *pool, 693251881Speter void **child_baton) 694251881Speter{ 695251881Speter struct dir_baton *pb = parent_baton; 696251881Speter struct edit_baton *eb = pb->edit_baton; 697251881Speter void *val; 698251881Speter svn_boolean_t is_copy = FALSE; 699251881Speter struct dir_baton *new_db 700251881Speter = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool); 701251881Speter 702251881Speter /* This might be a replacement -- is the path already deleted? */ 703251881Speter val = svn_hash_gets(pb->deleted_entries, path); 704251881Speter 705251881Speter /* Detect an add-with-history. */ 706251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 707251881Speter 708251881Speter /* Dump the node. */ 709251881Speter SVN_ERR(dump_node(eb, path, 710251881Speter svn_node_dir, 711251881Speter val ? svn_node_action_replace : svn_node_action_add, 712251881Speter is_copy, 713251881Speter is_copy ? copyfrom_path : NULL, 714251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 715251881Speter pool)); 716251881Speter 717251881Speter if (val) 718251881Speter /* Delete the path, it's now been dumped. */ 719251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 720251881Speter 721251881Speter new_db->written_out = TRUE; 722251881Speter 723251881Speter *child_baton = new_db; 724251881Speter return SVN_NO_ERROR; 725251881Speter} 726251881Speter 727251881Speter 728251881Speterstatic svn_error_t * 729251881Speteropen_directory(const char *path, 730251881Speter void *parent_baton, 731251881Speter svn_revnum_t base_revision, 732251881Speter apr_pool_t *pool, 733251881Speter void **child_baton) 734251881Speter{ 735251881Speter struct dir_baton *pb = parent_baton; 736251881Speter struct edit_baton *eb = pb->edit_baton; 737251881Speter struct dir_baton *new_db; 738251881Speter const char *cmp_path = NULL; 739251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 740251881Speter 741251881Speter /* If the parent directory has explicit comparison path and rev, 742251881Speter record the same for this one. */ 743251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 744251881Speter { 745251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 746251881Speter svn_relpath_basename(path, pool), pool); 747251881Speter cmp_rev = pb->cmp_rev; 748251881Speter } 749251881Speter 750251881Speter new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool); 751251881Speter *child_baton = new_db; 752251881Speter return SVN_NO_ERROR; 753251881Speter} 754251881Speter 755251881Speter 756251881Speterstatic svn_error_t * 757251881Speterclose_directory(void *dir_baton, 758251881Speter apr_pool_t *pool) 759251881Speter{ 760251881Speter struct dir_baton *db = dir_baton; 761251881Speter struct edit_baton *eb = db->edit_baton; 762251881Speter apr_pool_t *subpool = svn_pool_create(pool); 763251881Speter int i; 764251881Speter apr_array_header_t *sorted_entries; 765251881Speter 766251881Speter /* Sort entries lexically instead of as paths. Even though the entries 767251881Speter * are full paths they're all in the same directory (see comment in struct 768251881Speter * dir_baton definition). So we really want to sort by basename, in which 769251881Speter * case the lexical sort function is more efficient. */ 770251881Speter sorted_entries = svn_sort__hash(db->deleted_entries, 771251881Speter svn_sort_compare_items_lexically, pool); 772251881Speter for (i = 0; i < sorted_entries->nelts; i++) 773251881Speter { 774251881Speter const char *path = APR_ARRAY_IDX(sorted_entries, i, 775251881Speter svn_sort__item_t).key; 776251881Speter 777251881Speter svn_pool_clear(subpool); 778251881Speter 779251881Speter /* By sending 'svn_node_unknown', the Node-kind: header simply won't 780251881Speter be written out. No big deal at all, really. The loader 781251881Speter shouldn't care. */ 782251881Speter SVN_ERR(dump_node(eb, path, 783251881Speter svn_node_unknown, svn_node_action_delete, 784251881Speter FALSE, NULL, SVN_INVALID_REVNUM, subpool)); 785251881Speter } 786251881Speter 787251881Speter svn_pool_destroy(subpool); 788251881Speter return SVN_NO_ERROR; 789251881Speter} 790251881Speter 791251881Speter 792251881Speterstatic svn_error_t * 793251881Speteradd_file(const char *path, 794251881Speter void *parent_baton, 795251881Speter const char *copyfrom_path, 796251881Speter svn_revnum_t copyfrom_rev, 797251881Speter apr_pool_t *pool, 798251881Speter void **file_baton) 799251881Speter{ 800251881Speter struct dir_baton *pb = parent_baton; 801251881Speter struct edit_baton *eb = pb->edit_baton; 802251881Speter void *val; 803251881Speter svn_boolean_t is_copy = FALSE; 804251881Speter 805251881Speter /* This might be a replacement -- is the path already deleted? */ 806251881Speter val = svn_hash_gets(pb->deleted_entries, path); 807251881Speter 808251881Speter /* Detect add-with-history. */ 809251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 810251881Speter 811251881Speter /* Dump the node. */ 812251881Speter SVN_ERR(dump_node(eb, path, 813251881Speter svn_node_file, 814251881Speter val ? svn_node_action_replace : svn_node_action_add, 815251881Speter is_copy, 816251881Speter is_copy ? copyfrom_path : NULL, 817251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 818251881Speter pool)); 819251881Speter 820251881Speter if (val) 821251881Speter /* delete the path, it's now been dumped. */ 822251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 823251881Speter 824251881Speter *file_baton = NULL; /* muhahahaha */ 825251881Speter return SVN_NO_ERROR; 826251881Speter} 827251881Speter 828251881Speter 829251881Speterstatic svn_error_t * 830251881Speteropen_file(const char *path, 831251881Speter void *parent_baton, 832251881Speter svn_revnum_t ancestor_revision, 833251881Speter apr_pool_t *pool, 834251881Speter void **file_baton) 835251881Speter{ 836251881Speter struct dir_baton *pb = parent_baton; 837251881Speter struct edit_baton *eb = pb->edit_baton; 838251881Speter const char *cmp_path = NULL; 839251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 840251881Speter 841251881Speter /* If the parent directory has explicit comparison path and rev, 842251881Speter record the same for this one. */ 843251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 844251881Speter { 845251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 846251881Speter svn_relpath_basename(path, pool), pool); 847251881Speter cmp_rev = pb->cmp_rev; 848251881Speter } 849251881Speter 850251881Speter SVN_ERR(dump_node(eb, path, 851251881Speter svn_node_file, svn_node_action_change, 852251881Speter FALSE, cmp_path, cmp_rev, pool)); 853251881Speter 854251881Speter *file_baton = NULL; /* muhahahaha again */ 855251881Speter return SVN_NO_ERROR; 856251881Speter} 857251881Speter 858251881Speter 859251881Speterstatic svn_error_t * 860251881Speterchange_dir_prop(void *parent_baton, 861251881Speter const char *name, 862251881Speter const svn_string_t *value, 863251881Speter apr_pool_t *pool) 864251881Speter{ 865251881Speter struct dir_baton *db = parent_baton; 866251881Speter struct edit_baton *eb = db->edit_baton; 867251881Speter 868251881Speter /* This function is what distinguishes between a directory that is 869251881Speter opened to merely get somewhere, vs. one that is opened because it 870251881Speter *actually* changed by itself. */ 871251881Speter if (! db->written_out) 872251881Speter { 873251881Speter SVN_ERR(dump_node(eb, db->path, 874251881Speter svn_node_dir, svn_node_action_change, 875251881Speter FALSE, db->cmp_path, db->cmp_rev, pool)); 876251881Speter db->written_out = TRUE; 877251881Speter } 878251881Speter return SVN_NO_ERROR; 879251881Speter} 880251881Speter 881251881Speterstatic svn_error_t * 882251881Speterfetch_props_func(apr_hash_t **props, 883251881Speter void *baton, 884251881Speter const char *path, 885251881Speter svn_revnum_t base_revision, 886251881Speter apr_pool_t *result_pool, 887251881Speter apr_pool_t *scratch_pool) 888251881Speter{ 889251881Speter struct edit_baton *eb = baton; 890251881Speter svn_error_t *err; 891251881Speter svn_fs_root_t *fs_root; 892251881Speter 893251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 894251881Speter base_revision = eb->current_rev - 1; 895251881Speter 896251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 897251881Speter 898251881Speter err = svn_fs_node_proplist(props, fs_root, path, result_pool); 899251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 900251881Speter { 901251881Speter svn_error_clear(err); 902251881Speter *props = apr_hash_make(result_pool); 903251881Speter return SVN_NO_ERROR; 904251881Speter } 905251881Speter else if (err) 906251881Speter return svn_error_trace(err); 907251881Speter 908251881Speter return SVN_NO_ERROR; 909251881Speter} 910251881Speter 911251881Speterstatic svn_error_t * 912251881Speterfetch_kind_func(svn_node_kind_t *kind, 913251881Speter void *baton, 914251881Speter const char *path, 915251881Speter svn_revnum_t base_revision, 916251881Speter apr_pool_t *scratch_pool) 917251881Speter{ 918251881Speter struct edit_baton *eb = baton; 919251881Speter svn_fs_root_t *fs_root; 920251881Speter 921251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 922251881Speter base_revision = eb->current_rev - 1; 923251881Speter 924251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 925251881Speter 926251881Speter SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 927251881Speter 928251881Speter return SVN_NO_ERROR; 929251881Speter} 930251881Speter 931251881Speterstatic svn_error_t * 932251881Speterfetch_base_func(const char **filename, 933251881Speter void *baton, 934251881Speter const char *path, 935251881Speter svn_revnum_t base_revision, 936251881Speter apr_pool_t *result_pool, 937251881Speter apr_pool_t *scratch_pool) 938251881Speter{ 939251881Speter struct edit_baton *eb = baton; 940251881Speter svn_stream_t *contents; 941251881Speter svn_stream_t *file_stream; 942251881Speter const char *tmp_filename; 943251881Speter svn_error_t *err; 944251881Speter svn_fs_root_t *fs_root; 945251881Speter 946251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 947251881Speter base_revision = eb->current_rev - 1; 948251881Speter 949251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 950251881Speter 951251881Speter err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 952251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 953251881Speter { 954251881Speter svn_error_clear(err); 955251881Speter *filename = NULL; 956251881Speter return SVN_NO_ERROR; 957251881Speter } 958251881Speter else if (err) 959251881Speter return svn_error_trace(err); 960251881Speter SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 961251881Speter svn_io_file_del_on_pool_cleanup, 962251881Speter scratch_pool, scratch_pool)); 963251881Speter SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 964251881Speter 965251881Speter *filename = apr_pstrdup(result_pool, tmp_filename); 966251881Speter 967251881Speter return SVN_NO_ERROR; 968251881Speter} 969251881Speter 970251881Speter 971251881Speterstatic svn_error_t * 972251881Speterget_dump_editor(const svn_delta_editor_t **editor, 973251881Speter void **edit_baton, 974251881Speter svn_fs_t *fs, 975251881Speter svn_revnum_t to_rev, 976251881Speter const char *root_path, 977251881Speter svn_stream_t *stream, 978251881Speter svn_boolean_t *found_old_reference, 979251881Speter svn_boolean_t *found_old_mergeinfo, 980251881Speter svn_error_t *(*custom_close_directory)(void *dir_baton, 981251881Speter apr_pool_t *scratch_pool), 982251881Speter svn_repos_notify_func_t notify_func, 983251881Speter void *notify_baton, 984251881Speter svn_revnum_t oldest_dumped_rev, 985251881Speter svn_boolean_t use_deltas, 986251881Speter svn_boolean_t verify, 987251881Speter apr_pool_t *pool) 988251881Speter{ 989251881Speter /* Allocate an edit baton to be stored in every directory baton. 990251881Speter Set it up for the directory baton we create here, which is the 991251881Speter root baton. */ 992251881Speter struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 993251881Speter svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool); 994251881Speter svn_delta_shim_callbacks_t *shim_callbacks = 995251881Speter svn_delta_shim_callbacks_default(pool); 996251881Speter 997251881Speter /* Set up the edit baton. */ 998251881Speter eb->stream = stream; 999251881Speter eb->notify_func = notify_func; 1000251881Speter eb->notify_baton = notify_baton; 1001251881Speter eb->oldest_dumped_rev = oldest_dumped_rev; 1002251881Speter eb->bufsize = sizeof(eb->buffer); 1003251881Speter eb->path = apr_pstrdup(pool, root_path); 1004251881Speter SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); 1005251881Speter eb->fs = fs; 1006251881Speter eb->current_rev = to_rev; 1007251881Speter eb->use_deltas = use_deltas; 1008251881Speter eb->verify = verify; 1009251881Speter eb->found_old_reference = found_old_reference; 1010251881Speter eb->found_old_mergeinfo = found_old_mergeinfo; 1011251881Speter 1012251881Speter /* Set up the editor. */ 1013251881Speter dump_editor->open_root = open_root; 1014251881Speter dump_editor->delete_entry = delete_entry; 1015251881Speter dump_editor->add_directory = add_directory; 1016251881Speter dump_editor->open_directory = open_directory; 1017251881Speter if (custom_close_directory) 1018251881Speter dump_editor->close_directory = custom_close_directory; 1019251881Speter else 1020251881Speter dump_editor->close_directory = close_directory; 1021251881Speter dump_editor->change_dir_prop = change_dir_prop; 1022251881Speter dump_editor->add_file = add_file; 1023251881Speter dump_editor->open_file = open_file; 1024251881Speter 1025251881Speter *edit_baton = eb; 1026251881Speter *editor = dump_editor; 1027251881Speter 1028251881Speter shim_callbacks->fetch_kind_func = fetch_kind_func; 1029251881Speter shim_callbacks->fetch_props_func = fetch_props_func; 1030251881Speter shim_callbacks->fetch_base_func = fetch_base_func; 1031251881Speter shim_callbacks->fetch_baton = eb; 1032251881Speter 1033251881Speter SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1034251881Speter NULL, NULL, shim_callbacks, pool, pool)); 1035251881Speter 1036251881Speter return SVN_NO_ERROR; 1037251881Speter} 1038251881Speter 1039251881Speter/*----------------------------------------------------------------------*/ 1040251881Speter 1041251881Speter/** The main dumping routine, svn_repos_dump_fs. **/ 1042251881Speter 1043251881Speter 1044251881Speter/* Helper for svn_repos_dump_fs. 1045251881Speter 1046251881Speter Write a revision record of REV in FS to writable STREAM, using POOL. 1047251881Speter */ 1048251881Speterstatic svn_error_t * 1049251881Speterwrite_revision_record(svn_stream_t *stream, 1050251881Speter svn_fs_t *fs, 1051251881Speter svn_revnum_t rev, 1052251881Speter apr_pool_t *pool) 1053251881Speter{ 1054251881Speter apr_size_t len; 1055251881Speter apr_hash_t *props; 1056251881Speter svn_stringbuf_t *encoded_prophash; 1057251881Speter apr_time_t timetemp; 1058251881Speter svn_string_t *datevalue; 1059251881Speter svn_stream_t *propstream; 1060251881Speter 1061251881Speter /* Read the revision props even if we're aren't going to dump 1062251881Speter them for verification purposes */ 1063251881Speter SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); 1064251881Speter 1065251881Speter /* Run revision date properties through the time conversion to 1066251881Speter canonicalize them. */ 1067251881Speter /* ### Remove this when it is no longer needed for sure. */ 1068251881Speter datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE); 1069251881Speter if (datevalue) 1070251881Speter { 1071251881Speter SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); 1072251881Speter datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), 1073251881Speter pool); 1074251881Speter svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); 1075251881Speter } 1076251881Speter 1077251881Speter encoded_prophash = svn_stringbuf_create_ensure(0, pool); 1078251881Speter propstream = svn_stream_from_stringbuf(encoded_prophash, pool); 1079251881Speter SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); 1080251881Speter SVN_ERR(svn_stream_close(propstream)); 1081251881Speter 1082251881Speter /* ### someday write a revision-content-checksum */ 1083251881Speter 1084251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1085251881Speter SVN_REPOS_DUMPFILE_REVISION_NUMBER 1086251881Speter ": %ld\n", rev)); 1087251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1088251881Speter SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 1089251881Speter ": %" APR_SIZE_T_FMT "\n", 1090251881Speter encoded_prophash->len)); 1091251881Speter 1092251881Speter /* Write out a regular Content-length header for the benefit of 1093251881Speter non-Subversion RFC-822 parsers. */ 1094251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1095251881Speter SVN_REPOS_DUMPFILE_CONTENT_LENGTH 1096251881Speter ": %" APR_SIZE_T_FMT "\n\n", 1097251881Speter encoded_prophash->len)); 1098251881Speter 1099251881Speter len = encoded_prophash->len; 1100251881Speter SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); 1101251881Speter 1102251881Speter len = 1; 1103251881Speter return svn_stream_write(stream, "\n", &len); 1104251881Speter} 1105251881Speter 1106251881Speter 1107251881Speter 1108251881Speter/* The main dumper. */ 1109251881Spetersvn_error_t * 1110251881Spetersvn_repos_dump_fs3(svn_repos_t *repos, 1111251881Speter svn_stream_t *stream, 1112251881Speter svn_revnum_t start_rev, 1113251881Speter svn_revnum_t end_rev, 1114251881Speter svn_boolean_t incremental, 1115251881Speter svn_boolean_t use_deltas, 1116251881Speter svn_repos_notify_func_t notify_func, 1117251881Speter void *notify_baton, 1118251881Speter svn_cancel_func_t cancel_func, 1119251881Speter void *cancel_baton, 1120251881Speter apr_pool_t *pool) 1121251881Speter{ 1122251881Speter const svn_delta_editor_t *dump_editor; 1123251881Speter void *dump_edit_baton = NULL; 1124251881Speter svn_revnum_t i; 1125251881Speter svn_fs_t *fs = svn_repos_fs(repos); 1126251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1127251881Speter svn_revnum_t youngest; 1128251881Speter const char *uuid; 1129251881Speter int version; 1130251881Speter svn_boolean_t found_old_reference = FALSE; 1131251881Speter svn_boolean_t found_old_mergeinfo = FALSE; 1132251881Speter svn_repos_notify_t *notify; 1133251881Speter 1134251881Speter /* Determine the current youngest revision of the filesystem. */ 1135251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1136251881Speter 1137251881Speter /* Use default vals if necessary. */ 1138251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1139251881Speter start_rev = 0; 1140251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1141251881Speter end_rev = youngest; 1142251881Speter if (! stream) 1143251881Speter stream = svn_stream_empty(pool); 1144251881Speter 1145251881Speter /* Validate the revisions. */ 1146251881Speter if (start_rev > end_rev) 1147251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1148251881Speter _("Start revision %ld" 1149251881Speter " is greater than end revision %ld"), 1150251881Speter start_rev, end_rev); 1151251881Speter if (end_rev > youngest) 1152251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1153251881Speter _("End revision %ld is invalid " 1154251881Speter "(youngest revision is %ld)"), 1155251881Speter end_rev, youngest); 1156251881Speter if ((start_rev == 0) && incremental) 1157251881Speter incremental = FALSE; /* revision 0 looks the same regardless of 1158251881Speter whether or not this is an incremental 1159251881Speter dump, so just simplify things. */ 1160251881Speter 1161251881Speter /* Write out the UUID. */ 1162251881Speter SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); 1163251881Speter 1164251881Speter /* If we're not using deltas, use the previous version, for 1165251881Speter compatibility with svn 1.0.x. */ 1166251881Speter version = SVN_REPOS_DUMPFILE_FORMAT_VERSION; 1167251881Speter if (!use_deltas) 1168251881Speter version--; 1169251881Speter 1170251881Speter /* Write out "general" metadata for the dumpfile. In this case, a 1171251881Speter magic header followed by a dumpfile format version. */ 1172251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1173251881Speter SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", 1174251881Speter version)); 1175251881Speter SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID 1176251881Speter ": %s\n\n", uuid)); 1177251881Speter 1178251881Speter /* Create a notify object that we can reuse in the loop. */ 1179251881Speter if (notify_func) 1180251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, 1181251881Speter pool); 1182251881Speter 1183251881Speter /* Main loop: we're going to dump revision i. */ 1184251881Speter for (i = start_rev; i <= end_rev; i++) 1185251881Speter { 1186251881Speter svn_revnum_t from_rev, to_rev; 1187251881Speter svn_fs_root_t *to_root; 1188251881Speter svn_boolean_t use_deltas_for_rev; 1189251881Speter 1190251881Speter svn_pool_clear(subpool); 1191251881Speter 1192251881Speter /* Check for cancellation. */ 1193251881Speter if (cancel_func) 1194251881Speter SVN_ERR(cancel_func(cancel_baton)); 1195251881Speter 1196251881Speter /* Special-case the initial revision dump: it needs to contain 1197251881Speter *all* nodes, because it's the foundation of all future 1198251881Speter revisions in the dumpfile. */ 1199251881Speter if ((i == start_rev) && (! incremental)) 1200251881Speter { 1201251881Speter /* Special-special-case a dump of revision 0. */ 1202251881Speter if (i == 0) 1203251881Speter { 1204251881Speter /* Just write out the one revision 0 record and move on. 1205251881Speter The parser might want to use its properties. */ 1206251881Speter SVN_ERR(write_revision_record(stream, fs, 0, subpool)); 1207251881Speter to_rev = 0; 1208251881Speter goto loop_end; 1209251881Speter } 1210251881Speter 1211251881Speter /* Compare START_REV to revision 0, so that everything 1212251881Speter appears to be added. */ 1213251881Speter from_rev = 0; 1214251881Speter to_rev = i; 1215251881Speter } 1216251881Speter else 1217251881Speter { 1218251881Speter /* In the normal case, we want to compare consecutive revs. */ 1219251881Speter from_rev = i - 1; 1220251881Speter to_rev = i; 1221251881Speter } 1222251881Speter 1223251881Speter /* Write the revision record. */ 1224251881Speter SVN_ERR(write_revision_record(stream, fs, to_rev, subpool)); 1225251881Speter 1226251881Speter /* Fetch the editor which dumps nodes to a file. Regardless of 1227251881Speter what we've been told, don't use deltas for the first rev of a 1228251881Speter non-incremental dump. */ 1229251881Speter use_deltas_for_rev = use_deltas && (incremental || i != start_rev); 1230251881Speter SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev, 1231251881Speter "", stream, &found_old_reference, 1232251881Speter &found_old_mergeinfo, NULL, 1233251881Speter notify_func, notify_baton, 1234251881Speter start_rev, use_deltas_for_rev, FALSE, subpool)); 1235251881Speter 1236251881Speter /* Drive the editor in one way or another. */ 1237251881Speter SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool)); 1238251881Speter 1239251881Speter /* If this is the first revision of a non-incremental dump, 1240251881Speter we're in for a full tree dump. Otherwise, we want to simply 1241251881Speter replay the revision. */ 1242251881Speter if ((i == start_rev) && (! incremental)) 1243251881Speter { 1244251881Speter svn_fs_root_t *from_root; 1245251881Speter SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool)); 1246251881Speter SVN_ERR(svn_repos_dir_delta2(from_root, "", "", 1247251881Speter to_root, "", 1248251881Speter dump_editor, dump_edit_baton, 1249251881Speter NULL, 1250251881Speter NULL, 1251251881Speter FALSE, /* don't send text-deltas */ 1252251881Speter svn_depth_infinity, 1253251881Speter FALSE, /* don't send entry props */ 1254251881Speter FALSE, /* don't ignore ancestry */ 1255251881Speter subpool)); 1256251881Speter } 1257251881Speter else 1258251881Speter { 1259251881Speter SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1260251881Speter dump_editor, dump_edit_baton, 1261251881Speter NULL, NULL, subpool)); 1262251881Speter 1263251881Speter /* While our editor close_edit implementation is a no-op, we still 1264251881Speter do this for completeness. */ 1265251881Speter SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool)); 1266251881Speter } 1267251881Speter 1268251881Speter loop_end: 1269251881Speter if (notify_func) 1270251881Speter { 1271251881Speter notify->revision = to_rev; 1272251881Speter notify_func(notify_baton, notify, subpool); 1273251881Speter } 1274251881Speter } 1275251881Speter 1276251881Speter if (notify_func) 1277251881Speter { 1278251881Speter /* Did we issue any warnings about references to revisions older than 1279251881Speter the oldest dumped revision? If so, then issue a final generic 1280251881Speter warning, since the inline warnings already issued might easily be 1281251881Speter missed. */ 1282251881Speter 1283251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool); 1284251881Speter notify_func(notify_baton, notify, subpool); 1285251881Speter 1286251881Speter if (found_old_reference) 1287251881Speter { 1288251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1289251881Speter 1290251881Speter notify->warning = svn_repos_notify_warning_found_old_reference; 1291251881Speter notify->warning_str = _("The range of revisions dumped " 1292251881Speter "contained references to " 1293251881Speter "copy sources outside that " 1294251881Speter "range."); 1295251881Speter notify_func(notify_baton, notify, subpool); 1296251881Speter } 1297251881Speter 1298251881Speter /* Ditto if we issued any warnings about old revisions referenced 1299251881Speter in dumped mergeinfo. */ 1300251881Speter if (found_old_mergeinfo) 1301251881Speter { 1302251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1303251881Speter 1304251881Speter notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 1305251881Speter notify->warning_str = _("The range of revisions dumped " 1306251881Speter "contained mergeinfo " 1307251881Speter "which reference revisions outside " 1308251881Speter "that range."); 1309251881Speter notify_func(notify_baton, notify, subpool); 1310251881Speter } 1311251881Speter } 1312251881Speter 1313251881Speter svn_pool_destroy(subpool); 1314251881Speter 1315251881Speter return SVN_NO_ERROR; 1316251881Speter} 1317251881Speter 1318251881Speter 1319251881Speter/*----------------------------------------------------------------------*/ 1320251881Speter 1321251881Speter/* verify, based on dump */ 1322251881Speter 1323251881Speter 1324251881Speter/* Creating a new revision that changes /A/B/E/bravo means creating new 1325251881Speter directory listings for /, /A, /A/B, and /A/B/E in the new revision, with 1326251881Speter each entry not changed in the new revision a link back to the entry in a 1327251881Speter previous revision. svn_repos_replay()ing a revision does not verify that 1328251881Speter those links are correct. 1329251881Speter 1330251881Speter For paths actually changed in the revision we verify, we get directory 1331251881Speter contents or file length twice: once in the dump editor, and once here. 1332251881Speter We could create a new verify baton, store in it the changed paths, and 1333251881Speter skip those here, but that means building an entire wrapper editor and 1334251881Speter managing two levels of batons. The impact from checking these entries 1335251881Speter twice should be minimal, while the code to avoid it is not. 1336251881Speter*/ 1337251881Speter 1338251881Speterstatic svn_error_t * 1339251881Speterverify_directory_entry(void *baton, const void *key, apr_ssize_t klen, 1340251881Speter void *val, apr_pool_t *pool) 1341251881Speter{ 1342251881Speter struct dir_baton *db = baton; 1343251881Speter svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; 1344251881Speter char *path = svn_relpath_join(db->path, (const char *)key, pool); 1345251881Speter apr_hash_t *dirents; 1346251881Speter svn_filesize_t len; 1347251881Speter 1348251881Speter /* since we can't access the directory entries directly by their ID, 1349251881Speter we need to navigate from the FS_ROOT to them (relatively expensive 1350251881Speter because we may start at a never rev than the last change to node). */ 1351251881Speter switch (dirent->kind) { 1352251881Speter case svn_node_dir: 1353251881Speter /* Getting this directory's contents is enough to ensure that our 1354251881Speter link to it is correct. */ 1355251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool)); 1356251881Speter break; 1357251881Speter case svn_node_file: 1358251881Speter /* Getting this file's size is enough to ensure that our link to it 1359251881Speter is correct. */ 1360251881Speter SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool)); 1361251881Speter break; 1362251881Speter default: 1363251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1364251881Speter _("Unexpected node kind %d for '%s'"), 1365251881Speter dirent->kind, path); 1366251881Speter } 1367251881Speter 1368251881Speter return SVN_NO_ERROR; 1369251881Speter} 1370251881Speter 1371251881Speterstatic svn_error_t * 1372251881Speterverify_close_directory(void *dir_baton, 1373251881Speter apr_pool_t *pool) 1374251881Speter{ 1375251881Speter struct dir_baton *db = dir_baton; 1376251881Speter apr_hash_t *dirents; 1377251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, 1378251881Speter db->path, pool)); 1379251881Speter SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, 1380251881Speter dir_baton, pool)); 1381251881Speter return close_directory(dir_baton, pool); 1382251881Speter} 1383251881Speter 1384251881Speter/* Baton type used for forwarding notifications from FS API to REPOS API. */ 1385251881Speterstruct verify_fs2_notify_func_baton_t 1386251881Speter{ 1387251881Speter /* notification function to call (must not be NULL) */ 1388251881Speter svn_repos_notify_func_t notify_func; 1389251881Speter 1390251881Speter /* baton to use for it */ 1391251881Speter void *notify_baton; 1392251881Speter 1393251881Speter /* type of notification to send (we will simply plug in the revision) */ 1394251881Speter svn_repos_notify_t *notify; 1395251881Speter}; 1396251881Speter 1397251881Speter/* Forward the notification to BATON. */ 1398251881Speterstatic void 1399251881Speterverify_fs2_notify_func(svn_revnum_t revision, 1400251881Speter void *baton, 1401251881Speter apr_pool_t *pool) 1402251881Speter{ 1403251881Speter struct verify_fs2_notify_func_baton_t *notify_baton = baton; 1404251881Speter 1405251881Speter notify_baton->notify->revision = revision; 1406251881Speter notify_baton->notify_func(notify_baton->notify_baton, 1407251881Speter notify_baton->notify, pool); 1408251881Speter} 1409251881Speter 1410251881Spetersvn_error_t * 1411251881Spetersvn_repos_verify_fs2(svn_repos_t *repos, 1412251881Speter svn_revnum_t start_rev, 1413251881Speter svn_revnum_t end_rev, 1414251881Speter svn_repos_notify_func_t notify_func, 1415251881Speter void *notify_baton, 1416251881Speter svn_cancel_func_t cancel_func, 1417251881Speter void *cancel_baton, 1418251881Speter apr_pool_t *pool) 1419251881Speter{ 1420251881Speter svn_fs_t *fs = svn_repos_fs(repos); 1421251881Speter svn_revnum_t youngest; 1422251881Speter svn_revnum_t rev; 1423251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1424251881Speter svn_repos_notify_t *notify; 1425251881Speter svn_fs_progress_notify_func_t verify_notify = NULL; 1426251881Speter struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL; 1427251881Speter 1428251881Speter /* Determine the current youngest revision of the filesystem. */ 1429251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1430251881Speter 1431251881Speter /* Use default vals if necessary. */ 1432251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1433251881Speter start_rev = 0; 1434251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1435251881Speter end_rev = youngest; 1436251881Speter 1437251881Speter /* Validate the revisions. */ 1438251881Speter if (start_rev > end_rev) 1439251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1440251881Speter _("Start revision %ld" 1441251881Speter " is greater than end revision %ld"), 1442251881Speter start_rev, end_rev); 1443251881Speter if (end_rev > youngest) 1444251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1445251881Speter _("End revision %ld is invalid " 1446251881Speter "(youngest revision is %ld)"), 1447251881Speter end_rev, youngest); 1448251881Speter 1449251881Speter /* Create a notify object that we can reuse within the loop and a 1450251881Speter forwarding structure for notifications from inside svn_fs_verify(). */ 1451251881Speter if (notify_func) 1452251881Speter { 1453251881Speter notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, 1454251881Speter pool); 1455251881Speter 1456251881Speter verify_notify = verify_fs2_notify_func; 1457251881Speter verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); 1458251881Speter verify_notify_baton->notify_func = notify_func; 1459251881Speter verify_notify_baton->notify_baton = notify_baton; 1460251881Speter verify_notify_baton->notify 1461251881Speter = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); 1462251881Speter } 1463251881Speter 1464251881Speter /* Verify global metadata and backend-specific data first. */ 1465251881Speter SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), 1466251881Speter start_rev, end_rev, 1467251881Speter verify_notify, verify_notify_baton, 1468251881Speter cancel_func, cancel_baton, pool)); 1469251881Speter 1470251881Speter for (rev = start_rev; rev <= end_rev; rev++) 1471251881Speter { 1472251881Speter const svn_delta_editor_t *dump_editor; 1473251881Speter void *dump_edit_baton; 1474251881Speter const svn_delta_editor_t *cancel_editor; 1475251881Speter void *cancel_edit_baton; 1476251881Speter svn_fs_root_t *to_root; 1477251881Speter apr_hash_t *props; 1478251881Speter 1479251881Speter svn_pool_clear(iterpool); 1480251881Speter 1481251881Speter /* Get cancellable dump editor, but with our close_directory handler. */ 1482251881Speter SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, 1483251881Speter fs, rev, "", 1484251881Speter svn_stream_empty(iterpool), 1485251881Speter NULL, NULL, 1486251881Speter verify_close_directory, 1487251881Speter notify_func, notify_baton, 1488251881Speter start_rev, 1489251881Speter FALSE, TRUE, /* use_deltas, verify */ 1490251881Speter iterpool)); 1491251881Speter SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1492251881Speter dump_editor, dump_edit_baton, 1493251881Speter &cancel_editor, 1494251881Speter &cancel_edit_baton, 1495251881Speter iterpool)); 1496251881Speter 1497251881Speter SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); 1498251881Speter SVN_ERR(svn_fs_verify_root(to_root, iterpool)); 1499251881Speter 1500251881Speter SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1501251881Speter cancel_editor, cancel_edit_baton, 1502251881Speter NULL, NULL, iterpool)); 1503251881Speter /* While our editor close_edit implementation is a no-op, we still 1504251881Speter do this for completeness. */ 1505251881Speter SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool)); 1506251881Speter 1507251881Speter SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool)); 1508251881Speter 1509251881Speter if (notify_func) 1510251881Speter { 1511251881Speter notify->revision = rev; 1512251881Speter notify_func(notify_baton, notify, iterpool); 1513251881Speter } 1514251881Speter } 1515251881Speter 1516251881Speter /* We're done. */ 1517251881Speter if (notify_func) 1518251881Speter { 1519251881Speter notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool); 1520251881Speter notify_func(notify_baton, notify, iterpool); 1521251881Speter } 1522251881Speter 1523251881Speter /* Per-backend verification. */ 1524251881Speter svn_pool_destroy(iterpool); 1525251881Speter 1526251881Speter return SVN_NO_ERROR; 1527251881Speter} 1528