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 24299742Sdim#include <stdarg.h> 25299742Sdim 26251881Speter#include "svn_private_config.h" 27251881Speter#include "svn_pools.h" 28251881Speter#include "svn_error.h" 29251881Speter#include "svn_fs.h" 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_iter.h" 32251881Speter#include "svn_repos.h" 33251881Speter#include "svn_string.h" 34251881Speter#include "svn_dirent_uri.h" 35251881Speter#include "svn_path.h" 36251881Speter#include "svn_time.h" 37251881Speter#include "svn_checksum.h" 38251881Speter#include "svn_props.h" 39251881Speter#include "svn_sorts.h" 40251881Speter 41299742Sdim#include "private/svn_repos_private.h" 42251881Speter#include "private/svn_mergeinfo_private.h" 43251881Speter#include "private/svn_fs_private.h" 44299742Sdim#include "private/svn_sorts_private.h" 45299742Sdim#include "private/svn_utf_private.h" 46299742Sdim#include "private/svn_cache.h" 47251881Speter 48251881Speter#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r)) 49251881Speter 50251881Speter/*----------------------------------------------------------------------*/ 51251881Speter 52251881Speter 53299742Sdim/* To be able to check whether a path exists in the current revision 54299742Sdim (as changes come in), we need to track the relevant tree changes. 55251881Speter 56299742Sdim In particular, we remember deletions, additions and copies including 57299742Sdim their copy-from info. Since the dump performs a pre-order tree walk, 58299742Sdim we only need to store the data for the stack of parent folders. 59299742Sdim 60299742Sdim The problem that we are trying to solve is that the dump receives 61299742Sdim transforming operations whose validity depends on previous operations 62299742Sdim in the same revision but cannot be checked against the final state 63299742Sdim as stored in the repository as that is the state *after* we applied 64299742Sdim the respective tree changes. 65299742Sdim 66299742Sdim Note that the tracker functions don't perform any sanity or validity 67299742Sdim checks. Those higher-level tests have to be done in the calling code. 68299742Sdim However, there is no way to corrupt the data structure using the 69299742Sdim provided functions. 70299742Sdim */ 71299742Sdim 72299742Sdim/* Single entry in the path tracker. Not all levels along the path 73299742Sdim hierarchy do need to have an instance of this struct but only those 74299742Sdim that got changed by a tree modification. 75299742Sdim 76299742Sdim Please note that the path info in this struct is stored in re-usable 77299742Sdim stringbuf objects such that we don't need to allocate more memory than 78299742Sdim the longest path we encounter. 79299742Sdim */ 80299742Sdimtypedef struct path_tracker_entry_t 81299742Sdim{ 82299742Sdim /* path in the current tree */ 83299742Sdim svn_stringbuf_t *path; 84299742Sdim 85299742Sdim /* copy-from path (must be empty if COPYFROM_REV is SVN_INVALID_REVNUM) */ 86299742Sdim svn_stringbuf_t *copyfrom_path; 87299742Sdim 88299742Sdim /* copy-from revision (SVN_INVALID_REVNUM for additions / replacements 89299742Sdim that don't copy history, i.e. with no sub-tree) */ 90299742Sdim svn_revnum_t copyfrom_rev; 91299742Sdim 92299742Sdim /* if FALSE, PATH has been deleted */ 93299742Sdim svn_boolean_t exists; 94299742Sdim} path_tracker_entry_t; 95299742Sdim 96299742Sdim/* Tracks all tree modifications above the current path. 97299742Sdim */ 98299742Sdimtypedef struct path_tracker_t 99299742Sdim{ 100299742Sdim /* Container for all relevant tree changes in depth order. 101299742Sdim May contain more entries than DEPTH to allow for reusing memory. 102299742Sdim Only entries 0 .. DEPTH-1 are valid. 103299742Sdim */ 104299742Sdim apr_array_header_t *stack; 105299742Sdim 106299742Sdim /* Number of relevant entries in STACK. May be 0 */ 107299742Sdim int depth; 108299742Sdim 109299742Sdim /* Revision that we current track. If DEPTH is 0, paths are exist in 110299742Sdim REVISION exactly when they exist in REVISION-1. This applies only 111299742Sdim to the current state of our tree walk. 112299742Sdim */ 113299742Sdim svn_revnum_t revision; 114299742Sdim 115299742Sdim /* Allocate container entries here. */ 116299742Sdim apr_pool_t *pool; 117299742Sdim} path_tracker_t; 118299742Sdim 119299742Sdim/* Return a new path tracker object for REVISION, allocated in POOL. 120299742Sdim */ 121299742Sdimstatic path_tracker_t * 122299742Sdimtracker_create(svn_revnum_t revision, 123299742Sdim apr_pool_t *pool) 124299742Sdim{ 125299742Sdim path_tracker_t *result = apr_pcalloc(pool, sizeof(*result)); 126299742Sdim result->stack = apr_array_make(pool, 16, sizeof(path_tracker_entry_t)); 127299742Sdim result->revision = revision; 128299742Sdim result->pool = pool; 129299742Sdim 130299742Sdim return result; 131299742Sdim} 132299742Sdim 133299742Sdim/* Remove all entries from TRACKER that are not relevant to PATH anymore. 134299742Sdim * If ALLOW_EXACT_MATCH is FALSE, keep only entries that pertain to 135299742Sdim * parent folders but not to PATH itself. 136299742Sdim * 137299742Sdim * This internal function implicitly updates the tracker state during the 138299742Sdim * tree by removing "past" entries. Other functions will add entries when 139299742Sdim * we encounter a new tree change. 140299742Sdim */ 141299742Sdimstatic void 142299742Sdimtracker_trim(path_tracker_t *tracker, 143299742Sdim const char *path, 144299742Sdim svn_boolean_t allow_exact_match) 145299742Sdim{ 146299742Sdim /* remove everything that is unrelated to PATH. 147299742Sdim Note that TRACKER->STACK is depth-ordered, 148299742Sdim i.e. stack[N] is a (maybe indirect) parent of stack[N+1] 149299742Sdim for N+1 < DEPTH. 150299742Sdim */ 151299742Sdim for (; tracker->depth; --tracker->depth) 152299742Sdim { 153299742Sdim path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack, 154299742Sdim tracker->depth - 1, 155299742Sdim path_tracker_entry_t); 156299742Sdim const char *rel_path 157299742Sdim = svn_dirent_skip_ancestor(parent->path->data, path); 158299742Sdim 159299742Sdim /* always keep parents. Keep exact matches when allowed. */ 160299742Sdim if (rel_path && (allow_exact_match || *rel_path != '\0')) 161299742Sdim break; 162299742Sdim } 163299742Sdim} 164299742Sdim 165299742Sdim/* Using TRACKER, check what path at what revision in the repository must 166299742Sdim be checked to decide that whether PATH exists. Return the info in 167299742Sdim *ORIG_PATH and *ORIG_REV, respectively. 168299742Sdim 169299742Sdim If the path is known to not exist, *ORIG_PATH will be NULL and *ORIG_REV 170299742Sdim will be SVN_INVALID_REVNUM. If *ORIG_REV is SVN_INVALID_REVNUM, PATH 171299742Sdim has just been added in the revision currently being tracked. 172299742Sdim 173299742Sdim Use POOL for allocations. Note that *ORIG_PATH may be allocated in POOL, 174299742Sdim a reference to internal data with the same lifetime as TRACKER or just 175299742Sdim PATH. 176299742Sdim */ 177299742Sdimstatic void 178299742Sdimtracker_lookup(const char **orig_path, 179299742Sdim svn_revnum_t *orig_rev, 180299742Sdim path_tracker_t *tracker, 181299742Sdim const char *path, 182299742Sdim apr_pool_t *pool) 183299742Sdim{ 184299742Sdim tracker_trim(tracker, path, TRUE); 185299742Sdim if (tracker->depth == 0) 186299742Sdim { 187299742Sdim /* no tree changes -> paths are the same as in the previous rev. */ 188299742Sdim *orig_path = path; 189299742Sdim *orig_rev = tracker->revision - 1; 190299742Sdim } 191299742Sdim else 192299742Sdim { 193299742Sdim path_tracker_entry_t *parent = &APR_ARRAY_IDX(tracker->stack, 194299742Sdim tracker->depth - 1, 195299742Sdim path_tracker_entry_t); 196299742Sdim if (parent->exists) 197299742Sdim { 198299742Sdim const char *rel_path 199299742Sdim = svn_dirent_skip_ancestor(parent->path->data, path); 200299742Sdim 201299742Sdim if (parent->copyfrom_rev != SVN_INVALID_REVNUM) 202299742Sdim { 203299742Sdim /* parent is a copy with history. Translate path. */ 204299742Sdim *orig_path = svn_dirent_join(parent->copyfrom_path->data, 205299742Sdim rel_path, pool); 206299742Sdim *orig_rev = parent->copyfrom_rev; 207299742Sdim } 208299742Sdim else if (*rel_path == '\0') 209299742Sdim { 210299742Sdim /* added in this revision with no history */ 211299742Sdim *orig_path = path; 212299742Sdim *orig_rev = tracker->revision; 213299742Sdim } 214299742Sdim else 215299742Sdim { 216299742Sdim /* parent got added but not this path */ 217299742Sdim *orig_path = NULL; 218299742Sdim *orig_rev = SVN_INVALID_REVNUM; 219299742Sdim } 220299742Sdim } 221299742Sdim else 222299742Sdim { 223299742Sdim /* (maybe parent) path has been deleted */ 224299742Sdim *orig_path = NULL; 225299742Sdim *orig_rev = SVN_INVALID_REVNUM; 226299742Sdim } 227299742Sdim } 228299742Sdim} 229299742Sdim 230299742Sdim/* Return a reference to the stack entry in TRACKER for PATH. If no 231299742Sdim suitable entry exists, add one. Implicitly updates the tracked tree 232299742Sdim location. 233299742Sdim 234299742Sdim Only the PATH member of the result is being updated. All other members 235299742Sdim will have undefined values. 236299742Sdim */ 237299742Sdimstatic path_tracker_entry_t * 238299742Sdimtracker_add_entry(path_tracker_t *tracker, 239299742Sdim const char *path) 240299742Sdim{ 241299742Sdim path_tracker_entry_t *entry; 242299742Sdim tracker_trim(tracker, path, FALSE); 243299742Sdim 244299742Sdim if (tracker->depth == tracker->stack->nelts) 245299742Sdim { 246299742Sdim entry = apr_array_push(tracker->stack); 247299742Sdim entry->path = svn_stringbuf_create_empty(tracker->pool); 248299742Sdim entry->copyfrom_path = svn_stringbuf_create_empty(tracker->pool); 249299742Sdim } 250299742Sdim else 251299742Sdim { 252299742Sdim entry = &APR_ARRAY_IDX(tracker->stack, tracker->depth, 253299742Sdim path_tracker_entry_t); 254299742Sdim } 255299742Sdim 256299742Sdim svn_stringbuf_set(entry->path, path); 257299742Sdim ++tracker->depth; 258299742Sdim 259299742Sdim return entry; 260299742Sdim} 261299742Sdim 262299742Sdim/* Update the TRACKER with a copy from COPYFROM_PATH@COPYFROM_REV to 263299742Sdim PATH in the tracked revision. 264299742Sdim */ 265299742Sdimstatic void 266299742Sdimtracker_path_copy(path_tracker_t *tracker, 267299742Sdim const char *path, 268299742Sdim const char *copyfrom_path, 269299742Sdim svn_revnum_t copyfrom_rev) 270299742Sdim{ 271299742Sdim path_tracker_entry_t *entry = tracker_add_entry(tracker, path); 272299742Sdim 273299742Sdim svn_stringbuf_set(entry->copyfrom_path, copyfrom_path); 274299742Sdim entry->copyfrom_rev = copyfrom_rev; 275299742Sdim entry->exists = TRUE; 276299742Sdim} 277299742Sdim 278299742Sdim/* Update the TRACKER with a plain addition of PATH (without history). 279299742Sdim */ 280299742Sdimstatic void 281299742Sdimtracker_path_add(path_tracker_t *tracker, 282299742Sdim const char *path) 283299742Sdim{ 284299742Sdim path_tracker_entry_t *entry = tracker_add_entry(tracker, path); 285299742Sdim 286299742Sdim svn_stringbuf_setempty(entry->copyfrom_path); 287299742Sdim entry->copyfrom_rev = SVN_INVALID_REVNUM; 288299742Sdim entry->exists = TRUE; 289299742Sdim} 290299742Sdim 291299742Sdim/* Update the TRACKER with a replacement of PATH with a plain addition 292299742Sdim (without history). 293299742Sdim */ 294299742Sdimstatic void 295299742Sdimtracker_path_replace(path_tracker_t *tracker, 296299742Sdim const char *path) 297299742Sdim{ 298299742Sdim /* this will implicitly purge all previous sub-tree info from STACK. 299299742Sdim Thus, no need to tack the deletion explicitly. */ 300299742Sdim tracker_path_add(tracker, path); 301299742Sdim} 302299742Sdim 303299742Sdim/* Update the TRACKER with a deletion of PATH. 304299742Sdim */ 305299742Sdimstatic void 306299742Sdimtracker_path_delete(path_tracker_t *tracker, 307299742Sdim const char *path) 308299742Sdim{ 309299742Sdim path_tracker_entry_t *entry = tracker_add_entry(tracker, path); 310299742Sdim 311299742Sdim svn_stringbuf_setempty(entry->copyfrom_path); 312299742Sdim entry->copyfrom_rev = SVN_INVALID_REVNUM; 313299742Sdim entry->exists = FALSE; 314299742Sdim} 315299742Sdim 316299742Sdim 317251881Speter/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and 318251881Speter store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, 319251881Speter in which case the delta will be computed against an empty file, as 320251881Speter per the svn_fs_get_file_delta_stream docstring. Record the length 321251881Speter of the temporary file in *LEN, and rewind the file before 322251881Speter returning. */ 323251881Speterstatic svn_error_t * 324251881Speterstore_delta(apr_file_t **tempfile, svn_filesize_t *len, 325251881Speter svn_fs_root_t *oldroot, const char *oldpath, 326251881Speter svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) 327251881Speter{ 328251881Speter svn_stream_t *temp_stream; 329251881Speter apr_off_t offset = 0; 330251881Speter svn_txdelta_stream_t *delta_stream; 331251881Speter svn_txdelta_window_handler_t wh; 332251881Speter void *whb; 333251881Speter 334251881Speter /* Create a temporary file and open a stream to it. Note that we need 335251881Speter the file handle in order to rewind it. */ 336251881Speter SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, 337251881Speter svn_io_file_del_on_pool_cleanup, 338251881Speter pool, pool)); 339251881Speter temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); 340251881Speter 341251881Speter /* Compute the delta and send it to the temporary file. */ 342251881Speter SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, 343251881Speter newroot, newpath, pool)); 344251881Speter svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0, 345251881Speter SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); 346251881Speter SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); 347251881Speter 348251881Speter /* Get the length of the temporary file and rewind it. */ 349251881Speter SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); 350251881Speter *len = offset; 351251881Speter offset = 0; 352251881Speter return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); 353251881Speter} 354251881Speter 355251881Speter 356299742Sdim/* Send a notification of type #svn_repos_notify_warning, subtype WARNING, 357299742Sdim with message WARNING_FMT formatted with the remaining variable arguments. 358299742Sdim Send it by calling NOTIFY_FUNC (if not null) with NOTIFY_BATON. 359299742Sdim */ 360299742Sdim__attribute__((format(printf, 5, 6))) 361299742Sdimstatic void 362299742Sdimnotify_warning(apr_pool_t *scratch_pool, 363299742Sdim svn_repos_notify_func_t notify_func, 364299742Sdim void *notify_baton, 365299742Sdim svn_repos_notify_warning_t warning, 366299742Sdim const char *warning_fmt, 367299742Sdim ...) 368299742Sdim{ 369299742Sdim va_list va; 370299742Sdim svn_repos_notify_t *notify; 371299742Sdim 372299742Sdim if (notify_func == NULL) 373299742Sdim return; 374299742Sdim 375299742Sdim notify = svn_repos_notify_create(svn_repos_notify_warning, scratch_pool); 376299742Sdim notify->warning = warning; 377299742Sdim va_start(va, warning_fmt); 378299742Sdim notify->warning_str = apr_pvsprintf(scratch_pool, warning_fmt, va); 379299742Sdim va_end(va); 380299742Sdim 381299742Sdim notify_func(notify_baton, notify, scratch_pool); 382299742Sdim} 383299742Sdim 384299742Sdim 385251881Speter/*----------------------------------------------------------------------*/ 386251881Speter 387299742Sdim/* Write to STREAM the header in HEADERS named KEY, if present. 388299742Sdim */ 389299742Sdimstatic svn_error_t * 390299742Sdimwrite_header(svn_stream_t *stream, 391299742Sdim apr_hash_t *headers, 392299742Sdim const char *key, 393299742Sdim apr_pool_t *scratch_pool) 394299742Sdim{ 395299742Sdim const char *val = svn_hash_gets(headers, key); 396299742Sdim 397299742Sdim if (val) 398299742Sdim { 399299742Sdim SVN_ERR(svn_stream_printf(stream, scratch_pool, 400299742Sdim "%s: %s\n", key, val)); 401299742Sdim } 402299742Sdim return SVN_NO_ERROR; 403299742Sdim} 404299742Sdim 405299742Sdim/* Write headers, in arbitrary order. 406299742Sdim * ### TODO: use a stable order 407299742Sdim * ### Modifies HEADERS. 408299742Sdim */ 409299742Sdimstatic svn_error_t * 410299742Sdimwrite_revision_headers(svn_stream_t *stream, 411299742Sdim apr_hash_t *headers, 412299742Sdim apr_pool_t *scratch_pool) 413299742Sdim{ 414299742Sdim const char **h; 415299742Sdim apr_hash_index_t *hi; 416299742Sdim 417299742Sdim static const char *revision_headers_order[] = 418299742Sdim { 419299742Sdim SVN_REPOS_DUMPFILE_REVISION_NUMBER, /* must be first */ 420299742Sdim NULL 421299742Sdim }; 422299742Sdim 423299742Sdim /* Write some headers in a given order */ 424299742Sdim for (h = revision_headers_order; *h; h++) 425299742Sdim { 426299742Sdim SVN_ERR(write_header(stream, headers, *h, scratch_pool)); 427299742Sdim svn_hash_sets(headers, *h, NULL); 428299742Sdim } 429299742Sdim 430299742Sdim /* Write any and all remaining headers except Content-length. 431299742Sdim * ### TODO: use a stable order 432299742Sdim */ 433299742Sdim for (hi = apr_hash_first(scratch_pool, headers); hi; hi = apr_hash_next(hi)) 434299742Sdim { 435299742Sdim const char *key = apr_hash_this_key(hi); 436299742Sdim 437299742Sdim if (strcmp(key, SVN_REPOS_DUMPFILE_CONTENT_LENGTH) != 0) 438299742Sdim SVN_ERR(write_header(stream, headers, key, scratch_pool)); 439299742Sdim } 440299742Sdim 441299742Sdim /* Content-length must be last */ 442299742Sdim SVN_ERR(write_header(stream, headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, 443299742Sdim scratch_pool)); 444299742Sdim 445299742Sdim return SVN_NO_ERROR; 446299742Sdim} 447299742Sdim 448299742Sdim/* A header entry: the element type of the apr_array_header_t which is 449299742Sdim * the real type of svn_repos__dumpfile_headers_t. 450299742Sdim */ 451299742Sdimtypedef struct svn_repos__dumpfile_header_entry_t { 452299742Sdim const char *key, *val; 453299742Sdim} svn_repos__dumpfile_header_entry_t; 454299742Sdim 455299742Sdimsvn_repos__dumpfile_headers_t * 456299742Sdimsvn_repos__dumpfile_headers_create(apr_pool_t *pool) 457299742Sdim{ 458299742Sdim svn_repos__dumpfile_headers_t *headers 459299742Sdim = apr_array_make(pool, 5, sizeof(svn_repos__dumpfile_header_entry_t)); 460299742Sdim 461299742Sdim return headers; 462299742Sdim} 463299742Sdim 464299742Sdimvoid 465299742Sdimsvn_repos__dumpfile_header_push(svn_repos__dumpfile_headers_t *headers, 466299742Sdim const char *key, 467299742Sdim const char *val) 468299742Sdim{ 469299742Sdim svn_repos__dumpfile_header_entry_t *h 470299742Sdim = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); 471299742Sdim 472299742Sdim h->key = apr_pstrdup(headers->pool, key); 473299742Sdim h->val = apr_pstrdup(headers->pool, val); 474299742Sdim} 475299742Sdim 476299742Sdimvoid 477299742Sdimsvn_repos__dumpfile_header_pushf(svn_repos__dumpfile_headers_t *headers, 478299742Sdim const char *key, 479299742Sdim const char *val_fmt, 480299742Sdim ...) 481299742Sdim{ 482299742Sdim va_list ap; 483299742Sdim svn_repos__dumpfile_header_entry_t *h 484299742Sdim = &APR_ARRAY_PUSH(headers, svn_repos__dumpfile_header_entry_t); 485299742Sdim 486299742Sdim h->key = apr_pstrdup(headers->pool, key); 487299742Sdim va_start(ap, val_fmt); 488299742Sdim h->val = apr_pvsprintf(headers->pool, val_fmt, ap); 489299742Sdim va_end(ap); 490299742Sdim} 491299742Sdim 492299742Sdimsvn_error_t * 493299742Sdimsvn_repos__dump_headers(svn_stream_t *stream, 494299742Sdim svn_repos__dumpfile_headers_t *headers, 495299742Sdim apr_pool_t *scratch_pool) 496299742Sdim{ 497299742Sdim int i; 498299742Sdim 499299742Sdim for (i = 0; i < headers->nelts; i++) 500299742Sdim { 501299742Sdim svn_repos__dumpfile_header_entry_t *h 502299742Sdim = &APR_ARRAY_IDX(headers, i, svn_repos__dumpfile_header_entry_t); 503299742Sdim 504299742Sdim SVN_ERR(svn_stream_printf(stream, scratch_pool, 505299742Sdim "%s: %s\n", h->key, h->val)); 506299742Sdim } 507299742Sdim 508299742Sdim /* End of headers */ 509299742Sdim SVN_ERR(svn_stream_puts(stream, "\n")); 510299742Sdim 511299742Sdim return SVN_NO_ERROR; 512299742Sdim} 513299742Sdim 514299742Sdimsvn_error_t * 515299742Sdimsvn_repos__dump_revision_record(svn_stream_t *dump_stream, 516299742Sdim svn_revnum_t revision, 517299742Sdim apr_hash_t *extra_headers, 518299742Sdim apr_hash_t *revprops, 519299742Sdim svn_boolean_t props_section_always, 520299742Sdim apr_pool_t *scratch_pool) 521299742Sdim{ 522299742Sdim svn_stringbuf_t *propstring = NULL; 523299742Sdim apr_hash_t *headers; 524299742Sdim 525299742Sdim if (extra_headers) 526299742Sdim headers = apr_hash_copy(scratch_pool, extra_headers); 527299742Sdim else 528299742Sdim headers = apr_hash_make(scratch_pool); 529299742Sdim 530299742Sdim /* ### someday write a revision-content-checksum */ 531299742Sdim 532299742Sdim svn_hash_sets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER, 533299742Sdim apr_psprintf(scratch_pool, "%ld", revision)); 534299742Sdim 535299742Sdim if (apr_hash_count(revprops) || props_section_always) 536299742Sdim { 537299742Sdim svn_stream_t *propstream; 538299742Sdim 539299742Sdim propstring = svn_stringbuf_create_empty(scratch_pool); 540299742Sdim propstream = svn_stream_from_stringbuf(propstring, scratch_pool); 541299742Sdim SVN_ERR(svn_hash_write2(revprops, propstream, "PROPS-END", scratch_pool)); 542299742Sdim SVN_ERR(svn_stream_close(propstream)); 543299742Sdim 544299742Sdim svn_hash_sets(headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, 545299742Sdim apr_psprintf(scratch_pool, 546299742Sdim "%" APR_SIZE_T_FMT, propstring->len)); 547299742Sdim } 548299742Sdim 549299742Sdim /* Write out a regular Content-length header for the benefit of 550299742Sdim non-Subversion RFC-822 parsers. */ 551299742Sdim svn_hash_sets(headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, 552299742Sdim apr_psprintf(scratch_pool, 553299742Sdim "%" APR_SIZE_T_FMT, propstring->len)); 554299742Sdim SVN_ERR(write_revision_headers(dump_stream, headers, scratch_pool)); 555299742Sdim 556299742Sdim /* End of headers */ 557299742Sdim SVN_ERR(svn_stream_puts(dump_stream, "\n")); 558299742Sdim 559299742Sdim /* Property data. */ 560299742Sdim if (propstring) 561299742Sdim { 562299742Sdim SVN_ERR(svn_stream_write(dump_stream, propstring->data, &propstring->len)); 563299742Sdim } 564299742Sdim 565299742Sdim /* put an end to revision */ 566299742Sdim SVN_ERR(svn_stream_puts(dump_stream, "\n")); 567299742Sdim 568299742Sdim return SVN_NO_ERROR; 569299742Sdim} 570299742Sdim 571299742Sdimsvn_error_t * 572299742Sdimsvn_repos__dump_node_record(svn_stream_t *dump_stream, 573299742Sdim svn_repos__dumpfile_headers_t *headers, 574299742Sdim svn_stringbuf_t *props_str, 575299742Sdim svn_boolean_t has_text, 576299742Sdim svn_filesize_t text_content_length, 577299742Sdim svn_boolean_t content_length_always, 578299742Sdim apr_pool_t *scratch_pool) 579299742Sdim{ 580299742Sdim svn_filesize_t content_length = 0; 581299742Sdim 582299742Sdim /* add content-length headers */ 583299742Sdim if (props_str) 584299742Sdim { 585299742Sdim svn_repos__dumpfile_header_pushf( 586299742Sdim headers, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH, 587299742Sdim "%" APR_SIZE_T_FMT, props_str->len); 588299742Sdim content_length += props_str->len; 589299742Sdim } 590299742Sdim if (has_text) 591299742Sdim { 592299742Sdim svn_repos__dumpfile_header_pushf( 593299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH, 594299742Sdim "%" SVN_FILESIZE_T_FMT, text_content_length); 595299742Sdim content_length += text_content_length; 596299742Sdim } 597299742Sdim if (content_length_always || props_str || has_text) 598299742Sdim { 599299742Sdim svn_repos__dumpfile_header_pushf( 600299742Sdim headers, SVN_REPOS_DUMPFILE_CONTENT_LENGTH, 601299742Sdim "%" SVN_FILESIZE_T_FMT, content_length); 602299742Sdim } 603299742Sdim 604299742Sdim /* write the headers */ 605299742Sdim SVN_ERR(svn_repos__dump_headers(dump_stream, headers, scratch_pool)); 606299742Sdim 607299742Sdim /* write the props */ 608299742Sdim if (props_str) 609299742Sdim { 610299742Sdim SVN_ERR(svn_stream_write(dump_stream, props_str->data, &props_str->len)); 611299742Sdim } 612299742Sdim return SVN_NO_ERROR; 613299742Sdim} 614299742Sdim 615299742Sdim/*----------------------------------------------------------------------*/ 616299742Sdim 617251881Speter/** An editor which dumps node-data in 'dumpfile format' to a file. **/ 618251881Speter 619251881Speter/* Look, mom! No file batons! */ 620251881Speter 621251881Speterstruct edit_baton 622251881Speter{ 623251881Speter /* The relpath which implicitly prepends all full paths coming into 624251881Speter this editor. This will almost always be "". */ 625251881Speter const char *path; 626251881Speter 627251881Speter /* The stream to dump to. */ 628251881Speter svn_stream_t *stream; 629251881Speter 630251881Speter /* Send feedback here, if non-NULL */ 631251881Speter svn_repos_notify_func_t notify_func; 632251881Speter void *notify_baton; 633251881Speter 634251881Speter /* The fs revision root, so we can read the contents of paths. */ 635251881Speter svn_fs_root_t *fs_root; 636251881Speter svn_revnum_t current_rev; 637251881Speter 638251881Speter /* The fs, so we can grab historic information if needed. */ 639251881Speter svn_fs_t *fs; 640251881Speter 641251881Speter /* True if dumped nodes should output deltas instead of full text. */ 642251881Speter svn_boolean_t use_deltas; 643251881Speter 644251881Speter /* True if this "dump" is in fact a verify. */ 645251881Speter svn_boolean_t verify; 646251881Speter 647299742Sdim /* True if checking UCS normalization during a verify. */ 648299742Sdim svn_boolean_t check_normalization; 649299742Sdim 650251881Speter /* The first revision dumped in this dumpstream. */ 651251881Speter svn_revnum_t oldest_dumped_rev; 652251881Speter 653251881Speter /* If not NULL, set to true if any references to revisions older than 654251881Speter OLDEST_DUMPED_REV were found in the dumpstream. */ 655251881Speter svn_boolean_t *found_old_reference; 656251881Speter 657251881Speter /* If not NULL, set to true if any mergeinfo was dumped which contains 658251881Speter revisions older than OLDEST_DUMPED_REV. */ 659251881Speter svn_boolean_t *found_old_mergeinfo; 660251881Speter 661299742Sdim /* Structure allows us to verify the paths currently being dumped. 662299742Sdim If NULL, validity checks are being skipped. */ 663299742Sdim path_tracker_t *path_tracker; 664251881Speter}; 665251881Speter 666251881Speterstruct dir_baton 667251881Speter{ 668251881Speter struct edit_baton *edit_baton; 669251881Speter 670251881Speter /* has this directory been written to the output stream? */ 671251881Speter svn_boolean_t written_out; 672251881Speter 673251881Speter /* the repository relpath associated with this directory */ 674251881Speter const char *path; 675251881Speter 676251881Speter /* The comparison repository relpath and revision of this directory. 677251881Speter If both of these are valid, use them as a source against which to 678251881Speter compare the directory instead of the default comparison source of 679251881Speter PATH in the previous revision. */ 680251881Speter const char *cmp_path; 681251881Speter svn_revnum_t cmp_rev; 682251881Speter 683251881Speter /* hash of paths that need to be deleted, though some -might- be 684251881Speter replaced. maps const char * paths to this dir_baton. (they're 685251881Speter full paths, because that's what the editor driver gives us. but 686251881Speter really, they're all within this directory.) */ 687251881Speter apr_hash_t *deleted_entries; 688251881Speter 689299742Sdim /* A flag indicating that new entries have been added to this 690299742Sdim directory in this revision. Used to optimize detection of UCS 691299742Sdim representation collisions; we will only check for that in 692299742Sdim revisions where new names appear in the directory. */ 693299742Sdim svn_boolean_t check_name_collision; 694299742Sdim 695251881Speter /* pool to be used for deleting the hash items */ 696251881Speter apr_pool_t *pool; 697251881Speter}; 698251881Speter 699251881Speter 700251881Speter/* Make a directory baton to represent the directory was path 701251881Speter (relative to EDIT_BATON's path) is PATH. 702251881Speter 703251881Speter CMP_PATH/CMP_REV are the path/revision against which this directory 704251881Speter should be compared for changes. If either is omitted (NULL for the 705251881Speter path, SVN_INVALID_REVNUM for the rev), just compare this directory 706251881Speter PATH against itself in the previous revision. 707251881Speter 708299742Sdim PB is the directory baton of this directory's parent, 709299742Sdim or NULL if this is the top-level directory of the edit. 710299742Sdim 711251881Speter Perform all allocations in POOL. */ 712251881Speterstatic struct dir_baton * 713251881Spetermake_dir_baton(const char *path, 714251881Speter const char *cmp_path, 715251881Speter svn_revnum_t cmp_rev, 716251881Speter void *edit_baton, 717299742Sdim struct dir_baton *pb, 718251881Speter apr_pool_t *pool) 719251881Speter{ 720251881Speter struct edit_baton *eb = edit_baton; 721251881Speter struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db)); 722251881Speter const char *full_path; 723251881Speter 724251881Speter /* A path relative to nothing? I don't think so. */ 725251881Speter SVN_ERR_ASSERT_NO_RETURN(!path || pb); 726251881Speter 727251881Speter /* Construct the full path of this node. */ 728251881Speter if (pb) 729251881Speter full_path = svn_relpath_join(eb->path, path, pool); 730251881Speter else 731251881Speter full_path = apr_pstrdup(pool, eb->path); 732251881Speter 733251881Speter /* Remove leading slashes from copyfrom paths. */ 734251881Speter if (cmp_path) 735251881Speter cmp_path = svn_relpath_canonicalize(cmp_path, pool); 736251881Speter 737251881Speter new_db->edit_baton = eb; 738251881Speter new_db->path = full_path; 739251881Speter new_db->cmp_path = cmp_path; 740251881Speter new_db->cmp_rev = cmp_rev; 741251881Speter new_db->written_out = FALSE; 742251881Speter new_db->deleted_entries = apr_hash_make(pool); 743299742Sdim new_db->check_name_collision = FALSE; 744251881Speter new_db->pool = pool; 745251881Speter 746251881Speter return new_db; 747251881Speter} 748251881Speter 749299742Sdimstatic svn_error_t * 750299742Sdimfetch_kind_func(svn_node_kind_t *kind, 751299742Sdim void *baton, 752299742Sdim const char *path, 753299742Sdim svn_revnum_t base_revision, 754299742Sdim apr_pool_t *scratch_pool); 755251881Speter 756299742Sdim/* Return an error when PATH in REVISION does not exist or is of a 757299742Sdim different kind than EXPECTED_KIND. If the latter is svn_node_unknown, 758299742Sdim skip that check. Use EB for context information. If REVISION is the 759299742Sdim current revision, use EB's path tracker to follow renames, deletions, 760299742Sdim etc. 761299742Sdim 762299742Sdim Use SCRATCH_POOL for temporary allocations. 763299742Sdim No-op if EB's path tracker has not been initialized. 764299742Sdim */ 765299742Sdimstatic svn_error_t * 766299742Sdimnode_must_exist(struct edit_baton *eb, 767299742Sdim const char *path, 768299742Sdim svn_revnum_t revision, 769299742Sdim svn_node_kind_t expected_kind, 770299742Sdim apr_pool_t *scratch_pool) 771299742Sdim{ 772299742Sdim svn_node_kind_t kind = svn_node_none; 773299742Sdim 774299742Sdim /* in case the caller is trying something stupid ... */ 775299742Sdim if (eb->path_tracker == NULL) 776299742Sdim return SVN_NO_ERROR; 777299742Sdim 778299742Sdim /* paths pertaining to the revision currently being processed must 779299742Sdim be translated / checked using our path tracker. */ 780299742Sdim if (revision == eb->path_tracker->revision) 781299742Sdim tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool); 782299742Sdim 783299742Sdim /* determine the node type (default: no such node) */ 784299742Sdim if (path) 785299742Sdim SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); 786299742Sdim 787299742Sdim /* check results */ 788299742Sdim if (kind == svn_node_none) 789299742Sdim return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL, 790299742Sdim _("Path '%s' not found in r%ld."), 791299742Sdim path, revision); 792299742Sdim 793299742Sdim if (expected_kind != kind && expected_kind != svn_node_unknown) 794299742Sdim return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 795299742Sdim _("Unexpected node kind %d for '%s' at r%ld. " 796299742Sdim "Expected kind was %d."), 797299742Sdim kind, path, revision, expected_kind); 798299742Sdim 799299742Sdim return SVN_NO_ERROR; 800299742Sdim} 801299742Sdim 802299742Sdim/* Return an error when PATH exists in REVISION. Use EB for context 803299742Sdim information. If REVISION is the current revision, use EB's path 804299742Sdim tracker to follow renames, deletions, etc. 805299742Sdim 806299742Sdim Use SCRATCH_POOL for temporary allocations. 807299742Sdim No-op if EB's path tracker has not been initialized. 808299742Sdim */ 809299742Sdimstatic svn_error_t * 810299742Sdimnode_must_not_exist(struct edit_baton *eb, 811299742Sdim const char *path, 812299742Sdim svn_revnum_t revision, 813299742Sdim apr_pool_t *scratch_pool) 814299742Sdim{ 815299742Sdim svn_node_kind_t kind = svn_node_none; 816299742Sdim 817299742Sdim /* in case the caller is trying something stupid ... */ 818299742Sdim if (eb->path_tracker == NULL) 819299742Sdim return SVN_NO_ERROR; 820299742Sdim 821299742Sdim /* paths pertaining to the revision currently being processed must 822299742Sdim be translated / checked using our path tracker. */ 823299742Sdim if (revision == eb->path_tracker->revision) 824299742Sdim tracker_lookup(&path, &revision, eb->path_tracker, path, scratch_pool); 825299742Sdim 826299742Sdim /* determine the node type (default: no such node) */ 827299742Sdim if (path) 828299742Sdim SVN_ERR(fetch_kind_func(&kind, eb, path, revision, scratch_pool)); 829299742Sdim 830299742Sdim /* check results */ 831299742Sdim if (kind != svn_node_none) 832299742Sdim return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL, 833299742Sdim _("Path '%s' exists in r%ld."), 834299742Sdim path, revision); 835299742Sdim 836299742Sdim return SVN_NO_ERROR; 837299742Sdim} 838299742Sdim 839269847Speter/* If the mergeinfo in MERGEINFO_STR refers to any revisions older than 840269847Speter * OLDEST_DUMPED_REV, issue a warning and set *FOUND_OLD_MERGEINFO to TRUE, 841269847Speter * otherwise leave *FOUND_OLD_MERGEINFO unchanged. 842269847Speter */ 843269847Speterstatic svn_error_t * 844269847Speterverify_mergeinfo_revisions(svn_boolean_t *found_old_mergeinfo, 845269847Speter const char *mergeinfo_str, 846269847Speter svn_revnum_t oldest_dumped_rev, 847269847Speter svn_repos_notify_func_t notify_func, 848269847Speter void *notify_baton, 849269847Speter apr_pool_t *pool) 850269847Speter{ 851269847Speter svn_mergeinfo_t mergeinfo, old_mergeinfo; 852269847Speter 853269847Speter SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str, pool)); 854269847Speter SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges( 855269847Speter &old_mergeinfo, mergeinfo, 856269847Speter oldest_dumped_rev - 1, 0, 857269847Speter TRUE, pool, pool)); 858269847Speter 859269847Speter if (apr_hash_count(old_mergeinfo)) 860269847Speter { 861299742Sdim notify_warning(pool, notify_func, notify_baton, 862299742Sdim svn_repos_notify_warning_found_old_mergeinfo, 863299742Sdim _("Mergeinfo referencing revision(s) prior " 864299742Sdim "to the oldest dumped revision (r%ld). " 865299742Sdim "Loading this dump may result in invalid " 866299742Sdim "mergeinfo."), 867299742Sdim oldest_dumped_rev); 868269847Speter 869269847Speter if (found_old_mergeinfo) 870269847Speter *found_old_mergeinfo = TRUE; 871269847Speter } 872269847Speter 873269847Speter return SVN_NO_ERROR; 874269847Speter} 875269847Speter 876299742Sdim/* Unique string pointers used by verify_mergeinfo_normalization() 877299742Sdim and check_name_collision() */ 878299742Sdimstatic const char normalized_unique[] = "normalized_unique"; 879299742Sdimstatic const char normalized_collision[] = "normalized_collision"; 880269847Speter 881299742Sdim 882299742Sdim/* Baton for extract_mergeinfo_paths */ 883299742Sdimstruct extract_mergeinfo_paths_baton 884299742Sdim{ 885299742Sdim apr_hash_t *result; 886299742Sdim svn_boolean_t normalize; 887299742Sdim svn_membuf_t buffer; 888299742Sdim}; 889299742Sdim 890299742Sdim/* Hash iterator that uniquifies all keys into a single hash table, 891299742Sdim optionally normalizing them first. */ 892299742Sdimstatic svn_error_t * 893299742Sdimextract_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, 894299742Sdim void *val, apr_pool_t *iterpool) 895299742Sdim{ 896299742Sdim struct extract_mergeinfo_paths_baton *const xb = baton; 897299742Sdim if (xb->normalize) 898299742Sdim { 899299742Sdim const char *normkey; 900299742Sdim SVN_ERR(svn_utf__normalize(&normkey, key, klen, &xb->buffer)); 901299742Sdim svn_hash_sets(xb->result, 902299742Sdim apr_pstrdup(xb->buffer.pool, normkey), 903299742Sdim normalized_unique); 904299742Sdim } 905299742Sdim else 906299742Sdim apr_hash_set(xb->result, 907299742Sdim apr_pmemdup(xb->buffer.pool, key, klen + 1), klen, 908299742Sdim normalized_unique); 909299742Sdim return SVN_NO_ERROR; 910299742Sdim} 911299742Sdim 912299742Sdim/* Baton for filter_mergeinfo_paths */ 913299742Sdimstruct filter_mergeinfo_paths_baton 914299742Sdim{ 915299742Sdim apr_hash_t *paths; 916299742Sdim}; 917299742Sdim 918299742Sdim/* Compare two sets of denormalized paths from mergeinfo entries, 919299742Sdim removing duplicates. */ 920299742Sdimstatic svn_error_t * 921299742Sdimfilter_mergeinfo_paths(void *baton, const void *key, apr_ssize_t klen, 922299742Sdim void *val, apr_pool_t *iterpool) 923299742Sdim{ 924299742Sdim struct filter_mergeinfo_paths_baton *const fb = baton; 925299742Sdim 926299742Sdim if (apr_hash_get(fb->paths, key, klen)) 927299742Sdim apr_hash_set(fb->paths, key, klen, NULL); 928299742Sdim 929299742Sdim return SVN_NO_ERROR; 930299742Sdim} 931299742Sdim 932299742Sdim/* Baton used by the check_mergeinfo_normalization hash iterator. */ 933299742Sdimstruct verify_mergeinfo_normalization_baton 934299742Sdim{ 935299742Sdim const char* path; 936299742Sdim apr_hash_t *normalized_paths; 937299742Sdim svn_membuf_t buffer; 938299742Sdim svn_repos_notify_func_t notify_func; 939299742Sdim void *notify_baton; 940299742Sdim}; 941299742Sdim 942299742Sdim/* Hash iterator that verifies normalization and collision of paths in 943299742Sdim an svn:mergeinfo property. */ 944299742Sdimstatic svn_error_t * 945299742Sdimverify_mergeinfo_normalization(void *baton, const void *key, apr_ssize_t klen, 946299742Sdim void *val, apr_pool_t *iterpool) 947299742Sdim{ 948299742Sdim struct verify_mergeinfo_normalization_baton *const vb = baton; 949299742Sdim 950299742Sdim const char *const path = key; 951299742Sdim const char *normpath; 952299742Sdim const char *found; 953299742Sdim 954299742Sdim SVN_ERR(svn_utf__normalize(&normpath, path, klen, &vb->buffer)); 955299742Sdim found = svn_hash_gets(vb->normalized_paths, normpath); 956299742Sdim if (!found) 957299742Sdim svn_hash_sets(vb->normalized_paths, 958299742Sdim apr_pstrdup(vb->buffer.pool, normpath), 959299742Sdim normalized_unique); 960299742Sdim else if (found == normalized_collision) 961299742Sdim /* Skip already reported collision */; 962299742Sdim else 963299742Sdim { 964299742Sdim /* Report path collision in mergeinfo */ 965299742Sdim svn_hash_sets(vb->normalized_paths, 966299742Sdim apr_pstrdup(vb->buffer.pool, normpath), 967299742Sdim normalized_collision); 968299742Sdim 969299742Sdim notify_warning(iterpool, vb->notify_func, vb->notify_baton, 970299742Sdim svn_repos_notify_warning_mergeinfo_collision, 971299742Sdim _("Duplicate representation of path '%s'" 972299742Sdim " in %s property of '%s'"), 973299742Sdim normpath, SVN_PROP_MERGEINFO, vb->path); 974299742Sdim } 975299742Sdim return SVN_NO_ERROR; 976299742Sdim} 977299742Sdim 978299742Sdim/* Check UCS normalization of mergeinfo for PATH. NEW_MERGEINFO is the 979299742Sdim svn:mergeinfo property value being set; OLD_MERGEINFO is the 980299742Sdim previous property value, which may be NULL. Only the paths that 981299742Sdim were added in are checked, including collision checks. This 982299742Sdim minimizes the number of notifications we generate for a given 983299742Sdim mergeinfo property. */ 984299742Sdimstatic svn_error_t * 985299742Sdimcheck_mergeinfo_normalization(const char *path, 986299742Sdim const char *new_mergeinfo, 987299742Sdim const char *old_mergeinfo, 988299742Sdim svn_repos_notify_func_t notify_func, 989299742Sdim void *notify_baton, 990299742Sdim apr_pool_t *pool) 991299742Sdim{ 992299742Sdim svn_mergeinfo_t mergeinfo; 993299742Sdim apr_hash_t *normalized_paths; 994299742Sdim apr_hash_t *added_paths; 995299742Sdim struct extract_mergeinfo_paths_baton extract_baton; 996299742Sdim struct verify_mergeinfo_normalization_baton verify_baton; 997299742Sdim 998299742Sdim SVN_ERR(svn_mergeinfo_parse(&mergeinfo, new_mergeinfo, pool)); 999299742Sdim 1000299742Sdim extract_baton.result = apr_hash_make(pool); 1001299742Sdim extract_baton.normalize = FALSE; 1002299742Sdim svn_membuf__create(&extract_baton.buffer, 0, pool); 1003299742Sdim SVN_ERR(svn_iter_apr_hash(NULL, mergeinfo, 1004299742Sdim extract_mergeinfo_paths, 1005299742Sdim &extract_baton, pool)); 1006299742Sdim added_paths = extract_baton.result; 1007299742Sdim 1008299742Sdim if (old_mergeinfo) 1009299742Sdim { 1010299742Sdim struct filter_mergeinfo_paths_baton filter_baton; 1011299742Sdim svn_mergeinfo_t oldinfo; 1012299742Sdim 1013299742Sdim extract_baton.result = apr_hash_make(pool); 1014299742Sdim extract_baton.normalize = TRUE; 1015299742Sdim SVN_ERR(svn_mergeinfo_parse(&oldinfo, old_mergeinfo, pool)); 1016299742Sdim SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, 1017299742Sdim extract_mergeinfo_paths, 1018299742Sdim &extract_baton, pool)); 1019299742Sdim normalized_paths = extract_baton.result; 1020299742Sdim 1021299742Sdim filter_baton.paths = added_paths; 1022299742Sdim SVN_ERR(svn_iter_apr_hash(NULL, oldinfo, 1023299742Sdim filter_mergeinfo_paths, 1024299742Sdim &filter_baton, pool)); 1025299742Sdim } 1026299742Sdim else 1027299742Sdim normalized_paths = apr_hash_make(pool); 1028299742Sdim 1029299742Sdim verify_baton.path = path; 1030299742Sdim verify_baton.normalized_paths = normalized_paths; 1031299742Sdim verify_baton.buffer = extract_baton.buffer; 1032299742Sdim verify_baton.notify_func = notify_func; 1033299742Sdim verify_baton.notify_baton = notify_baton; 1034299742Sdim SVN_ERR(svn_iter_apr_hash(NULL, added_paths, 1035299742Sdim verify_mergeinfo_normalization, 1036299742Sdim &verify_baton, pool)); 1037299742Sdim 1038299742Sdim return SVN_NO_ERROR; 1039299742Sdim} 1040299742Sdim 1041299742Sdim 1042299742Sdim/* A special case of dump_node(), for a delete record. 1043299742Sdim * 1044299742Sdim * The only thing special about this version is it only writes one blank 1045299742Sdim * line, not two, after the headers. Why? Historical precedent for the 1046299742Sdim * case where a delete record is used as part of a (delete + add-with-history) 1047299742Sdim * in implementing a replacement. 1048299742Sdim * 1049299742Sdim * Also it doesn't do a path-tracker check. 1050299742Sdim */ 1051299742Sdimstatic svn_error_t * 1052299742Sdimdump_node_delete(svn_stream_t *stream, 1053299742Sdim const char *node_relpath, 1054299742Sdim apr_pool_t *pool) 1055299742Sdim{ 1056299742Sdim svn_repos__dumpfile_headers_t *headers 1057299742Sdim = svn_repos__dumpfile_headers_create(pool); 1058299742Sdim 1059299742Sdim /* Node-path: ... */ 1060299742Sdim svn_repos__dumpfile_header_push( 1061299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath); 1062299742Sdim 1063299742Sdim /* Node-action: delete */ 1064299742Sdim svn_repos__dumpfile_header_push( 1065299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); 1066299742Sdim 1067299742Sdim SVN_ERR(svn_repos__dump_headers(stream, headers, pool)); 1068299742Sdim return SVN_NO_ERROR; 1069299742Sdim} 1070299742Sdim 1071251881Speter/* This helper is the main "meat" of the editor -- it does all the 1072251881Speter work of writing a node record. 1073251881Speter 1074251881Speter Write out a node record for PATH of type KIND under EB->FS_ROOT. 1075251881Speter ACTION describes what is happening to the node (see enum svn_node_action). 1076299742Sdim Write record to writable EB->STREAM. 1077251881Speter 1078251881Speter If the node was itself copied, IS_COPY is TRUE and the 1079251881Speter path/revision of the copy source are in CMP_PATH/CMP_REV. If 1080251881Speter IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part 1081251881Speter of a copied subtree. 1082251881Speter */ 1083251881Speterstatic svn_error_t * 1084251881Speterdump_node(struct edit_baton *eb, 1085251881Speter const char *path, 1086251881Speter svn_node_kind_t kind, 1087251881Speter enum svn_node_action action, 1088251881Speter svn_boolean_t is_copy, 1089251881Speter const char *cmp_path, 1090251881Speter svn_revnum_t cmp_rev, 1091251881Speter apr_pool_t *pool) 1092251881Speter{ 1093251881Speter svn_stringbuf_t *propstring; 1094251881Speter apr_size_t len; 1095251881Speter svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE; 1096251881Speter const char *compare_path = path; 1097251881Speter svn_revnum_t compare_rev = eb->current_rev - 1; 1098251881Speter svn_fs_root_t *compare_root = NULL; 1099251881Speter apr_file_t *delta_file = NULL; 1100299742Sdim svn_repos__dumpfile_headers_t *headers 1101299742Sdim = svn_repos__dumpfile_headers_create(pool); 1102299742Sdim svn_filesize_t textlen; 1103251881Speter 1104251881Speter /* Maybe validate the path. */ 1105251881Speter if (eb->verify || eb->notify_func) 1106251881Speter { 1107251881Speter svn_error_t *err = svn_fs__path_valid(path, pool); 1108251881Speter 1109251881Speter if (err) 1110251881Speter { 1111251881Speter if (eb->notify_func) 1112251881Speter { 1113251881Speter char errbuf[512]; /* ### svn_strerror() magic number */ 1114251881Speter 1115299742Sdim notify_warning(pool, eb->notify_func, eb->notify_baton, 1116299742Sdim svn_repos_notify_warning_invalid_fspath, 1117299742Sdim _("E%06d: While validating fspath '%s': %s"), 1118299742Sdim err->apr_err, path, 1119299742Sdim svn_err_best_message(err, errbuf, sizeof(errbuf))); 1120251881Speter } 1121251881Speter 1122251881Speter /* Return the error in addition to notifying about it. */ 1123251881Speter if (eb->verify) 1124251881Speter return svn_error_trace(err); 1125251881Speter else 1126251881Speter svn_error_clear(err); 1127251881Speter } 1128251881Speter } 1129251881Speter 1130251881Speter /* Write out metadata headers for this file node. */ 1131299742Sdim svn_repos__dumpfile_header_push( 1132299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_PATH, path); 1133251881Speter if (kind == svn_node_file) 1134299742Sdim svn_repos__dumpfile_header_push( 1135299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file"); 1136251881Speter else if (kind == svn_node_dir) 1137299742Sdim svn_repos__dumpfile_header_push( 1138299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir"); 1139251881Speter 1140251881Speter /* Remove leading slashes from copyfrom paths. */ 1141251881Speter if (cmp_path) 1142251881Speter cmp_path = svn_relpath_canonicalize(cmp_path, pool); 1143251881Speter 1144251881Speter /* Validate the comparison path/rev. */ 1145251881Speter if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev)) 1146251881Speter { 1147251881Speter compare_path = cmp_path; 1148251881Speter compare_rev = cmp_rev; 1149251881Speter } 1150251881Speter 1151299742Sdim switch (action) 1152251881Speter { 1153299742Sdim case svn_node_action_change: 1154299742Sdim if (eb->path_tracker) 1155299742Sdim SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool), 1156299742Sdim apr_psprintf(pool, _("Change invalid path '%s' in r%ld"), 1157299742Sdim path, eb->current_rev)); 1158251881Speter 1159299742Sdim svn_repos__dumpfile_header_push( 1160299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change"); 1161299742Sdim 1162251881Speter /* either the text or props changed, or possibly both. */ 1163251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 1164251881Speter svn_fs_root_fs(eb->fs_root), 1165251881Speter compare_rev, pool)); 1166251881Speter 1167251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 1168251881Speter compare_root, compare_path, 1169251881Speter eb->fs_root, path, pool)); 1170251881Speter if (kind == svn_node_file) 1171251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 1172251881Speter compare_root, compare_path, 1173251881Speter eb->fs_root, path, pool)); 1174299742Sdim break; 1175299742Sdim 1176299742Sdim case svn_node_action_delete: 1177299742Sdim if (eb->path_tracker) 1178299742Sdim { 1179299742Sdim SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, kind, pool), 1180299742Sdim apr_psprintf(pool, _("Deleting invalid path '%s' in r%ld"), 1181299742Sdim path, eb->current_rev)); 1182299742Sdim tracker_path_delete(eb->path_tracker, path); 1183299742Sdim } 1184299742Sdim 1185299742Sdim svn_repos__dumpfile_header_push( 1186299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete"); 1187299742Sdim 1188299742Sdim /* we can leave this routine quietly now, don't need to dump 1189299742Sdim any content. */ 1190299742Sdim must_dump_text = FALSE; 1191299742Sdim must_dump_props = FALSE; 1192299742Sdim break; 1193299742Sdim 1194299742Sdim case svn_node_action_replace: 1195299742Sdim if (eb->path_tracker) 1196299742Sdim SVN_ERR_W(node_must_exist(eb, path, eb->current_rev, 1197299742Sdim svn_node_unknown, pool), 1198299742Sdim apr_psprintf(pool, 1199299742Sdim _("Replacing non-existent path '%s' in r%ld"), 1200299742Sdim path, eb->current_rev)); 1201299742Sdim 1202251881Speter if (! is_copy) 1203251881Speter { 1204299742Sdim if (eb->path_tracker) 1205299742Sdim tracker_path_replace(eb->path_tracker, path); 1206299742Sdim 1207251881Speter /* a simple delete+add, implied by a single 'replace' action. */ 1208299742Sdim svn_repos__dumpfile_header_push( 1209299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace"); 1210251881Speter 1211251881Speter /* definitely need to dump all content for a replace. */ 1212251881Speter if (kind == svn_node_file) 1213251881Speter must_dump_text = TRUE; 1214251881Speter must_dump_props = TRUE; 1215299742Sdim break; 1216251881Speter } 1217251881Speter else 1218251881Speter { 1219251881Speter /* more complex: delete original, then add-with-history. */ 1220299742Sdim /* ### Why not write a 'replace' record? Don't know. */ 1221251881Speter 1222299742Sdim if (eb->path_tracker) 1223299742Sdim { 1224299742Sdim tracker_path_delete(eb->path_tracker, path); 1225299742Sdim } 1226251881Speter 1227299742Sdim /* ### Unusually, we end this 'delete' node record with only a single 1228299742Sdim blank line after the header block -- no extra blank line. */ 1229299742Sdim SVN_ERR(dump_node_delete(eb->stream, path, pool)); 1230251881Speter 1231299742Sdim /* The remaining action is a non-replacing add-with-history */ 1232299742Sdim /* action = svn_node_action_add; */ 1233251881Speter } 1234299742Sdim /* FALL THROUGH to 'add' */ 1235251881Speter 1236299742Sdim case svn_node_action_add: 1237299742Sdim if (eb->path_tracker) 1238299742Sdim SVN_ERR_W(node_must_not_exist(eb, path, eb->current_rev, pool), 1239299742Sdim apr_psprintf(pool, 1240299742Sdim _("Adding already existing path '%s' in r%ld"), 1241299742Sdim path, eb->current_rev)); 1242251881Speter 1243299742Sdim svn_repos__dumpfile_header_push( 1244299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add"); 1245299742Sdim 1246251881Speter if (! is_copy) 1247251881Speter { 1248299742Sdim if (eb->path_tracker) 1249299742Sdim tracker_path_add(eb->path_tracker, path); 1250299742Sdim 1251251881Speter /* Dump all contents for a simple 'add'. */ 1252251881Speter if (kind == svn_node_file) 1253251881Speter must_dump_text = TRUE; 1254251881Speter must_dump_props = TRUE; 1255251881Speter } 1256251881Speter else 1257251881Speter { 1258299742Sdim if (eb->path_tracker) 1259299742Sdim { 1260299742Sdim SVN_ERR_W(node_must_exist(eb, compare_path, compare_rev, 1261299742Sdim kind, pool), 1262299742Sdim apr_psprintf(pool, 1263299742Sdim _("Copying from invalid path to " 1264299742Sdim "'%s' in r%ld"), 1265299742Sdim path, eb->current_rev)); 1266299742Sdim tracker_path_copy(eb->path_tracker, path, compare_path, 1267299742Sdim compare_rev); 1268299742Sdim } 1269299742Sdim 1270251881Speter if (!eb->verify && cmp_rev < eb->oldest_dumped_rev 1271251881Speter && eb->notify_func) 1272251881Speter { 1273299742Sdim notify_warning(pool, eb->notify_func, eb->notify_baton, 1274299742Sdim svn_repos_notify_warning_found_old_reference, 1275299742Sdim _("Referencing data in revision %ld," 1276299742Sdim " which is older than the oldest" 1277299742Sdim " dumped revision (r%ld). Loading this dump" 1278299742Sdim " into an empty repository" 1279299742Sdim " will fail."), 1280299742Sdim cmp_rev, eb->oldest_dumped_rev); 1281251881Speter if (eb->found_old_reference) 1282251881Speter *eb->found_old_reference = TRUE; 1283251881Speter } 1284251881Speter 1285299742Sdim svn_repos__dumpfile_header_pushf( 1286299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", cmp_rev); 1287299742Sdim svn_repos__dumpfile_header_push( 1288299742Sdim headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, cmp_path); 1289251881Speter 1290251881Speter SVN_ERR(svn_fs_revision_root(&compare_root, 1291251881Speter svn_fs_root_fs(eb->fs_root), 1292251881Speter compare_rev, pool)); 1293251881Speter 1294251881Speter /* Need to decide if the copied node had any extra textual or 1295251881Speter property mods as well. */ 1296251881Speter SVN_ERR(svn_fs_props_changed(&must_dump_props, 1297251881Speter compare_root, compare_path, 1298251881Speter eb->fs_root, path, pool)); 1299251881Speter if (kind == svn_node_file) 1300251881Speter { 1301251881Speter svn_checksum_t *checksum; 1302251881Speter const char *hex_digest; 1303251881Speter SVN_ERR(svn_fs_contents_changed(&must_dump_text, 1304251881Speter compare_root, compare_path, 1305251881Speter eb->fs_root, path, pool)); 1306251881Speter 1307251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 1308251881Speter compare_root, compare_path, 1309251881Speter FALSE, pool)); 1310251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1311251881Speter if (hex_digest) 1312299742Sdim svn_repos__dumpfile_header_push( 1313299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5, hex_digest); 1314251881Speter 1315251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1316251881Speter compare_root, compare_path, 1317251881Speter FALSE, pool)); 1318251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1319251881Speter if (hex_digest) 1320299742Sdim svn_repos__dumpfile_header_push( 1321299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1, hex_digest); 1322251881Speter } 1323251881Speter } 1324299742Sdim break; 1325251881Speter } 1326251881Speter 1327251881Speter if ((! must_dump_text) && (! must_dump_props)) 1328251881Speter { 1329251881Speter /* If we're not supposed to dump text or props, so be it, we can 1330251881Speter just go home. However, if either one needs to be dumped, 1331251881Speter then our dumpstream format demands that at a *minimum*, we 1332251881Speter see a lone "PROPS-END" as a divider between text and props 1333251881Speter content within the content-block. */ 1334299742Sdim SVN_ERR(svn_repos__dump_headers(eb->stream, headers, pool)); 1335299742Sdim len = 1; 1336299742Sdim return svn_stream_write(eb->stream, "\n", &len); /* ### needed? */ 1337251881Speter } 1338251881Speter 1339251881Speter /*** Start prepping content to dump... ***/ 1340251881Speter 1341251881Speter /* If we are supposed to dump properties, write out a property 1342251881Speter length header and generate a stringbuf that contains those 1343251881Speter property values here. */ 1344251881Speter if (must_dump_props) 1345251881Speter { 1346251881Speter apr_hash_t *prophash, *oldhash = NULL; 1347251881Speter svn_stream_t *propstream; 1348251881Speter 1349251881Speter SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool)); 1350251881Speter 1351251881Speter /* If this is a partial dump, then issue a warning if we dump mergeinfo 1352251881Speter properties that refer to revisions older than the first revision 1353251881Speter dumped. */ 1354251881Speter if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1) 1355251881Speter { 1356251881Speter svn_string_t *mergeinfo_str = svn_hash_gets(prophash, 1357251881Speter SVN_PROP_MERGEINFO); 1358251881Speter if (mergeinfo_str) 1359251881Speter { 1360269847Speter /* An error in verifying the mergeinfo must not prevent dumping 1361269847Speter the data. Ignore any such error. */ 1362269847Speter svn_error_clear(verify_mergeinfo_revisions( 1363269847Speter eb->found_old_mergeinfo, 1364269847Speter mergeinfo_str->data, eb->oldest_dumped_rev, 1365269847Speter eb->notify_func, eb->notify_baton, 1366269847Speter pool)); 1367251881Speter } 1368251881Speter } 1369251881Speter 1370299742Sdim /* If we're checking UCS normalization, also parse any changed 1371299742Sdim mergeinfo and warn about denormalized paths and name 1372299742Sdim collisions there. */ 1373299742Sdim if (eb->verify && eb->check_normalization && eb->notify_func) 1374299742Sdim { 1375299742Sdim /* N.B.: This hash lookup happens only once; the conditions 1376299742Sdim for verifying historic mergeinfo references and checking 1377299742Sdim UCS normalization are mutually exclusive. */ 1378299742Sdim svn_string_t *mergeinfo_str = svn_hash_gets(prophash, 1379299742Sdim SVN_PROP_MERGEINFO); 1380299742Sdim if (mergeinfo_str) 1381299742Sdim { 1382299742Sdim svn_string_t *oldinfo_str = NULL; 1383299742Sdim if (compare_root) 1384299742Sdim { 1385299742Sdim SVN_ERR(svn_fs_node_proplist(&oldhash, 1386299742Sdim compare_root, compare_path, 1387299742Sdim pool)); 1388299742Sdim oldinfo_str = svn_hash_gets(oldhash, SVN_PROP_MERGEINFO); 1389299742Sdim } 1390299742Sdim SVN_ERR(check_mergeinfo_normalization( 1391299742Sdim path, mergeinfo_str->data, 1392299742Sdim (oldinfo_str ? oldinfo_str->data : NULL), 1393299742Sdim eb->notify_func, eb->notify_baton, pool)); 1394299742Sdim } 1395299742Sdim } 1396299742Sdim 1397251881Speter if (eb->use_deltas && compare_root) 1398251881Speter { 1399251881Speter /* Fetch the old property hash to diff against and output a header 1400251881Speter saying that our property contents are a delta. */ 1401299742Sdim if (!oldhash) /* May have been set for normalization check */ 1402299742Sdim SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path, 1403299742Sdim pool)); 1404299742Sdim svn_repos__dumpfile_header_push( 1405299742Sdim headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true"); 1406251881Speter } 1407251881Speter else 1408251881Speter oldhash = apr_hash_make(pool); 1409251881Speter propstring = svn_stringbuf_create_ensure(0, pool); 1410251881Speter propstream = svn_stream_from_stringbuf(propstring, pool); 1411251881Speter SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream, 1412251881Speter "PROPS-END", pool)); 1413251881Speter SVN_ERR(svn_stream_close(propstream)); 1414251881Speter } 1415251881Speter 1416251881Speter /* If we are supposed to dump text, write out a text length header 1417251881Speter here, and an MD5 checksum (if available). */ 1418251881Speter if (must_dump_text && (kind == svn_node_file)) 1419251881Speter { 1420251881Speter svn_checksum_t *checksum; 1421251881Speter const char *hex_digest; 1422251881Speter 1423251881Speter if (eb->use_deltas) 1424251881Speter { 1425251881Speter /* Compute the text delta now and write it into a temporary 1426251881Speter file, so that we can find its length. Output a header 1427251881Speter saying our text contents are a delta. */ 1428251881Speter SVN_ERR(store_delta(&delta_file, &textlen, compare_root, 1429251881Speter compare_path, eb->fs_root, path, pool)); 1430299742Sdim svn_repos__dumpfile_header_push( 1431299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true"); 1432251881Speter 1433251881Speter if (compare_root) 1434251881Speter { 1435251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 1436251881Speter compare_root, compare_path, 1437251881Speter FALSE, pool)); 1438251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1439251881Speter if (hex_digest) 1440299742Sdim svn_repos__dumpfile_header_push( 1441299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, hex_digest); 1442251881Speter 1443251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1444251881Speter compare_root, compare_path, 1445251881Speter FALSE, pool)); 1446251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1447251881Speter if (hex_digest) 1448299742Sdim svn_repos__dumpfile_header_push( 1449299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1, hex_digest); 1450251881Speter } 1451251881Speter } 1452251881Speter else 1453251881Speter { 1454251881Speter /* Just fetch the length of the file. */ 1455251881Speter SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool)); 1456251881Speter } 1457251881Speter 1458251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5, 1459251881Speter eb->fs_root, path, FALSE, pool)); 1460251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1461251881Speter if (hex_digest) 1462299742Sdim svn_repos__dumpfile_header_push( 1463299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, hex_digest); 1464251881Speter 1465251881Speter SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1, 1466251881Speter eb->fs_root, path, FALSE, pool)); 1467251881Speter hex_digest = svn_checksum_to_cstring(checksum, pool); 1468251881Speter if (hex_digest) 1469299742Sdim svn_repos__dumpfile_header_push( 1470299742Sdim headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1, hex_digest); 1471251881Speter } 1472251881Speter 1473251881Speter /* 'Content-length:' is the last header before we dump the content, 1474251881Speter and is the sum of the text and prop contents lengths. We write 1475251881Speter this only for the benefit of non-Subversion RFC-822 parsers. */ 1476299742Sdim SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, 1477299742Sdim must_dump_props ? propstring : NULL, 1478299742Sdim must_dump_text, 1479299742Sdim must_dump_text ? textlen : 0, 1480299742Sdim TRUE /*content_length_always*/, 1481299742Sdim pool)); 1482251881Speter 1483251881Speter /* Dump text content */ 1484251881Speter if (must_dump_text && (kind == svn_node_file)) 1485251881Speter { 1486251881Speter svn_stream_t *contents; 1487251881Speter 1488251881Speter if (delta_file) 1489251881Speter { 1490251881Speter /* Make sure to close the underlying file when the stream is 1491251881Speter closed. */ 1492251881Speter contents = svn_stream_from_aprfile2(delta_file, FALSE, pool); 1493251881Speter } 1494251881Speter else 1495251881Speter SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool)); 1496251881Speter 1497251881Speter SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool), 1498251881Speter NULL, NULL, pool)); 1499251881Speter } 1500251881Speter 1501251881Speter len = 2; 1502251881Speter return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */ 1503251881Speter} 1504251881Speter 1505251881Speter 1506251881Speterstatic svn_error_t * 1507251881Speteropen_root(void *edit_baton, 1508251881Speter svn_revnum_t base_revision, 1509251881Speter apr_pool_t *pool, 1510251881Speter void **root_baton) 1511251881Speter{ 1512251881Speter *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM, 1513299742Sdim edit_baton, NULL, pool); 1514251881Speter return SVN_NO_ERROR; 1515251881Speter} 1516251881Speter 1517251881Speter 1518251881Speterstatic svn_error_t * 1519251881Speterdelete_entry(const char *path, 1520251881Speter svn_revnum_t revision, 1521251881Speter void *parent_baton, 1522251881Speter apr_pool_t *pool) 1523251881Speter{ 1524251881Speter struct dir_baton *pb = parent_baton; 1525251881Speter const char *mypath = apr_pstrdup(pb->pool, path); 1526251881Speter 1527251881Speter /* remember this path needs to be deleted. */ 1528251881Speter svn_hash_sets(pb->deleted_entries, mypath, pb); 1529251881Speter 1530251881Speter return SVN_NO_ERROR; 1531251881Speter} 1532251881Speter 1533251881Speter 1534251881Speterstatic svn_error_t * 1535251881Speteradd_directory(const char *path, 1536251881Speter void *parent_baton, 1537251881Speter const char *copyfrom_path, 1538251881Speter svn_revnum_t copyfrom_rev, 1539251881Speter apr_pool_t *pool, 1540251881Speter void **child_baton) 1541251881Speter{ 1542251881Speter struct dir_baton *pb = parent_baton; 1543251881Speter struct edit_baton *eb = pb->edit_baton; 1544299742Sdim void *was_deleted; 1545251881Speter svn_boolean_t is_copy = FALSE; 1546251881Speter struct dir_baton *new_db 1547299742Sdim = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, pool); 1548251881Speter 1549251881Speter /* This might be a replacement -- is the path already deleted? */ 1550299742Sdim was_deleted = svn_hash_gets(pb->deleted_entries, path); 1551251881Speter 1552251881Speter /* Detect an add-with-history. */ 1553251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 1554251881Speter 1555251881Speter /* Dump the node. */ 1556251881Speter SVN_ERR(dump_node(eb, path, 1557251881Speter svn_node_dir, 1558299742Sdim was_deleted ? svn_node_action_replace : svn_node_action_add, 1559251881Speter is_copy, 1560251881Speter is_copy ? copyfrom_path : NULL, 1561251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 1562251881Speter pool)); 1563251881Speter 1564299742Sdim if (was_deleted) 1565251881Speter /* Delete the path, it's now been dumped. */ 1566251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 1567251881Speter 1568299742Sdim /* Check for normalized name clashes, but only if this is actually a 1569299742Sdim new name in the parent, not a replacement. */ 1570299742Sdim if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func) 1571299742Sdim { 1572299742Sdim pb->check_name_collision = TRUE; 1573299742Sdim } 1574299742Sdim 1575251881Speter new_db->written_out = TRUE; 1576251881Speter 1577251881Speter *child_baton = new_db; 1578251881Speter return SVN_NO_ERROR; 1579251881Speter} 1580251881Speter 1581251881Speter 1582251881Speterstatic svn_error_t * 1583251881Speteropen_directory(const char *path, 1584251881Speter void *parent_baton, 1585251881Speter svn_revnum_t base_revision, 1586251881Speter apr_pool_t *pool, 1587251881Speter void **child_baton) 1588251881Speter{ 1589251881Speter struct dir_baton *pb = parent_baton; 1590251881Speter struct edit_baton *eb = pb->edit_baton; 1591251881Speter struct dir_baton *new_db; 1592251881Speter const char *cmp_path = NULL; 1593251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 1594251881Speter 1595251881Speter /* If the parent directory has explicit comparison path and rev, 1596251881Speter record the same for this one. */ 1597251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 1598251881Speter { 1599251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 1600251881Speter svn_relpath_basename(path, pool), pool); 1601251881Speter cmp_rev = pb->cmp_rev; 1602251881Speter } 1603251881Speter 1604299742Sdim new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, pool); 1605251881Speter *child_baton = new_db; 1606251881Speter return SVN_NO_ERROR; 1607251881Speter} 1608251881Speter 1609251881Speter 1610251881Speterstatic svn_error_t * 1611251881Speterclose_directory(void *dir_baton, 1612251881Speter apr_pool_t *pool) 1613251881Speter{ 1614251881Speter struct dir_baton *db = dir_baton; 1615251881Speter struct edit_baton *eb = db->edit_baton; 1616251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1617251881Speter int i; 1618251881Speter apr_array_header_t *sorted_entries; 1619251881Speter 1620251881Speter /* Sort entries lexically instead of as paths. Even though the entries 1621251881Speter * are full paths they're all in the same directory (see comment in struct 1622251881Speter * dir_baton definition). So we really want to sort by basename, in which 1623251881Speter * case the lexical sort function is more efficient. */ 1624251881Speter sorted_entries = svn_sort__hash(db->deleted_entries, 1625251881Speter svn_sort_compare_items_lexically, pool); 1626251881Speter for (i = 0; i < sorted_entries->nelts; i++) 1627251881Speter { 1628251881Speter const char *path = APR_ARRAY_IDX(sorted_entries, i, 1629251881Speter svn_sort__item_t).key; 1630251881Speter 1631251881Speter svn_pool_clear(subpool); 1632251881Speter 1633251881Speter /* By sending 'svn_node_unknown', the Node-kind: header simply won't 1634251881Speter be written out. No big deal at all, really. The loader 1635251881Speter shouldn't care. */ 1636251881Speter SVN_ERR(dump_node(eb, path, 1637251881Speter svn_node_unknown, svn_node_action_delete, 1638251881Speter FALSE, NULL, SVN_INVALID_REVNUM, subpool)); 1639251881Speter } 1640251881Speter 1641251881Speter svn_pool_destroy(subpool); 1642251881Speter return SVN_NO_ERROR; 1643251881Speter} 1644251881Speter 1645251881Speter 1646251881Speterstatic svn_error_t * 1647251881Speteradd_file(const char *path, 1648251881Speter void *parent_baton, 1649251881Speter const char *copyfrom_path, 1650251881Speter svn_revnum_t copyfrom_rev, 1651251881Speter apr_pool_t *pool, 1652251881Speter void **file_baton) 1653251881Speter{ 1654251881Speter struct dir_baton *pb = parent_baton; 1655251881Speter struct edit_baton *eb = pb->edit_baton; 1656299742Sdim void *was_deleted; 1657251881Speter svn_boolean_t is_copy = FALSE; 1658251881Speter 1659251881Speter /* This might be a replacement -- is the path already deleted? */ 1660299742Sdim was_deleted = svn_hash_gets(pb->deleted_entries, path); 1661251881Speter 1662251881Speter /* Detect add-with-history. */ 1663251881Speter is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev); 1664251881Speter 1665251881Speter /* Dump the node. */ 1666251881Speter SVN_ERR(dump_node(eb, path, 1667251881Speter svn_node_file, 1668299742Sdim was_deleted ? svn_node_action_replace : svn_node_action_add, 1669251881Speter is_copy, 1670251881Speter is_copy ? copyfrom_path : NULL, 1671251881Speter is_copy ? copyfrom_rev : SVN_INVALID_REVNUM, 1672251881Speter pool)); 1673251881Speter 1674299742Sdim if (was_deleted) 1675251881Speter /* delete the path, it's now been dumped. */ 1676251881Speter svn_hash_sets(pb->deleted_entries, path, NULL); 1677251881Speter 1678299742Sdim /* Check for normalized name clashes, but only if this is actually a 1679299742Sdim new name in the parent, not a replacement. */ 1680299742Sdim if (!was_deleted && eb->verify && eb->check_normalization && eb->notify_func) 1681299742Sdim { 1682299742Sdim pb->check_name_collision = TRUE; 1683299742Sdim } 1684299742Sdim 1685251881Speter *file_baton = NULL; /* muhahahaha */ 1686251881Speter return SVN_NO_ERROR; 1687251881Speter} 1688251881Speter 1689251881Speter 1690251881Speterstatic svn_error_t * 1691251881Speteropen_file(const char *path, 1692251881Speter void *parent_baton, 1693251881Speter svn_revnum_t ancestor_revision, 1694251881Speter apr_pool_t *pool, 1695251881Speter void **file_baton) 1696251881Speter{ 1697251881Speter struct dir_baton *pb = parent_baton; 1698251881Speter struct edit_baton *eb = pb->edit_baton; 1699251881Speter const char *cmp_path = NULL; 1700251881Speter svn_revnum_t cmp_rev = SVN_INVALID_REVNUM; 1701251881Speter 1702251881Speter /* If the parent directory has explicit comparison path and rev, 1703251881Speter record the same for this one. */ 1704251881Speter if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev)) 1705251881Speter { 1706251881Speter cmp_path = svn_relpath_join(pb->cmp_path, 1707251881Speter svn_relpath_basename(path, pool), pool); 1708251881Speter cmp_rev = pb->cmp_rev; 1709251881Speter } 1710251881Speter 1711251881Speter SVN_ERR(dump_node(eb, path, 1712251881Speter svn_node_file, svn_node_action_change, 1713251881Speter FALSE, cmp_path, cmp_rev, pool)); 1714251881Speter 1715251881Speter *file_baton = NULL; /* muhahahaha again */ 1716251881Speter return SVN_NO_ERROR; 1717251881Speter} 1718251881Speter 1719251881Speter 1720251881Speterstatic svn_error_t * 1721251881Speterchange_dir_prop(void *parent_baton, 1722251881Speter const char *name, 1723251881Speter const svn_string_t *value, 1724251881Speter apr_pool_t *pool) 1725251881Speter{ 1726251881Speter struct dir_baton *db = parent_baton; 1727251881Speter struct edit_baton *eb = db->edit_baton; 1728251881Speter 1729251881Speter /* This function is what distinguishes between a directory that is 1730251881Speter opened to merely get somewhere, vs. one that is opened because it 1731299742Sdim *actually* changed by itself. 1732299742Sdim 1733299742Sdim Instead of recording the prop changes here, we just use this method 1734299742Sdim to trigger writing the node; dump_node() finds all the changes. */ 1735251881Speter if (! db->written_out) 1736251881Speter { 1737251881Speter SVN_ERR(dump_node(eb, db->path, 1738251881Speter svn_node_dir, svn_node_action_change, 1739299742Sdim /* ### We pass is_copy=FALSE; this might be wrong 1740299742Sdim but the parameter isn't used when action=change. */ 1741251881Speter FALSE, db->cmp_path, db->cmp_rev, pool)); 1742251881Speter db->written_out = TRUE; 1743251881Speter } 1744251881Speter return SVN_NO_ERROR; 1745251881Speter} 1746251881Speter 1747251881Speterstatic svn_error_t * 1748251881Speterfetch_props_func(apr_hash_t **props, 1749251881Speter void *baton, 1750251881Speter const char *path, 1751251881Speter svn_revnum_t base_revision, 1752251881Speter apr_pool_t *result_pool, 1753251881Speter apr_pool_t *scratch_pool) 1754251881Speter{ 1755251881Speter struct edit_baton *eb = baton; 1756251881Speter svn_error_t *err; 1757251881Speter svn_fs_root_t *fs_root; 1758251881Speter 1759251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 1760251881Speter base_revision = eb->current_rev - 1; 1761251881Speter 1762251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 1763251881Speter 1764251881Speter err = svn_fs_node_proplist(props, fs_root, path, result_pool); 1765251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1766251881Speter { 1767251881Speter svn_error_clear(err); 1768251881Speter *props = apr_hash_make(result_pool); 1769251881Speter return SVN_NO_ERROR; 1770251881Speter } 1771251881Speter else if (err) 1772251881Speter return svn_error_trace(err); 1773251881Speter 1774251881Speter return SVN_NO_ERROR; 1775251881Speter} 1776251881Speter 1777251881Speterstatic svn_error_t * 1778251881Speterfetch_kind_func(svn_node_kind_t *kind, 1779251881Speter void *baton, 1780251881Speter const char *path, 1781251881Speter svn_revnum_t base_revision, 1782251881Speter apr_pool_t *scratch_pool) 1783251881Speter{ 1784251881Speter struct edit_baton *eb = baton; 1785251881Speter svn_fs_root_t *fs_root; 1786251881Speter 1787251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 1788251881Speter base_revision = eb->current_rev - 1; 1789251881Speter 1790251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 1791251881Speter 1792251881Speter SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool)); 1793251881Speter 1794251881Speter return SVN_NO_ERROR; 1795251881Speter} 1796251881Speter 1797251881Speterstatic svn_error_t * 1798251881Speterfetch_base_func(const char **filename, 1799251881Speter void *baton, 1800251881Speter const char *path, 1801251881Speter svn_revnum_t base_revision, 1802251881Speter apr_pool_t *result_pool, 1803251881Speter apr_pool_t *scratch_pool) 1804251881Speter{ 1805251881Speter struct edit_baton *eb = baton; 1806251881Speter svn_stream_t *contents; 1807251881Speter svn_stream_t *file_stream; 1808251881Speter const char *tmp_filename; 1809251881Speter svn_error_t *err; 1810251881Speter svn_fs_root_t *fs_root; 1811251881Speter 1812251881Speter if (!SVN_IS_VALID_REVNUM(base_revision)) 1813251881Speter base_revision = eb->current_rev - 1; 1814251881Speter 1815251881Speter SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool)); 1816251881Speter 1817251881Speter err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool); 1818251881Speter if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND) 1819251881Speter { 1820251881Speter svn_error_clear(err); 1821251881Speter *filename = NULL; 1822251881Speter return SVN_NO_ERROR; 1823251881Speter } 1824251881Speter else if (err) 1825251881Speter return svn_error_trace(err); 1826251881Speter SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL, 1827251881Speter svn_io_file_del_on_pool_cleanup, 1828251881Speter scratch_pool, scratch_pool)); 1829251881Speter SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool)); 1830251881Speter 1831251881Speter *filename = apr_pstrdup(result_pool, tmp_filename); 1832251881Speter 1833251881Speter return SVN_NO_ERROR; 1834251881Speter} 1835251881Speter 1836251881Speter 1837251881Speterstatic svn_error_t * 1838251881Speterget_dump_editor(const svn_delta_editor_t **editor, 1839251881Speter void **edit_baton, 1840251881Speter svn_fs_t *fs, 1841251881Speter svn_revnum_t to_rev, 1842251881Speter const char *root_path, 1843251881Speter svn_stream_t *stream, 1844251881Speter svn_boolean_t *found_old_reference, 1845251881Speter svn_boolean_t *found_old_mergeinfo, 1846251881Speter svn_error_t *(*custom_close_directory)(void *dir_baton, 1847251881Speter apr_pool_t *scratch_pool), 1848251881Speter svn_repos_notify_func_t notify_func, 1849251881Speter void *notify_baton, 1850251881Speter svn_revnum_t oldest_dumped_rev, 1851251881Speter svn_boolean_t use_deltas, 1852251881Speter svn_boolean_t verify, 1853299742Sdim svn_boolean_t check_normalization, 1854251881Speter apr_pool_t *pool) 1855251881Speter{ 1856251881Speter /* Allocate an edit baton to be stored in every directory baton. 1857251881Speter Set it up for the directory baton we create here, which is the 1858251881Speter root baton. */ 1859251881Speter struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 1860251881Speter svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool); 1861251881Speter svn_delta_shim_callbacks_t *shim_callbacks = 1862251881Speter svn_delta_shim_callbacks_default(pool); 1863251881Speter 1864251881Speter /* Set up the edit baton. */ 1865251881Speter eb->stream = stream; 1866251881Speter eb->notify_func = notify_func; 1867251881Speter eb->notify_baton = notify_baton; 1868251881Speter eb->oldest_dumped_rev = oldest_dumped_rev; 1869251881Speter eb->path = apr_pstrdup(pool, root_path); 1870251881Speter SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool)); 1871251881Speter eb->fs = fs; 1872251881Speter eb->current_rev = to_rev; 1873251881Speter eb->use_deltas = use_deltas; 1874251881Speter eb->verify = verify; 1875299742Sdim eb->check_normalization = check_normalization; 1876251881Speter eb->found_old_reference = found_old_reference; 1877251881Speter eb->found_old_mergeinfo = found_old_mergeinfo; 1878251881Speter 1879299742Sdim /* In non-verification mode, we will allow anything to be dumped because 1880299742Sdim it might be an incremental dump with possible manual intervention. 1881299742Sdim Also, this might be the last resort when it comes to data recovery. 1882299742Sdim 1883299742Sdim Else, make sure that all paths exists at their respective revisions. 1884299742Sdim */ 1885299742Sdim eb->path_tracker = verify ? tracker_create(to_rev, pool) : NULL; 1886299742Sdim 1887251881Speter /* Set up the editor. */ 1888251881Speter dump_editor->open_root = open_root; 1889251881Speter dump_editor->delete_entry = delete_entry; 1890251881Speter dump_editor->add_directory = add_directory; 1891251881Speter dump_editor->open_directory = open_directory; 1892251881Speter if (custom_close_directory) 1893251881Speter dump_editor->close_directory = custom_close_directory; 1894251881Speter else 1895251881Speter dump_editor->close_directory = close_directory; 1896251881Speter dump_editor->change_dir_prop = change_dir_prop; 1897251881Speter dump_editor->add_file = add_file; 1898251881Speter dump_editor->open_file = open_file; 1899251881Speter 1900251881Speter *edit_baton = eb; 1901251881Speter *editor = dump_editor; 1902251881Speter 1903251881Speter shim_callbacks->fetch_kind_func = fetch_kind_func; 1904251881Speter shim_callbacks->fetch_props_func = fetch_props_func; 1905251881Speter shim_callbacks->fetch_base_func = fetch_base_func; 1906251881Speter shim_callbacks->fetch_baton = eb; 1907251881Speter 1908251881Speter SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton, 1909251881Speter NULL, NULL, shim_callbacks, pool, pool)); 1910251881Speter 1911251881Speter return SVN_NO_ERROR; 1912251881Speter} 1913251881Speter 1914251881Speter/*----------------------------------------------------------------------*/ 1915251881Speter 1916251881Speter/** The main dumping routine, svn_repos_dump_fs. **/ 1917251881Speter 1918251881Speter 1919251881Speter/* Helper for svn_repos_dump_fs. 1920251881Speter 1921251881Speter Write a revision record of REV in FS to writable STREAM, using POOL. 1922251881Speter */ 1923251881Speterstatic svn_error_t * 1924251881Speterwrite_revision_record(svn_stream_t *stream, 1925251881Speter svn_fs_t *fs, 1926251881Speter svn_revnum_t rev, 1927251881Speter apr_pool_t *pool) 1928251881Speter{ 1929251881Speter apr_hash_t *props; 1930251881Speter apr_time_t timetemp; 1931251881Speter svn_string_t *datevalue; 1932251881Speter 1933251881Speter SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool)); 1934251881Speter 1935251881Speter /* Run revision date properties through the time conversion to 1936251881Speter canonicalize them. */ 1937251881Speter /* ### Remove this when it is no longer needed for sure. */ 1938251881Speter datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE); 1939251881Speter if (datevalue) 1940251881Speter { 1941251881Speter SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool)); 1942251881Speter datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool), 1943251881Speter pool); 1944251881Speter svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue); 1945251881Speter } 1946251881Speter 1947299742Sdim SVN_ERR(svn_repos__dump_revision_record(stream, rev, NULL, props, 1948299742Sdim TRUE /*props_section_always*/, 1949299742Sdim pool)); 1950299742Sdim return SVN_NO_ERROR; 1951251881Speter} 1952251881Speter 1953251881Speter 1954251881Speter 1955251881Speter/* The main dumper. */ 1956251881Spetersvn_error_t * 1957251881Spetersvn_repos_dump_fs3(svn_repos_t *repos, 1958251881Speter svn_stream_t *stream, 1959251881Speter svn_revnum_t start_rev, 1960251881Speter svn_revnum_t end_rev, 1961251881Speter svn_boolean_t incremental, 1962251881Speter svn_boolean_t use_deltas, 1963251881Speter svn_repos_notify_func_t notify_func, 1964251881Speter void *notify_baton, 1965251881Speter svn_cancel_func_t cancel_func, 1966251881Speter void *cancel_baton, 1967251881Speter apr_pool_t *pool) 1968251881Speter{ 1969251881Speter const svn_delta_editor_t *dump_editor; 1970251881Speter void *dump_edit_baton = NULL; 1971299742Sdim svn_revnum_t rev; 1972251881Speter svn_fs_t *fs = svn_repos_fs(repos); 1973251881Speter apr_pool_t *subpool = svn_pool_create(pool); 1974251881Speter svn_revnum_t youngest; 1975251881Speter const char *uuid; 1976251881Speter int version; 1977251881Speter svn_boolean_t found_old_reference = FALSE; 1978251881Speter svn_boolean_t found_old_mergeinfo = FALSE; 1979251881Speter svn_repos_notify_t *notify; 1980251881Speter 1981251881Speter /* Determine the current youngest revision of the filesystem. */ 1982251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 1983251881Speter 1984251881Speter /* Use default vals if necessary. */ 1985251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 1986251881Speter start_rev = 0; 1987251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 1988251881Speter end_rev = youngest; 1989251881Speter if (! stream) 1990251881Speter stream = svn_stream_empty(pool); 1991251881Speter 1992251881Speter /* Validate the revisions. */ 1993251881Speter if (start_rev > end_rev) 1994251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 1995251881Speter _("Start revision %ld" 1996251881Speter " is greater than end revision %ld"), 1997251881Speter start_rev, end_rev); 1998251881Speter if (end_rev > youngest) 1999251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 2000251881Speter _("End revision %ld is invalid " 2001251881Speter "(youngest revision is %ld)"), 2002251881Speter end_rev, youngest); 2003251881Speter 2004251881Speter /* Write out the UUID. */ 2005251881Speter SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool)); 2006251881Speter 2007251881Speter /* If we're not using deltas, use the previous version, for 2008251881Speter compatibility with svn 1.0.x. */ 2009251881Speter version = SVN_REPOS_DUMPFILE_FORMAT_VERSION; 2010251881Speter if (!use_deltas) 2011251881Speter version--; 2012251881Speter 2013251881Speter /* Write out "general" metadata for the dumpfile. In this case, a 2014251881Speter magic header followed by a dumpfile format version. */ 2015251881Speter SVN_ERR(svn_stream_printf(stream, pool, 2016251881Speter SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n", 2017251881Speter version)); 2018251881Speter SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID 2019251881Speter ": %s\n\n", uuid)); 2020251881Speter 2021251881Speter /* Create a notify object that we can reuse in the loop. */ 2022251881Speter if (notify_func) 2023251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end, 2024251881Speter pool); 2025251881Speter 2026299742Sdim /* Main loop: we're going to dump revision REV. */ 2027299742Sdim for (rev = start_rev; rev <= end_rev; rev++) 2028251881Speter { 2029251881Speter svn_fs_root_t *to_root; 2030251881Speter svn_boolean_t use_deltas_for_rev; 2031251881Speter 2032251881Speter svn_pool_clear(subpool); 2033251881Speter 2034251881Speter /* Check for cancellation. */ 2035251881Speter if (cancel_func) 2036251881Speter SVN_ERR(cancel_func(cancel_baton)); 2037251881Speter 2038299742Sdim /* Write the revision record. */ 2039299742Sdim SVN_ERR(write_revision_record(stream, fs, rev, subpool)); 2040251881Speter 2041299742Sdim /* When dumping revision 0, we just write out the revision record. 2042299742Sdim The parser might want to use its properties. */ 2043299742Sdim if (rev == 0) 2044299742Sdim goto loop_end; 2045251881Speter 2046251881Speter /* Fetch the editor which dumps nodes to a file. Regardless of 2047251881Speter what we've been told, don't use deltas for the first rev of a 2048251881Speter non-incremental dump. */ 2049299742Sdim use_deltas_for_rev = use_deltas && (incremental || rev != start_rev); 2050299742Sdim SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, rev, 2051251881Speter "", stream, &found_old_reference, 2052251881Speter &found_old_mergeinfo, NULL, 2053251881Speter notify_func, notify_baton, 2054299742Sdim start_rev, use_deltas_for_rev, FALSE, FALSE, 2055299742Sdim subpool)); 2056251881Speter 2057251881Speter /* Drive the editor in one way or another. */ 2058299742Sdim SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, subpool)); 2059251881Speter 2060251881Speter /* If this is the first revision of a non-incremental dump, 2061251881Speter we're in for a full tree dump. Otherwise, we want to simply 2062251881Speter replay the revision. */ 2063299742Sdim if ((rev == start_rev) && (! incremental)) 2064251881Speter { 2065299742Sdim /* Compare against revision 0, so everything appears to be added. */ 2066251881Speter svn_fs_root_t *from_root; 2067299742Sdim SVN_ERR(svn_fs_revision_root(&from_root, fs, 0, subpool)); 2068251881Speter SVN_ERR(svn_repos_dir_delta2(from_root, "", "", 2069251881Speter to_root, "", 2070251881Speter dump_editor, dump_edit_baton, 2071251881Speter NULL, 2072251881Speter NULL, 2073251881Speter FALSE, /* don't send text-deltas */ 2074251881Speter svn_depth_infinity, 2075251881Speter FALSE, /* don't send entry props */ 2076251881Speter FALSE, /* don't ignore ancestry */ 2077251881Speter subpool)); 2078251881Speter } 2079251881Speter else 2080251881Speter { 2081299742Sdim /* The normal case: compare consecutive revs. */ 2082251881Speter SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 2083251881Speter dump_editor, dump_edit_baton, 2084251881Speter NULL, NULL, subpool)); 2085251881Speter 2086251881Speter /* While our editor close_edit implementation is a no-op, we still 2087251881Speter do this for completeness. */ 2088251881Speter SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool)); 2089251881Speter } 2090251881Speter 2091251881Speter loop_end: 2092251881Speter if (notify_func) 2093251881Speter { 2094299742Sdim notify->revision = rev; 2095251881Speter notify_func(notify_baton, notify, subpool); 2096251881Speter } 2097251881Speter } 2098251881Speter 2099251881Speter if (notify_func) 2100251881Speter { 2101251881Speter /* Did we issue any warnings about references to revisions older than 2102251881Speter the oldest dumped revision? If so, then issue a final generic 2103251881Speter warning, since the inline warnings already issued might easily be 2104251881Speter missed. */ 2105251881Speter 2106251881Speter notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool); 2107251881Speter notify_func(notify_baton, notify, subpool); 2108251881Speter 2109251881Speter if (found_old_reference) 2110251881Speter { 2111299742Sdim notify_warning(subpool, notify_func, notify_baton, 2112299742Sdim svn_repos_notify_warning_found_old_reference, 2113299742Sdim _("The range of revisions dumped " 2114299742Sdim "contained references to " 2115299742Sdim "copy sources outside that " 2116299742Sdim "range.")); 2117251881Speter } 2118251881Speter 2119251881Speter /* Ditto if we issued any warnings about old revisions referenced 2120251881Speter in dumped mergeinfo. */ 2121251881Speter if (found_old_mergeinfo) 2122251881Speter { 2123299742Sdim notify_warning(subpool, notify_func, notify_baton, 2124299742Sdim svn_repos_notify_warning_found_old_mergeinfo, 2125299742Sdim _("The range of revisions dumped " 2126299742Sdim "contained mergeinfo " 2127299742Sdim "which reference revisions outside " 2128299742Sdim "that range.")); 2129251881Speter } 2130251881Speter } 2131251881Speter 2132251881Speter svn_pool_destroy(subpool); 2133251881Speter 2134251881Speter return SVN_NO_ERROR; 2135251881Speter} 2136251881Speter 2137251881Speter 2138251881Speter/*----------------------------------------------------------------------*/ 2139251881Speter 2140251881Speter/* verify, based on dump */ 2141251881Speter 2142251881Speter 2143251881Speter/* Creating a new revision that changes /A/B/E/bravo means creating new 2144251881Speter directory listings for /, /A, /A/B, and /A/B/E in the new revision, with 2145251881Speter each entry not changed in the new revision a link back to the entry in a 2146251881Speter previous revision. svn_repos_replay()ing a revision does not verify that 2147251881Speter those links are correct. 2148251881Speter 2149251881Speter For paths actually changed in the revision we verify, we get directory 2150251881Speter contents or file length twice: once in the dump editor, and once here. 2151251881Speter We could create a new verify baton, store in it the changed paths, and 2152251881Speter skip those here, but that means building an entire wrapper editor and 2153251881Speter managing two levels of batons. The impact from checking these entries 2154251881Speter twice should be minimal, while the code to avoid it is not. 2155251881Speter*/ 2156251881Speter 2157251881Speterstatic svn_error_t * 2158251881Speterverify_directory_entry(void *baton, const void *key, apr_ssize_t klen, 2159251881Speter void *val, apr_pool_t *pool) 2160251881Speter{ 2161251881Speter struct dir_baton *db = baton; 2162251881Speter svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val; 2163299742Sdim char *path; 2164299742Sdim svn_boolean_t right_kind; 2165251881Speter 2166299742Sdim path = svn_relpath_join(db->path, (const char *)key, pool); 2167299742Sdim 2168251881Speter /* since we can't access the directory entries directly by their ID, 2169251881Speter we need to navigate from the FS_ROOT to them (relatively expensive 2170299742Sdim because we may start at a never rev than the last change to node). 2171299742Sdim We check that the node kind stored in the noderev matches the dir 2172299742Sdim entry. This also ensures that all entries point to valid noderevs. 2173299742Sdim */ 2174251881Speter switch (dirent->kind) { 2175251881Speter case svn_node_dir: 2176299742Sdim SVN_ERR(svn_fs_is_dir(&right_kind, db->edit_baton->fs_root, path, pool)); 2177299742Sdim if (!right_kind) 2178299742Sdim return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 2179299742Sdim _("Node '%s' is not a directory."), 2180299742Sdim path); 2181299742Sdim 2182251881Speter break; 2183251881Speter case svn_node_file: 2184299742Sdim SVN_ERR(svn_fs_is_file(&right_kind, db->edit_baton->fs_root, path, pool)); 2185299742Sdim if (!right_kind) 2186299742Sdim return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 2187299742Sdim _("Node '%s' is not a file."), 2188299742Sdim path); 2189251881Speter break; 2190251881Speter default: 2191251881Speter return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, 2192251881Speter _("Unexpected node kind %d for '%s'"), 2193251881Speter dirent->kind, path); 2194251881Speter } 2195251881Speter 2196251881Speter return SVN_NO_ERROR; 2197251881Speter} 2198251881Speter 2199299742Sdim/* Baton used by the check_name_collision hash iterator. */ 2200299742Sdimstruct check_name_collision_baton 2201299742Sdim{ 2202299742Sdim struct dir_baton *dir_baton; 2203299742Sdim apr_hash_t *normalized; 2204299742Sdim svn_membuf_t buffer; 2205299742Sdim}; 2206299742Sdim 2207299742Sdim/* Scan the directory and report all entry names that differ only in 2208299742Sdim Unicode character representation. */ 2209251881Speterstatic svn_error_t * 2210299742Sdimcheck_name_collision(void *baton, const void *key, apr_ssize_t klen, 2211299742Sdim void *val, apr_pool_t *iterpool) 2212251881Speter{ 2213299742Sdim struct check_name_collision_baton *const cb = baton; 2214299742Sdim const char *name; 2215299742Sdim const char *found; 2216299742Sdim 2217299742Sdim SVN_ERR(svn_utf__normalize(&name, key, klen, &cb->buffer)); 2218299742Sdim 2219299742Sdim found = svn_hash_gets(cb->normalized, name); 2220299742Sdim if (!found) 2221299742Sdim svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), 2222299742Sdim normalized_unique); 2223299742Sdim else if (found == normalized_collision) 2224299742Sdim /* Skip already reported collision */; 2225299742Sdim else 2226299742Sdim { 2227299742Sdim struct dir_baton *const db = cb->dir_baton; 2228299742Sdim struct edit_baton *const eb = db->edit_baton; 2229299742Sdim const char* normpath; 2230299742Sdim 2231299742Sdim svn_hash_sets(cb->normalized, apr_pstrdup(cb->buffer.pool, name), 2232299742Sdim normalized_collision); 2233299742Sdim 2234299742Sdim SVN_ERR(svn_utf__normalize( 2235299742Sdim &normpath, svn_relpath_join(db->path, name, iterpool), 2236299742Sdim SVN_UTF__UNKNOWN_LENGTH, &cb->buffer)); 2237299742Sdim notify_warning(iterpool, eb->notify_func, eb->notify_baton, 2238299742Sdim svn_repos_notify_warning_name_collision, 2239299742Sdim _("Duplicate representation of path '%s'"), normpath); 2240299742Sdim } 2241299742Sdim return SVN_NO_ERROR; 2242299742Sdim} 2243299742Sdim 2244299742Sdim 2245299742Sdimstatic svn_error_t * 2246299742Sdimverify_close_directory(void *dir_baton, apr_pool_t *pool) 2247299742Sdim{ 2248251881Speter struct dir_baton *db = dir_baton; 2249251881Speter apr_hash_t *dirents; 2250251881Speter SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, 2251251881Speter db->path, pool)); 2252251881Speter SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry, 2253251881Speter dir_baton, pool)); 2254299742Sdim 2255299742Sdim if (db->check_name_collision) 2256299742Sdim { 2257299742Sdim struct check_name_collision_baton check_baton; 2258299742Sdim check_baton.dir_baton = db; 2259299742Sdim check_baton.normalized = apr_hash_make(pool); 2260299742Sdim svn_membuf__create(&check_baton.buffer, 0, pool); 2261299742Sdim SVN_ERR(svn_iter_apr_hash(NULL, dirents, check_name_collision, 2262299742Sdim &check_baton, pool)); 2263299742Sdim } 2264299742Sdim 2265251881Speter return close_directory(dir_baton, pool); 2266251881Speter} 2267251881Speter 2268299742Sdim/* Verify revision REV in file system FS. */ 2269299742Sdimstatic svn_error_t * 2270299742Sdimverify_one_revision(svn_fs_t *fs, 2271299742Sdim svn_revnum_t rev, 2272299742Sdim svn_repos_notify_func_t notify_func, 2273299742Sdim void *notify_baton, 2274299742Sdim svn_revnum_t start_rev, 2275299742Sdim svn_boolean_t check_normalization, 2276299742Sdim svn_cancel_func_t cancel_func, 2277299742Sdim void *cancel_baton, 2278299742Sdim apr_pool_t *scratch_pool) 2279299742Sdim{ 2280299742Sdim const svn_delta_editor_t *dump_editor; 2281299742Sdim void *dump_edit_baton; 2282299742Sdim svn_fs_root_t *to_root; 2283299742Sdim apr_hash_t *props; 2284299742Sdim const svn_delta_editor_t *cancel_editor; 2285299742Sdim void *cancel_edit_baton; 2286299742Sdim 2287299742Sdim /* Get cancellable dump editor, but with our close_directory handler.*/ 2288299742Sdim SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, 2289299742Sdim fs, rev, "", 2290299742Sdim svn_stream_empty(scratch_pool), 2291299742Sdim NULL, NULL, 2292299742Sdim verify_close_directory, 2293299742Sdim notify_func, notify_baton, 2294299742Sdim start_rev, 2295299742Sdim FALSE, TRUE, /* use_deltas, verify */ 2296299742Sdim check_normalization, 2297299742Sdim scratch_pool)); 2298299742Sdim SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton, 2299299742Sdim dump_editor, dump_edit_baton, 2300299742Sdim &cancel_editor, 2301299742Sdim &cancel_edit_baton, 2302299742Sdim scratch_pool)); 2303299742Sdim SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, scratch_pool)); 2304299742Sdim SVN_ERR(svn_fs_verify_root(to_root, scratch_pool)); 2305299742Sdim SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE, 2306299742Sdim cancel_editor, cancel_edit_baton, 2307299742Sdim NULL, NULL, scratch_pool)); 2308299742Sdim 2309299742Sdim /* While our editor close_edit implementation is a no-op, we still 2310299742Sdim do this for completeness. */ 2311299742Sdim SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, scratch_pool)); 2312299742Sdim 2313299742Sdim SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, scratch_pool)); 2314299742Sdim 2315299742Sdim return SVN_NO_ERROR; 2316299742Sdim} 2317299742Sdim 2318251881Speter/* Baton type used for forwarding notifications from FS API to REPOS API. */ 2319299742Sdimstruct verify_fs_notify_func_baton_t 2320251881Speter{ 2321251881Speter /* notification function to call (must not be NULL) */ 2322251881Speter svn_repos_notify_func_t notify_func; 2323251881Speter 2324251881Speter /* baton to use for it */ 2325251881Speter void *notify_baton; 2326251881Speter 2327251881Speter /* type of notification to send (we will simply plug in the revision) */ 2328251881Speter svn_repos_notify_t *notify; 2329251881Speter}; 2330251881Speter 2331251881Speter/* Forward the notification to BATON. */ 2332251881Speterstatic void 2333299742Sdimverify_fs_notify_func(svn_revnum_t revision, 2334251881Speter void *baton, 2335251881Speter apr_pool_t *pool) 2336251881Speter{ 2337299742Sdim struct verify_fs_notify_func_baton_t *notify_baton = baton; 2338251881Speter 2339251881Speter notify_baton->notify->revision = revision; 2340251881Speter notify_baton->notify_func(notify_baton->notify_baton, 2341251881Speter notify_baton->notify, pool); 2342251881Speter} 2343251881Speter 2344299742Sdimstatic svn_error_t * 2345299742Sdimreport_error(svn_revnum_t revision, 2346299742Sdim svn_error_t *verify_err, 2347299742Sdim svn_repos_verify_callback_t verify_callback, 2348299742Sdim void *verify_baton, 2349299742Sdim apr_pool_t *pool) 2350299742Sdim{ 2351299742Sdim if (verify_callback) 2352299742Sdim { 2353299742Sdim svn_error_t *cb_err; 2354299742Sdim 2355299742Sdim /* The caller provided us with a callback, so make him responsible 2356299742Sdim for what's going to happen with the error. */ 2357299742Sdim cb_err = verify_callback(verify_baton, revision, verify_err, pool); 2358299742Sdim svn_error_clear(verify_err); 2359299742Sdim SVN_ERR(cb_err); 2360299742Sdim 2361299742Sdim return SVN_NO_ERROR; 2362299742Sdim } 2363299742Sdim else 2364299742Sdim { 2365299742Sdim /* No callback -- no second guessing. Just return the error. */ 2366299742Sdim return svn_error_trace(verify_err); 2367299742Sdim } 2368299742Sdim} 2369299742Sdim 2370251881Spetersvn_error_t * 2371299742Sdimsvn_repos_verify_fs3(svn_repos_t *repos, 2372251881Speter svn_revnum_t start_rev, 2373251881Speter svn_revnum_t end_rev, 2374299742Sdim svn_boolean_t check_normalization, 2375299742Sdim svn_boolean_t metadata_only, 2376251881Speter svn_repos_notify_func_t notify_func, 2377251881Speter void *notify_baton, 2378299742Sdim svn_repos_verify_callback_t verify_callback, 2379299742Sdim void *verify_baton, 2380251881Speter svn_cancel_func_t cancel_func, 2381251881Speter void *cancel_baton, 2382251881Speter apr_pool_t *pool) 2383251881Speter{ 2384251881Speter svn_fs_t *fs = svn_repos_fs(repos); 2385251881Speter svn_revnum_t youngest; 2386251881Speter svn_revnum_t rev; 2387251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 2388251881Speter svn_repos_notify_t *notify; 2389251881Speter svn_fs_progress_notify_func_t verify_notify = NULL; 2390299742Sdim struct verify_fs_notify_func_baton_t *verify_notify_baton = NULL; 2391299742Sdim svn_error_t *err; 2392251881Speter 2393251881Speter /* Determine the current youngest revision of the filesystem. */ 2394251881Speter SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool)); 2395251881Speter 2396251881Speter /* Use default vals if necessary. */ 2397251881Speter if (! SVN_IS_VALID_REVNUM(start_rev)) 2398251881Speter start_rev = 0; 2399251881Speter if (! SVN_IS_VALID_REVNUM(end_rev)) 2400251881Speter end_rev = youngest; 2401251881Speter 2402251881Speter /* Validate the revisions. */ 2403251881Speter if (start_rev > end_rev) 2404251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 2405251881Speter _("Start revision %ld" 2406251881Speter " is greater than end revision %ld"), 2407251881Speter start_rev, end_rev); 2408251881Speter if (end_rev > youngest) 2409251881Speter return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL, 2410251881Speter _("End revision %ld is invalid " 2411251881Speter "(youngest revision is %ld)"), 2412251881Speter end_rev, youngest); 2413251881Speter 2414251881Speter /* Create a notify object that we can reuse within the loop and a 2415251881Speter forwarding structure for notifications from inside svn_fs_verify(). */ 2416251881Speter if (notify_func) 2417251881Speter { 2418299742Sdim notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end, pool); 2419251881Speter 2420299742Sdim verify_notify = verify_fs_notify_func; 2421251881Speter verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton)); 2422251881Speter verify_notify_baton->notify_func = notify_func; 2423251881Speter verify_notify_baton->notify_baton = notify_baton; 2424251881Speter verify_notify_baton->notify 2425251881Speter = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool); 2426251881Speter } 2427251881Speter 2428251881Speter /* Verify global metadata and backend-specific data first. */ 2429299742Sdim err = svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool), 2430299742Sdim start_rev, end_rev, 2431299742Sdim verify_notify, verify_notify_baton, 2432299742Sdim cancel_func, cancel_baton, pool); 2433251881Speter 2434299742Sdim if (err && err->apr_err == SVN_ERR_CANCELLED) 2435251881Speter { 2436299742Sdim return svn_error_trace(err); 2437299742Sdim } 2438299742Sdim else if (err) 2439299742Sdim { 2440299742Sdim SVN_ERR(report_error(SVN_INVALID_REVNUM, err, verify_callback, 2441299742Sdim verify_baton, iterpool)); 2442299742Sdim } 2443251881Speter 2444299742Sdim if (!metadata_only) 2445299742Sdim for (rev = start_rev; rev <= end_rev; rev++) 2446299742Sdim { 2447299742Sdim svn_pool_clear(iterpool); 2448251881Speter 2449299742Sdim /* Wrapper function to catch the possible errors. */ 2450299742Sdim err = verify_one_revision(fs, rev, notify_func, notify_baton, 2451299742Sdim start_rev, check_normalization, 2452299742Sdim cancel_func, cancel_baton, 2453299742Sdim iterpool); 2454251881Speter 2455299742Sdim if (err && err->apr_err == SVN_ERR_CANCELLED) 2456299742Sdim { 2457299742Sdim return svn_error_trace(err); 2458299742Sdim } 2459299742Sdim else if (err) 2460299742Sdim { 2461299742Sdim SVN_ERR(report_error(rev, err, verify_callback, verify_baton, 2462299742Sdim iterpool)); 2463299742Sdim } 2464299742Sdim else if (notify_func) 2465299742Sdim { 2466299742Sdim /* Tell the caller that we're done with this revision. */ 2467299742Sdim notify->revision = rev; 2468299742Sdim notify_func(notify_baton, notify, iterpool); 2469299742Sdim } 2470299742Sdim } 2471251881Speter 2472251881Speter /* We're done. */ 2473251881Speter if (notify_func) 2474251881Speter { 2475251881Speter notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool); 2476251881Speter notify_func(notify_baton, notify, iterpool); 2477251881Speter } 2478251881Speter 2479251881Speter svn_pool_destroy(iterpool); 2480251881Speter 2481251881Speter return SVN_NO_ERROR; 2482251881Speter} 2483