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 220251881Speter/* This helper is the main "meat" of the editor -- it does all the 221251881Speter work of writing a node record. 222251881Speter 223251881Speter Write out a node record for PATH of type KIND under EB->FS_ROOT. 224251881Speter ACTION describes what is happening to the node (see enum svn_node_action). 225251881Speter Write record to writable EB->STREAM, using EB->BUFFER to write in chunks. 226251881Speter 227251881Speter If the node was itself copied, IS_COPY is TRUE and the 228251881Speter path/revision of the copy source are in CMP_PATH/CMP_REV. If 229251881Speter IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part 230251881Speter of a copied subtree. 231251881Speter */ 232251881Speterstatic svn_error_t * 233251881Speterdump_node(struct edit_baton *eb, 234251881Speter const char *path, 235251881Speter svn_node_kind_t kind, 236251881Speter enum svn_node_action action, 237251881Speter svn_boolean_t is_copy, 238251881Speter const char *cmp_path, 239251881Speter svn_revnum_t cmp_rev, 240251881Speter apr_pool_t *pool) 241251881Speter{ 242251881Speter svn_stringbuf_t *propstring; 243251881Speter svn_filesize_t content_length = 0; 244251881Speter apr_size_t len; 245251881Speter svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; 246251881Speter const char *compare_path = path; 247251881Speter svn_revnum_t compare_rev = eb->current_rev - 1; 248251881Speter svn_fs_root_t *compare_root = NULL; 249251881Speter apr_file_t *delta_file = NULL; 250251881Speter 251251881Speter /* Maybe validate the path. */ 252251881Speter if (eb->verify || eb->notify_func) 253251881Speter { 254251881Speter svn_error_t *err = svn_fs__path_valid(path, pool); 255251881Speter 256251881Speter if (err) 257251881Speter { 258251881Speter if (eb->notify_func) 259251881Speter { 260251881Speter char errbuf[512]; /* ### svn_strerror() magic number */ 261251881Speter svn_repos_notify_t *notify; 262251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, pool); 263251881Speter 264251881Speter notify->warning = svn_repos_notify_warning_invalid_fspath; 265251881Speter notify->warning_str = apr_psprintf( 266251881Speter pool, 267251881Speter _("E%06d: While validating fspath '%s': %s"), 268251881Speter err->apr_err, path, 269251881Speter svn_err_best_message(err, errbuf, sizeof(errbuf))); 270251881Speter 271251881Speter eb->notify_func(eb->notify_baton, notify, pool); 272251881Speter } 273251881Speter 274251881Speter /* Return the error in addition to notifying about it. */ 275251881Speter if (eb->verify) 276251881Speter return svn_error_trace(err); 277251881Speter else 278251881Speter svn_error_clear(err); 279251881Speter } 280251881Speter } 281251881Speter 282251881Speter /* Write out metadata headers for this file node. */ 283251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 284251881Speter SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", 285251881Speter path)); 286251881Speter if (kind == svn_node_file) 287251881Speter SVN_ERR(svn_stream_puts(eb->stream, 288251881Speter SVN_REPOS_DUMPFILE_NODE_KIND ": file\n")); 289251881Speter else if (kind == svn_node_dir) 290251881Speter SVN_ERR(svn_stream_puts(eb->stream, 291251881Speter SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n")); 292251881Speter 293251881Speter /* Remove leading slashes from copyfrom paths. */ 294251881Speter if (cmp_path) 295251881Speter cmp_path = svn_relpath_canonicalize(cmp_path, pool); 296251881Speter 297251881Speter /* Validate the comparison path/rev. */ 298251881Speter if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) 299251881Speter { 300251881Speter compare_path = cmp_path; 301251881Speter compare_rev = cmp_rev; 302251881Speter } 303251881Speter 304251881Speter if (action == svn_node_action_change) 305251881Speter { 306251881Speter SVN_ERR(svn_stream_puts(eb->stream, 307251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n")); 308251881Speter 309251881Speter /* either the text or props changed, or possibly both. */ 310251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 311251881Speter svn_fs_root_fs(eb->fs_root), 312251881Speter compare_rev, pool)); 313251881Speter 314251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 315251881Speter compare_root, compare_path, 316251881Speter eb->fs_root, path, pool)); 317251881Speter if (kind == svn_node_file) 318251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 319251881Speter compare_root, compare_path, 320251881Speter eb->fs_root, path, pool)); 321251881Speter } 322251881Speter else if (action == svn_node_action_replace) 323251881Speter { 324251881Speter if (! is_copy) 325251881Speter { 326251881Speter /* a simple delete+add, implied by a single 'replace' action. */ 327251881Speter SVN_ERR(svn_stream_puts(eb->stream, 328251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION 329251881Speter ": replace\n")); 330251881Speter 331251881Speter /* definitely need to dump all content for a replace. */ 332251881Speter if (kind == svn_node_file) 333251881Speter must_dump_text = TRUE; 334251881Speter must_dump_props = TRUE; 335251881Speter } 336251881Speter else 337251881Speter { 338251881Speter /* more complex: delete original, then add-with-history. */ 339251881Speter 340251881Speter /* the path & kind headers have already been printed; just 341251881Speter add a delete action, and end the current record.*/ 342251881Speter SVN_ERR(svn_stream_puts(eb->stream, 343251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION 344251881Speter ": delete\n\n")); 345251881Speter 346251881Speter /* recurse: print an additional add-with-history record. */ 347251881Speter SVN_ERR(dump_node(eb, path, kind, svn_node_action_add, 348251881Speter is_copy, compare_path, compare_rev, pool)); 349251881Speter 350251881Speter /* we can leave this routine quietly now, don't need to dump 351251881Speter any content; that was already done in the second record. */ 352251881Speter must_dump_text = FALSE; 353251881Speter must_dump_props = FALSE; 354251881Speter } 355251881Speter } 356251881Speter else if (action == svn_node_action_delete) 357251881Speter { 358251881Speter SVN_ERR(svn_stream_puts(eb->stream, 359251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n")); 360251881Speter 361251881Speter /* we can leave this routine quietly now, don't need to dump 362251881Speter any content. */ 363251881Speter must_dump_text = FALSE; 364251881Speter must_dump_props = FALSE; 365251881Speter } 366251881Speter else if (action == svn_node_action_add) 367251881Speter { 368251881Speter SVN_ERR(svn_stream_puts(eb->stream, 369251881Speter SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n")); 370251881Speter 371251881Speter if (! is_copy) 372251881Speter { 373251881Speter /* Dump all contents for a simple 'add'. */ 374251881Speter if (kind == svn_node_file) 375251881Speter must_dump_text = TRUE; 376251881Speter must_dump_props = TRUE; 377251881Speter } 378251881Speter else 379251881Speter { 380251881Speter if (!eb->verify && cmp_rev < eb->oldest_dumped_rev 381251881Speter && eb->notify_func) 382251881Speter { 383251881Speter svn_repos_notify_t *notify = 384251881Speter svn_repos_notify_create(svn_repos_notify_warning, pool); 385251881Speter 386251881Speter notify->warning = svn_repos_notify_warning_found_old_reference; 387251881Speter notify->warning_str = apr_psprintf( 388251881Speter pool, 389251881Speter _("Referencing data in revision %ld," 390251881Speter " which is older than the oldest" 391251881Speter " dumped revision (r%ld). Loading this dump" 392251881Speter " into an empty repository" 393251881Speter " will fail."), 394251881Speter cmp_rev, eb->oldest_dumped_rev); 395251881Speter if (eb->found_old_reference) 396251881Speter *eb->found_old_reference = TRUE; 397251881Speter eb->notify_func(eb->notify_baton, notify, pool); 398251881Speter } 399251881Speter 400251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 401251881Speter SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV 402251881Speter ": %ld\n" 403251881Speter SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH 404251881Speter ": %s\n", 405251881Speter cmp_rev, cmp_path)); 406251881Speter 407251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 408251881Speter svn_fs_root_fs(eb->fs_root), 409251881Speter compare_rev, pool)); 410251881Speter 411251881Speter /* Need to decide if the copied node had any extra textual or 412251881Speter property mods as well. */ 413251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 414251881Speter compare_root, compare_path, 415251881Speter eb->fs_root, path, pool)); 416251881Speter if (kind == svn_node_file) 417251881Speter { 418251881Speter svn_checksum_t *checksum; 419251881Speter const char *hex_digest; 420251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 421251881Speter compare_root, compare_path, 422251881Speter eb->fs_root, path, pool)); 423251881Speter 424251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 425251881Speter compare_root, compare_path, 426251881Speter FALSE, pool)); 427251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 428251881Speter if (hex_digest) 429251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 430251881Speter SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5 431251881Speter ": %s\n", hex_digest)); 432251881Speter 433251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 434251881Speter compare_root, compare_path, 435251881Speter FALSE, pool)); 436251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 437251881Speter if (hex_digest) 438251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 439251881Speter SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1 440251881Speter ": %s\n", hex_digest)); 441251881Speter } 442251881Speter } 443251881Speter } 444251881Speter 445251881Speter if ((! must_dump_text) && (! must_dump_props)) 446251881Speter { 447251881Speter /* If we're not supposed to dump text or props, so be it, we can 448251881Speter just go home. However, if either one needs to be dumped, 449251881Speter then our dumpstream format demands that at a *minimum*, we 450251881Speter see a lone "PROPS-END" as a divider between text and props 451251881Speter content within the content-block. */ 452251881Speter len = 2; 453251881Speter return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 454251881Speter } 455251881Speter 456251881Speter /*** Start prepping content to dump... ***/ 457251881Speter 458251881Speter /* If we are supposed to dump properties, write out a property 459251881Speter length header and generate a stringbuf that contains those 460251881Speter property values here. */ 461251881Speter if (must_dump_props) 462251881Speter { 463251881Speter apr_hash_t *prophash, *oldhash = NULL; 464251881Speter apr_size_t proplen; 465251881Speter svn_stream_t *propstream; 466251881Speter 467251881Speter SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); 468251881Speter 469251881Speter /* If this is a partial dump, then issue a warning if we dump mergeinfo 470251881Speter properties that refer to revisions older than the first revision 471251881Speter dumped. */ 472251881Speter if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) 473251881Speter { 474251881Speter svn_string_t *mergeinfo_str = svn_hash_gets(prophash, 475251881Speter SVN_PROP_MERGEINFO); 476251881Speter if (mergeinfo_str) 477251881Speter { 478251881Speter svn_mergeinfo_t mergeinfo, old_mergeinfo; 479251881Speter 480251881Speter SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data, 481251881Speter pool)); 482251881Speter SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 483251881Speter &old_mergeinfo, mergeinfo, 484251881Speter eb->oldest_dumped_rev - 1, 0, 485251881Speter TRUE, pool, pool)); 486251881Speter if (apr_hash_count(old_mergeinfo)) 487251881Speter { 488251881Speter svn_repos_notify_t *notify = 489251881Speter svn_repos_notify_create(svn_repos_notify_warning, pool); 490251881Speter 491251881Speter notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 492251881Speter notify->warning_str = apr_psprintf( 493251881Speter pool, 494251881Speter _("Mergeinfo referencing revision(s) prior " 495251881Speter "to the oldest dumped revision (r%ld). " 496251881Speter "Loading this dump may result in invalid " 497251881Speter "mergeinfo."), 498251881Speter eb->oldest_dumped_rev); 499251881Speter 500251881Speter if (eb->found_old_mergeinfo) 501251881Speter *eb->found_old_mergeinfo = TRUE; 502251881Speter eb->notify_func(eb->notify_baton, notify, pool); 503251881Speter } 504251881Speter } 505251881Speter } 506251881Speter 507251881Speter if (eb->use_deltas && compare_root) 508251881Speter { 509251881Speter /* Fetch the old property hash to diff against and output a header 510251881Speter saying that our property contents are a delta. */ 511251881Speter SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, 512251881Speter pool)); 513251881Speter SVN_ERR(svn_stream_puts(eb->stream, 514251881Speter SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n")); 515251881Speter } 516251881Speter else 517251881Speter oldhash = apr_hash_make(pool); 518251881Speter propstring = svn_stringbuf_create_ensure(0, pool); 519251881Speter propstream = svn_stream_from_stringbuf(propstring, pool); 520251881Speter SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, 521251881Speter "PROPS-END", pool)); 522251881Speter SVN_ERR(svn_stream_close(propstream)); 523251881Speter proplen = propstring->len; 524251881Speter content_length += proplen; 525251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 526251881Speter SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 527251881Speter ": %" APR_SIZE_T_FMT "\n", proplen)); 528251881Speter } 529251881Speter 530251881Speter /* If we are supposed to dump text, write out a text length header 531251881Speter here, and an MD5 checksum (if available). */ 532251881Speter if (must_dump_text && (kind == svn_node_file)) 533251881Speter { 534251881Speter svn_checksum_t *checksum; 535251881Speter const char *hex_digest; 536251881Speter svn_filesize_t textlen; 537251881Speter 538251881Speter if (eb->use_deltas) 539251881Speter { 540251881Speter /* Compute the text delta now and write it into a temporary 541251881Speter file, so that we can find its length. Output a header 542251881Speter saying our text contents are a delta. */ 543251881Speter SVN_ERR(store_delta(&delta_file, &textlen, compare_root, 544251881Speter compare_path, eb->fs_root, path, pool)); 545251881Speter SVN_ERR(svn_stream_puts(eb->stream, 546251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n")); 547251881Speter 548251881Speter if (compare_root) 549251881Speter { 550251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 551251881Speter compare_root, compare_path, 552251881Speter FALSE, pool)); 553251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 554251881Speter if (hex_digest) 555251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 556251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5 557251881Speter ": %s\n", hex_digest)); 558251881Speter 559251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 560251881Speter compare_root, compare_path, 561251881Speter FALSE, pool)); 562251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 563251881Speter if (hex_digest) 564251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 565251881Speter SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1 566251881Speter ": %s\n", hex_digest)); 567251881Speter } 568251881Speter } 569251881Speter else 570251881Speter { 571251881Speter /* Just fetch the length of the file. */ 572251881Speter SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); 573251881Speter } 574251881Speter 575251881Speter content_length += textlen; 576251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 577251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH 578251881Speter ": %" SVN_FILESIZE_T_FMT "\n", textlen)); 579251881Speter 580251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 581251881Speter eb->fs_root, path, FALSE, pool)); 582251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 583251881Speter if (hex_digest) 584251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 585251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5 586251881Speter ": %s\n", hex_digest)); 587251881Speter 588251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 589251881Speter eb->fs_root, path, FALSE, pool)); 590251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 591251881Speter if (hex_digest) 592251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 593251881Speter SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1 594251881Speter ": %s\n", hex_digest)); 595251881Speter } 596251881Speter 597251881Speter /* 'Content-length:' is the last header before we dump the content, 598251881Speter and is the sum of the text and prop contents lengths. We write 599251881Speter this only for the benefit of non-Subversion RFC-822 parsers. */ 600251881Speter SVN_ERR(svn_stream_printf(eb->stream, pool, 601251881Speter SVN_REPOS_DUMPFILE_CONTENT_LENGTH 602251881Speter ": %" SVN_FILESIZE_T_FMT "\n\n", 603251881Speter content_length)); 604251881Speter 605251881Speter /* Dump property content if we're supposed to do so. */ 606251881Speter if (must_dump_props) 607251881Speter { 608251881Speter len = propstring->len; 609251881Speter SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len)); 610251881Speter } 611251881Speter 612251881Speter /* Dump text content */ 613251881Speter if (must_dump_text && (kind == svn_node_file)) 614251881Speter { 615251881Speter svn_stream_t *contents; 616251881Speter 617251881Speter if (delta_file) 618251881Speter { 619251881Speter /* Make sure to close the underlying file when the stream is 620251881Speter closed. */ 621251881Speter contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); 622251881Speter } 623251881Speter else 624251881Speter SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); 625251881Speter 626251881Speter SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), 627251881Speter NULL, NULL, pool)); 628251881Speter } 629251881Speter 630251881Speter len = 2; 631251881Speter return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 632251881Speter} 633251881Speter 634251881Speter 635251881Speterstatic svn_error_t * 636251881Speteropen_root(void *edit_baton, 637251881Speter svn_revnum_t base_revision, 638251881Speter apr_pool_t *pool, 639251881Speter void **root_baton) 640251881Speter{ 641251881Speter *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 642251881Speter edit_baton, NULL, FALSE, pool); 643251881Speter return SVN_NO_ERROR; 644251881Speter} 645251881Speter 646251881Speter 647251881Speterstatic svn_error_t * 648251881Speterdelete_entry(const char *path, 649251881Speter svn_revnum_t revision, 650251881Speter void *parent_baton, 651251881Speter apr_pool_t *pool) 652251881Speter{ 653251881Speter struct dir_baton *pb = parent_baton; 654251881Speter const char *mypath = apr_pstrdup(pb->pool, path); 655251881Speter 656251881Speter /* remember this path needs to be deleted. */ 657251881Speter svn_hash_sets(pb->deleted_entries, mypath, pb); 658251881Speter 659251881Speter return SVN_NO_ERROR; 660251881Speter} 661251881Speter 662251881Speter 663251881Speterstatic svn_error_t * 664251881Speteradd_directory(const char *path, 665251881Speter void *parent_baton, 666251881Speter const char *copyfrom_path, 667251881Speter svn_revnum_t copyfrom_rev, 668251881Speter apr_pool_t *pool, 669251881Speter void **child_baton) 670251881Speter{ 671251881Speter struct dir_baton *pb = parent_baton; 672251881Speter struct edit_baton *eb = pb->edit_baton; 673251881Speter void *val; 674251881Speter svn_boolean_t is_copy = FALSE; 675251881Speter struct dir_baton *new_db 676251881Speter = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool); 677251881Speter 678251881Speter /* This might be a replacement -- is the path already deleted? */ 679251881Speter val = svn_hash_gets(pb->deleted_entries, path); 680251881Speter 681251881Speter /* Detect an add-with-history. */ 682251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 683251881Speter 684251881Speter /* Dump the node. */ 685251881Speter SVN_ERR(dump_node(eb, path, 686251881Speter svn_node_dir, 687251881Speter val ? svn_node_action_replace : svn_node_action_add, 688251881Speter is_copy, 689251881Speter is_copy ? copyfrom_path : NULL, 690251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 691251881Speter pool)); 692251881Speter 693251881Speter if (val) 694251881Speter /* Delete the path, it's now been dumped. */ 695251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 696251881Speter 697251881Speter new_db->written_out = TRUE; 698251881Speter 699251881Speter *child_baton = new_db; 700251881Speter return SVN_NO_ERROR; 701251881Speter} 702251881Speter 703251881Speter 704251881Speterstatic svn_error_t * 705251881Speteropen_directory(const char *path, 706251881Speter void *parent_baton, 707251881Speter svn_revnum_t base_revision, 708251881Speter apr_pool_t *pool, 709251881Speter void **child_baton) 710251881Speter{ 711251881Speter struct dir_baton *pb = parent_baton; 712251881Speter struct edit_baton *eb = pb->edit_baton; 713251881Speter struct dir_baton *new_db; 714251881Speter const char *cmp_path = NULL; 715251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 716251881Speter 717251881Speter /* If the parent directory has explicit comparison path and rev, 718251881Speter record the same for this one. */ 719251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 720251881Speter { 721251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 722251881Speter svn_relpath_basename(path, pool), pool); 723251881Speter cmp_rev = pb->cmp_rev; 724251881Speter } 725251881Speter 726251881Speter new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool); 727251881Speter *child_baton = new_db; 728251881Speter return SVN_NO_ERROR; 729251881Speter} 730251881Speter 731251881Speter 732251881Speterstatic svn_error_t * 733251881Speterclose_directory(void *dir_baton, 734251881Speter apr_pool_t *pool) 735251881Speter{ 736251881Speter struct dir_baton *db = dir_baton; 737251881Speter struct edit_baton *eb = db->edit_baton; 738251881Speter apr_pool_t *subpool = svn_pool_create(pool); 739251881Speter int i; 740251881Speter apr_array_header_t *sorted_entries; 741251881Speter 742251881Speter /* Sort entries lexically instead of as paths. Even though the entries 743251881Speter * are full paths they're all in the same directory (see comment in struct 744251881Speter * dir_baton definition). So we really want to sort by basename, in which 745251881Speter * case the lexical sort function is more efficient. */ 746251881Speter sorted_entries = svn_sort__hash(db->deleted_entries, 747251881Speter svn_sort_compare_items_lexically, pool); 748251881Speter for (i = 0; i < sorted_entries->nelts; i++) 749251881Speter { 750251881Speter const char *path = APR_ARRAY_IDX(sorted_entries, i, 751251881Speter svn_sort__item_t).key; 752251881Speter 753251881Speter svn_pool_clear(subpool); 754251881Speter 755251881Speter /* By sending 'svn_node_unknown', the Node-kind: header simply won't 756251881Speter be written out. No big deal at all, really. The loader 757251881Speter shouldn't care. */ 758251881Speter SVN_ERR(dump_node(eb, path, 759251881Speter svn_node_unknown, svn_node_action_delete, 760251881Speter FALSE, NULL, SVN_INVALID_REVNUM, subpool)); 761251881Speter } 762251881Speter 763251881Speter svn_pool_destroy(subpool); 764251881Speter return SVN_NO_ERROR; 765251881Speter} 766251881Speter 767251881Speter 768251881Speterstatic svn_error_t * 769251881Speteradd_file(const char *path, 770251881Speter void *parent_baton, 771251881Speter const char *copyfrom_path, 772251881Speter svn_revnum_t copyfrom_rev, 773251881Speter apr_pool_t *pool, 774251881Speter void **file_baton) 775251881Speter{ 776251881Speter struct dir_baton *pb = parent_baton; 777251881Speter struct edit_baton *eb = pb->edit_baton; 778251881Speter void *val; 779251881Speter svn_boolean_t is_copy = FALSE; 780251881Speter 781251881Speter /* This might be a replacement -- is the path already deleted? */ 782251881Speter val = svn_hash_gets(pb->deleted_entries, path); 783251881Speter 784251881Speter /* Detect add-with-history. */ 785251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 786251881Speter 787251881Speter /* Dump the node. */ 788251881Speter SVN_ERR(dump_node(eb, path, 789251881Speter svn_node_file, 790251881Speter val ? svn_node_action_replace : svn_node_action_add, 791251881Speter is_copy, 792251881Speter is_copy ? copyfrom_path : NULL, 793251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 794251881Speter pool)); 795251881Speter 796251881Speter if (val) 797251881Speter /* delete the path, it's now been dumped. */ 798251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 799251881Speter 800251881Speter *file_baton = NULL; /* muhahahaha */ 801251881Speter return SVN_NO_ERROR; 802251881Speter} 803251881Speter 804251881Speter 805251881Speterstatic svn_error_t * 806251881Speteropen_file(const char *path, 807251881Speter void *parent_baton, 808251881Speter svn_revnum_t ancestor_revision, 809251881Speter apr_pool_t *pool, 810251881Speter void **file_baton) 811251881Speter{ 812251881Speter struct dir_baton *pb = parent_baton; 813251881Speter struct edit_baton *eb = pb->edit_baton; 814251881Speter const char *cmp_path = NULL; 815251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 816251881Speter 817251881Speter /* If the parent directory has explicit comparison path and rev, 818251881Speter record the same for this one. */ 819251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 820251881Speter { 821251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 822251881Speter svn_relpath_basename(path, pool), pool); 823251881Speter cmp_rev = pb->cmp_rev; 824251881Speter } 825251881Speter 826251881Speter SVN_ERR(dump_node(eb, path, 827251881Speter svn_node_file, svn_node_action_change, 828251881Speter FALSE, cmp_path, cmp_rev, pool)); 829251881Speter 830251881Speter *file_baton = NULL; /* muhahahaha again */ 831251881Speter return SVN_NO_ERROR; 832251881Speter} 833251881Speter 834251881Speter 835251881Speterstatic svn_error_t * 836251881Speterchange_dir_prop(void *parent_baton, 837251881Speter const char *name, 838251881Speter const svn_string_t *value, 839251881Speter apr_pool_t *pool) 840251881Speter{ 841251881Speter struct dir_baton *db = parent_baton; 842251881Speter struct edit_baton *eb = db->edit_baton; 843251881Speter 844251881Speter /* This function is what distinguishes between a directory that is 845251881Speter opened to merely get somewhere, vs. one that is opened because it 846251881Speter *actually* changed by itself. */ 847251881Speter if (! db->written_out) 848251881Speter { 849251881Speter SVN_ERR(dump_node(eb, db->path, 850251881Speter svn_node_dir, svn_node_action_change, 851251881Speter FALSE, db->cmp_path, db->cmp_rev, pool)); 852251881Speter db->written_out = TRUE; 853251881Speter } 854251881Speter return SVN_NO_ERROR; 855251881Speter} 856251881Speter 857251881Speterstatic svn_error_t * 858251881Speterfetch_props_func(apr_hash_t **props, 859251881Speter void *baton, 860251881Speter const char *path, 861251881Speter svn_revnum_t base_revision, 862251881Speter apr_pool_t *result_pool, 863251881Speter apr_pool_t *scratch_pool) 864251881Speter{ 865251881Speter struct edit_baton *eb = baton; 866251881Speter svn_error_t *err; 867251881Speter svn_fs_root_t *fs_root; 868251881Speter 869251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 870251881Speter base_revision = eb->current_rev - 1; 871251881Speter 872251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 873251881Speter 874251881Speter err = svn_fs_node_proplist(props, fs_root, path, result_pool); 875251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 876251881Speter { 877251881Speter svn_error_clear(err); 878251881Speter *props = apr_hash_make(result_pool); 879251881Speter return SVN_NO_ERROR; 880251881Speter } 881251881Speter else if (err) 882251881Speter return svn_error_trace(err); 883251881Speter 884251881Speter return SVN_NO_ERROR; 885251881Speter} 886251881Speter 887251881Speterstatic svn_error_t * 888251881Speterfetch_kind_func(svn_node_kind_t *kind, 889251881Speter void *baton, 890251881Speter const char *path, 891251881Speter svn_revnum_t base_revision, 892251881Speter apr_pool_t *scratch_pool) 893251881Speter{ 894251881Speter struct edit_baton *eb = baton; 895251881Speter svn_fs_root_t *fs_root; 896251881Speter 897251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 898251881Speter base_revision = eb->current_rev - 1; 899251881Speter 900251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 901251881Speter 902251881Speter SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 903251881Speter 904251881Speter return SVN_NO_ERROR; 905251881Speter} 906251881Speter 907251881Speterstatic svn_error_t * 908251881Speterfetch_base_func(const char **filename, 909251881Speter void *baton, 910251881Speter const char *path, 911251881Speter svn_revnum_t base_revision, 912251881Speter apr_pool_t *result_pool, 913251881Speter apr_pool_t *scratch_pool) 914251881Speter{ 915251881Speter struct edit_baton *eb = baton; 916251881Speter svn_stream_t *contents; 917251881Speter svn_stream_t *file_stream; 918251881Speter const char *tmp_filename; 919251881Speter svn_error_t *err; 920251881Speter svn_fs_root_t *fs_root; 921251881Speter 922251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 923251881Speter base_revision = eb->current_rev - 1; 924251881Speter 925251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 926251881Speter 927251881Speter err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 928251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 929251881Speter { 930251881Speter svn_error_clear(err); 931251881Speter *filename = NULL; 932251881Speter return SVN_NO_ERROR; 933251881Speter } 934251881Speter else if (err) 935251881Speter return svn_error_trace(err); 936251881Speter SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 937251881Speter svn_io_file_del_on_pool_cleanup, 938251881Speter scratch_pool, scratch_pool)); 939251881Speter SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 940251881Speter 941251881Speter *filename = apr_pstrdup(result_pool, tmp_filename); 942251881Speter 943251881Speter return SVN_NO_ERROR; 944251881Speter} 945251881Speter 946251881Speter 947251881Speterstatic svn_error_t * 948251881Speterget_dump_editor(const svn_delta_editor_t **editor, 949251881Speter void **edit_baton, 950251881Speter svn_fs_t *fs, 951251881Speter svn_revnum_t to_rev, 952251881Speter const char *root_path, 953251881Speter svn_stream_t *stream, 954251881Speter svn_boolean_t *found_old_reference, 955251881Speter svn_boolean_t *found_old_mergeinfo, 956251881Speter svn_error_t *(*custom_close_directory)(void *dir_baton, 957251881Speter apr_pool_t *scratch_pool), 958251881Speter svn_repos_notify_func_t notify_func, 959251881Speter void *notify_baton, 960251881Speter svn_revnum_t oldest_dumped_rev, 961251881Speter svn_boolean_t use_deltas, 962251881Speter svn_boolean_t verify, 963251881Speter apr_pool_t *pool) 964251881Speter{ 965251881Speter /* Allocate an edit baton to be stored in every directory baton. 966251881Speter Set it up for the directory baton we create here, which is the 967251881Speter root baton. */ 968251881Speter struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 969251881Speter svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool); 970251881Speter svn_delta_shim_callbacks_t *shim_callbacks = 971251881Speter svn_delta_shim_callbacks_default(pool); 972251881Speter 973251881Speter /* Set up the edit baton. */ 974251881Speter eb->stream = stream; 975251881Speter eb->notify_func = notify_func; 976251881Speter eb->notify_baton = notify_baton; 977251881Speter eb->oldest_dumped_rev = oldest_dumped_rev; 978251881Speter eb->bufsize = sizeof(eb->buffer); 979251881Speter eb->path = apr_pstrdup(pool, root_path); 980251881Speter SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); 981251881Speter eb->fs = fs; 982251881Speter eb->current_rev = to_rev; 983251881Speter eb->use_deltas = use_deltas; 984251881Speter eb->verify = verify; 985251881Speter eb->found_old_reference = found_old_reference; 986251881Speter eb->found_old_mergeinfo = found_old_mergeinfo; 987251881Speter 988251881Speter /* Set up the editor. */ 989251881Speter dump_editor->open_root = open_root; 990251881Speter dump_editor->delete_entry = delete_entry; 991251881Speter dump_editor->add_directory = add_directory; 992251881Speter dump_editor->open_directory = open_directory; 993251881Speter if (custom_close_directory) 994251881Speter dump_editor->close_directory = custom_close_directory; 995251881Speter else 996251881Speter dump_editor->close_directory = close_directory; 997251881Speter dump_editor->change_dir_prop = change_dir_prop; 998251881Speter dump_editor->add_file = add_file; 999251881Speter dump_editor->open_file = open_file; 1000251881Speter 1001251881Speter *edit_baton = eb; 1002251881Speter *editor = dump_editor; 1003251881Speter 1004251881Speter shim_callbacks->fetch_kind_func = fetch_kind_func; 1005251881Speter shim_callbacks->fetch_props_func = fetch_props_func; 1006251881Speter shim_callbacks->fetch_base_func = fetch_base_func; 1007251881Speter shim_callbacks->fetch_baton = eb; 1008251881Speter 1009251881Speter SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1010251881Speter NULL, NULL, shim_callbacks, pool, pool)); 1011251881Speter 1012251881Speter return SVN_NO_ERROR; 1013251881Speter} 1014251881Speter 1015251881Speter/*----------------------------------------------------------------------*/ 1016251881Speter 1017251881Speter/** The main dumping routine, svn_repos_dump_fs. **/ 1018251881Speter 1019251881Speter 1020251881Speter/* Helper for svn_repos_dump_fs. 1021251881Speter 1022251881Speter Write a revision record of REV in FS to writable STREAM, using POOL. 1023251881Speter */ 1024251881Speterstatic svn_error_t * 1025251881Speterwrite_revision_record(svn_stream_t *stream, 1026251881Speter svn_fs_t *fs, 1027251881Speter svn_revnum_t rev, 1028251881Speter apr_pool_t *pool) 1029251881Speter{ 1030251881Speter apr_size_t len; 1031251881Speter apr_hash_t *props; 1032251881Speter svn_stringbuf_t *encoded_prophash; 1033251881Speter apr_time_t timetemp; 1034251881Speter svn_string_t *datevalue; 1035251881Speter svn_stream_t *propstream; 1036251881Speter 1037251881Speter /* Read the revision props even if we're aren't going to dump 1038251881Speter them for verification purposes */ 1039251881Speter SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); 1040251881Speter 1041251881Speter /* Run revision date properties through the time conversion to 1042251881Speter canonicalize them. */ 1043251881Speter /* ### Remove this when it is no longer needed for sure. */ 1044251881Speter datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE); 1045251881Speter if (datevalue) 1046251881Speter { 1047251881Speter SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); 1048251881Speter datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), 1049251881Speter pool); 1050251881Speter svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); 1051251881Speter } 1052251881Speter 1053251881Speter encoded_prophash = svn_stringbuf_create_ensure(0, pool); 1054251881Speter propstream = svn_stream_from_stringbuf(encoded_prophash, pool); 1055251881Speter SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool)); 1056251881Speter SVN_ERR(svn_stream_close(propstream)); 1057251881Speter 1058251881Speter /* ### someday write a revision-content-checksum */ 1059251881Speter 1060251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1061251881Speter SVN_REPOS_DUMPFILE_REVISION_NUMBER 1062251881Speter ": %ld\n", rev)); 1063251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1064251881Speter SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH 1065251881Speter ": %" APR_SIZE_T_FMT "\n", 1066251881Speter encoded_prophash->len)); 1067251881Speter 1068251881Speter /* Write out a regular Content-length header for the benefit of 1069251881Speter non-Subversion RFC-822 parsers. */ 1070251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1071251881Speter SVN_REPOS_DUMPFILE_CONTENT_LENGTH 1072251881Speter ": %" APR_SIZE_T_FMT "\n\n", 1073251881Speter encoded_prophash->len)); 1074251881Speter 1075251881Speter len = encoded_prophash->len; 1076251881Speter SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len)); 1077251881Speter 1078251881Speter len = 1; 1079251881Speter return svn_stream_write(stream, "\n", &len); 1080251881Speter} 1081251881Speter 1082251881Speter 1083251881Speter 1084251881Speter/* The main dumper. */ 1085251881Spetersvn_error_t * 1086251881Spetersvn_repos_dump_fs3(svn_repos_t *repos, 1087251881Speter svn_stream_t *stream, 1088251881Speter svn_revnum_t start_rev, 1089251881Speter svn_revnum_t end_rev, 1090251881Speter svn_boolean_t incremental, 1091251881Speter svn_boolean_t use_deltas, 1092251881Speter svn_repos_notify_func_t notify_func, 1093251881Speter void *notify_baton, 1094251881Speter svn_cancel_func_t cancel_func, 1095251881Speter void *cancel_baton, 1096251881Speter apr_pool_t *pool) 1097251881Speter{ 1098251881Speter const svn_delta_editor_t *dump_editor; 1099251881Speter void *dump_edit_baton = NULL; 1100251881Speter svn_revnum_t i; 1101251881Speter svn_fs_t *fs = svn_repos_fs(repos); 1102251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1103251881Speter svn_revnum_t youngest; 1104251881Speter const char *uuid; 1105251881Speter int version; 1106251881Speter svn_boolean_t found_old_reference = FALSE; 1107251881Speter svn_boolean_t found_old_mergeinfo = FALSE; 1108251881Speter svn_repos_notify_t *notify; 1109251881Speter 1110251881Speter /* Determine the current youngest revision of the filesystem. */ 1111251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1112251881Speter 1113251881Speter /* Use default vals if necessary. */ 1114251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1115251881Speter start_rev = 0; 1116251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1117251881Speter end_rev = youngest; 1118251881Speter if (! stream) 1119251881Speter stream = svn_stream_empty(pool); 1120251881Speter 1121251881Speter /* Validate the revisions. */ 1122251881Speter if (start_rev > end_rev) 1123251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1124251881Speter _("Start revision %ld" 1125251881Speter " is greater than end revision %ld"), 1126251881Speter start_rev, end_rev); 1127251881Speter if (end_rev > youngest) 1128251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1129251881Speter _("End revision %ld is invalid " 1130251881Speter "(youngest revision is %ld)"), 1131251881Speter end_rev, youngest); 1132251881Speter if ((start_rev == 0) && incremental) 1133251881Speter incremental = FALSE; /* revision 0 looks the same regardless of 1134251881Speter whether or not this is an incremental 1135251881Speter dump, so just simplify things. */ 1136251881Speter 1137251881Speter /* Write out the UUID. */ 1138251881Speter SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); 1139251881Speter 1140251881Speter /* If we're not using deltas, use the previous version, for 1141251881Speter compatibility with svn 1.0.x. */ 1142251881Speter version = SVN_REPOS_DUMPFILE_FORMAT_VERSION; 1143251881Speter if (!use_deltas) 1144251881Speter version--; 1145251881Speter 1146251881Speter /* Write out "general" metadata for the dumpfile. In this case, a 1147251881Speter magic header followed by a dumpfile format version. */ 1148251881Speter SVN_ERR(svn_stream_printf(stream, pool, 1149251881Speter SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", 1150251881Speter version)); 1151251881Speter SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID 1152251881Speter ": %s\n\n", uuid)); 1153251881Speter 1154251881Speter /* Create a notify object that we can reuse in the loop. */ 1155251881Speter if (notify_func) 1156251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, 1157251881Speter pool); 1158251881Speter 1159251881Speter /* Main loop: we're going to dump revision i. */ 1160251881Speter for (i = start_rev; i <= end_rev; i++) 1161251881Speter { 1162251881Speter svn_revnum_t from_rev, to_rev; 1163251881Speter svn_fs_root_t *to_root; 1164251881Speter svn_boolean_t use_deltas_for_rev; 1165251881Speter 1166251881Speter svn_pool_clear(subpool); 1167251881Speter 1168251881Speter /* Check for cancellation. */ 1169251881Speter if (cancel_func) 1170251881Speter SVN_ERR(cancel_func(cancel_baton)); 1171251881Speter 1172251881Speter /* Special-case the initial revision dump: it needs to contain 1173251881Speter *all* nodes, because it's the foundation of all future 1174251881Speter revisions in the dumpfile. */ 1175251881Speter if ((i == start_rev) && (! incremental)) 1176251881Speter { 1177251881Speter /* Special-special-case a dump of revision 0. */ 1178251881Speter if (i == 0) 1179251881Speter { 1180251881Speter /* Just write out the one revision 0 record and move on. 1181251881Speter The parser might want to use its properties. */ 1182251881Speter SVN_ERR(write_revision_record(stream, fs, 0, subpool)); 1183251881Speter to_rev = 0; 1184251881Speter goto loop_end; 1185251881Speter } 1186251881Speter 1187251881Speter /* Compare START_REV to revision 0, so that everything 1188251881Speter appears to be added. */ 1189251881Speter from_rev = 0; 1190251881Speter to_rev = i; 1191251881Speter } 1192251881Speter else 1193251881Speter { 1194251881Speter /* In the normal case, we want to compare consecutive revs. */ 1195251881Speter from_rev = i - 1; 1196251881Speter to_rev = i; 1197251881Speter } 1198251881Speter 1199251881Speter /* Write the revision record. */ 1200251881Speter SVN_ERR(write_revision_record(stream, fs, to_rev, subpool)); 1201251881Speter 1202251881Speter /* Fetch the editor which dumps nodes to a file. Regardless of 1203251881Speter what we've been told, don't use deltas for the first rev of a 1204251881Speter non-incremental dump. */ 1205251881Speter use_deltas_for_rev = use_deltas && (incremental || i != start_rev); 1206251881Speter SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev, 1207251881Speter "", stream, &found_old_reference, 1208251881Speter &found_old_mergeinfo, NULL, 1209251881Speter notify_func, notify_baton, 1210251881Speter start_rev, use_deltas_for_rev, FALSE, subpool)); 1211251881Speter 1212251881Speter /* Drive the editor in one way or another. */ 1213251881Speter SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool)); 1214251881Speter 1215251881Speter /* If this is the first revision of a non-incremental dump, 1216251881Speter we're in for a full tree dump. Otherwise, we want to simply 1217251881Speter replay the revision. */ 1218251881Speter if ((i == start_rev) && (! incremental)) 1219251881Speter { 1220251881Speter svn_fs_root_t *from_root; 1221251881Speter SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool)); 1222251881Speter SVN_ERR(svn_repos_dir_delta2(from_root, "", "", 1223251881Speter to_root, "", 1224251881Speter dump_editor, dump_edit_baton, 1225251881Speter NULL, 1226251881Speter NULL, 1227251881Speter FALSE, /* don't send text-deltas */ 1228251881Speter svn_depth_infinity, 1229251881Speter FALSE, /* don't send entry props */ 1230251881Speter FALSE, /* don't ignore ancestry */ 1231251881Speter subpool)); 1232251881Speter } 1233251881Speter else 1234251881Speter { 1235251881Speter SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1236251881Speter dump_editor, dump_edit_baton, 1237251881Speter NULL, NULL, subpool)); 1238251881Speter 1239251881Speter /* While our editor close_edit implementation is a no-op, we still 1240251881Speter do this for completeness. */ 1241251881Speter SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool)); 1242251881Speter } 1243251881Speter 1244251881Speter loop_end: 1245251881Speter if (notify_func) 1246251881Speter { 1247251881Speter notify->revision = to_rev; 1248251881Speter notify_func(notify_baton, notify, subpool); 1249251881Speter } 1250251881Speter } 1251251881Speter 1252251881Speter if (notify_func) 1253251881Speter { 1254251881Speter /* Did we issue any warnings about references to revisions older than 1255251881Speter the oldest dumped revision? If so, then issue a final generic 1256251881Speter warning, since the inline warnings already issued might easily be 1257251881Speter missed. */ 1258251881Speter 1259251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool); 1260251881Speter notify_func(notify_baton, notify, subpool); 1261251881Speter 1262251881Speter if (found_old_reference) 1263251881Speter { 1264251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1265251881Speter 1266251881Speter notify->warning = svn_repos_notify_warning_found_old_reference; 1267251881Speter notify->warning_str = _("The range of revisions dumped " 1268251881Speter "contained references to " 1269251881Speter "copy sources outside that " 1270251881Speter "range."); 1271251881Speter notify_func(notify_baton, notify, subpool); 1272251881Speter } 1273251881Speter 1274251881Speter /* Ditto if we issued any warnings about old revisions referenced 1275251881Speter in dumped mergeinfo. */ 1276251881Speter if (found_old_mergeinfo) 1277251881Speter { 1278251881Speter notify = svn_repos_notify_create(svn_repos_notify_warning, subpool); 1279251881Speter 1280251881Speter notify->warning = svn_repos_notify_warning_found_old_mergeinfo; 1281251881Speter notify->warning_str = _("The range of revisions dumped " 1282251881Speter "contained mergeinfo " 1283251881Speter "which reference revisions outside " 1284251881Speter "that range."); 1285251881Speter notify_func(notify_baton, notify, subpool); 1286251881Speter } 1287251881Speter } 1288251881Speter 1289251881Speter svn_pool_destroy(subpool); 1290251881Speter 1291251881Speter return SVN_NO_ERROR; 1292251881Speter} 1293251881Speter 1294251881Speter 1295251881Speter/*----------------------------------------------------------------------*/ 1296251881Speter 1297251881Speter/* verify, based on dump */ 1298251881Speter 1299251881Speter 1300251881Speter/* Creating a new revision that changes /A/B/E/bravo means creating new 1301251881Speter directory listings for /, /A, /A/B, and /A/B/E in the new revision, with 1302251881Speter each entry not changed in the new revision a link back to the entry in a 1303251881Speter previous revision. svn_repos_replay()ing a revision does not verify that 1304251881Speter those links are correct. 1305251881Speter 1306251881Speter For paths actually changed in the revision we verify, we get directory 1307251881Speter contents or file length twice: once in the dump editor, and once here. 1308251881Speter We could create a new verify baton, store in it the changed paths, and 1309251881Speter skip those here, but that means building an entire wrapper editor and 1310251881Speter managing two levels of batons. The impact from checking these entries 1311251881Speter twice should be minimal, while the code to avoid it is not. 1312251881Speter*/ 1313251881Speter 1314251881Speterstatic svn_error_t * 1315251881Speterverify_directory_entry(void *baton, const void *key, apr_ssize_t klen, 1316251881Speter void *val, apr_pool_t *pool) 1317251881Speter{ 1318251881Speter struct dir_baton *db = baton; 1319251881Speter svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; 1320251881Speter char *path = svn_relpath_join(db->path, (const char *)key, pool); 1321251881Speter apr_hash_t *dirents; 1322251881Speter svn_filesize_t len; 1323251881Speter 1324251881Speter /* since we can't access the directory entries directly by their ID, 1325251881Speter we need to navigate from the FS_ROOT to them (relatively expensive 1326251881Speter because we may start at a never rev than the last change to node). */ 1327251881Speter switch (dirent->kind) { 1328251881Speter case svn_node_dir: 1329251881Speter /* Getting this directory's contents is enough to ensure that our 1330251881Speter link to it is correct. */ 1331251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool)); 1332251881Speter break; 1333251881Speter case svn_node_file: 1334251881Speter /* Getting this file's size is enough to ensure that our link to it 1335251881Speter is correct. */ 1336251881Speter SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool)); 1337251881Speter break; 1338251881Speter default: 1339251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 1340251881Speter _("Unexpected node kind %d for '%s'"), 1341251881Speter dirent->kind, path); 1342251881Speter } 1343251881Speter 1344251881Speter return SVN_NO_ERROR; 1345251881Speter} 1346251881Speter 1347251881Speterstatic svn_error_t * 1348251881Speterverify_close_directory(void *dir_baton, 1349251881Speter apr_pool_t *pool) 1350251881Speter{ 1351251881Speter struct dir_baton *db = dir_baton; 1352251881Speter apr_hash_t *dirents; 1353251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, 1354251881Speter db->path, pool)); 1355251881Speter SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, 1356251881Speter dir_baton, pool)); 1357251881Speter return close_directory(dir_baton, pool); 1358251881Speter} 1359251881Speter 1360251881Speter/* Baton type used for forwarding notifications from FS API to REPOS API. */ 1361251881Speterstruct verify_fs2_notify_func_baton_t 1362251881Speter{ 1363251881Speter /* notification function to call (must not be NULL) */ 1364251881Speter svn_repos_notify_func_t notify_func; 1365251881Speter 1366251881Speter /* baton to use for it */ 1367251881Speter void *notify_baton; 1368251881Speter 1369251881Speter /* type of notification to send (we will simply plug in the revision) */ 1370251881Speter svn_repos_notify_t *notify; 1371251881Speter}; 1372251881Speter 1373251881Speter/* Forward the notification to BATON. */ 1374251881Speterstatic void 1375251881Speterverify_fs2_notify_func(svn_revnum_t revision, 1376251881Speter void *baton, 1377251881Speter apr_pool_t *pool) 1378251881Speter{ 1379251881Speter struct verify_fs2_notify_func_baton_t *notify_baton = baton; 1380251881Speter 1381251881Speter notify_baton->notify->revision = revision; 1382251881Speter notify_baton->notify_func(notify_baton->notify_baton, 1383251881Speter notify_baton->notify, pool); 1384251881Speter} 1385251881Speter 1386251881Spetersvn_error_t * 1387251881Spetersvn_repos_verify_fs2(svn_repos_t *repos, 1388251881Speter svn_revnum_t start_rev, 1389251881Speter svn_revnum_t end_rev, 1390251881Speter svn_repos_notify_func_t notify_func, 1391251881Speter void *notify_baton, 1392251881Speter svn_cancel_func_t cancel_func, 1393251881Speter void *cancel_baton, 1394251881Speter apr_pool_t *pool) 1395251881Speter{ 1396251881Speter svn_fs_t *fs = svn_repos_fs(repos); 1397251881Speter svn_revnum_t youngest; 1398251881Speter svn_revnum_t rev; 1399251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1400251881Speter svn_repos_notify_t *notify; 1401251881Speter svn_fs_progress_notify_func_t verify_notify = NULL; 1402251881Speter struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL; 1403251881Speter 1404251881Speter /* Determine the current youngest revision of the filesystem. */ 1405251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1406251881Speter 1407251881Speter /* Use default vals if necessary. */ 1408251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1409251881Speter start_rev = 0; 1410251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1411251881Speter end_rev = youngest; 1412251881Speter 1413251881Speter /* Validate the revisions. */ 1414251881Speter if (start_rev > end_rev) 1415251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1416251881Speter _("Start revision %ld" 1417251881Speter " is greater than end revision %ld"), 1418251881Speter start_rev, end_rev); 1419251881Speter if (end_rev > youngest) 1420251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1421251881Speter _("End revision %ld is invalid " 1422251881Speter "(youngest revision is %ld)"), 1423251881Speter end_rev, youngest); 1424251881Speter 1425251881Speter /* Create a notify object that we can reuse within the loop and a 1426251881Speter forwarding structure for notifications from inside svn_fs_verify(). */ 1427251881Speter if (notify_func) 1428251881Speter { 1429251881Speter notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, 1430251881Speter pool); 1431251881Speter 1432251881Speter verify_notify = verify_fs2_notify_func; 1433251881Speter verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); 1434251881Speter verify_notify_baton->notify_func = notify_func; 1435251881Speter verify_notify_baton->notify_baton = notify_baton; 1436251881Speter verify_notify_baton->notify 1437251881Speter = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); 1438251881Speter } 1439251881Speter 1440251881Speter /* Verify global metadata and backend-specific data first. */ 1441251881Speter SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), 1442251881Speter start_rev, end_rev, 1443251881Speter verify_notify, verify_notify_baton, 1444251881Speter cancel_func, cancel_baton, pool)); 1445251881Speter 1446251881Speter for (rev = start_rev; rev <= end_rev; rev++) 1447251881Speter { 1448251881Speter const svn_delta_editor_t *dump_editor; 1449251881Speter void *dump_edit_baton; 1450251881Speter const svn_delta_editor_t *cancel_editor; 1451251881Speter void *cancel_edit_baton; 1452251881Speter svn_fs_root_t *to_root; 1453251881Speter apr_hash_t *props; 1454251881Speter 1455251881Speter svn_pool_clear(iterpool); 1456251881Speter 1457251881Speter /* Get cancellable dump editor, but with our close_directory handler. */ 1458251881Speter SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, 1459251881Speter fs, rev, "", 1460251881Speter svn_stream_empty(iterpool), 1461251881Speter NULL, NULL, 1462251881Speter verify_close_directory, 1463251881Speter notify_func, notify_baton, 1464251881Speter start_rev, 1465251881Speter FALSE, TRUE, /* use_deltas, verify */ 1466251881Speter iterpool)); 1467251881Speter SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 1468251881Speter dump_editor, dump_edit_baton, 1469251881Speter &cancel_editor, 1470251881Speter &cancel_edit_baton, 1471251881Speter iterpool)); 1472251881Speter 1473251881Speter SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool)); 1474251881Speter SVN_ERR(svn_fs_verify_root(to_root, iterpool)); 1475251881Speter 1476251881Speter SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 1477251881Speter cancel_editor, cancel_edit_baton, 1478251881Speter NULL, NULL, iterpool)); 1479251881Speter /* While our editor close_edit implementation is a no-op, we still 1480251881Speter do this for completeness. */ 1481251881Speter SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool)); 1482251881Speter 1483251881Speter SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool)); 1484251881Speter 1485251881Speter if (notify_func) 1486251881Speter { 1487251881Speter notify->revision = rev; 1488251881Speter notify_func(notify_baton, notify, iterpool); 1489251881Speter } 1490251881Speter } 1491251881Speter 1492251881Speter /* We're done. */ 1493251881Speter if (notify_func) 1494251881Speter { 1495251881Speter notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool); 1496251881Speter notify_func(notify_baton, notify, iterpool); 1497251881Speter } 1498251881Speter 1499251881Speter /* Per-backend verification. */ 1500251881Speter svn_pool_destroy(iterpool); 1501251881Speter 1502251881Speter return SVN_NO_ERROR; 1503251881Speter} 1504