1 /* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
3 * ====================================================================
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
20 * ====================================================================
24 #include "svn_private_config.h"
25 #include "svn_pools.h"
26 #include "svn_error.h"
30 #include "svn_repos.h"
31 #include "svn_string.h"
32 #include "svn_dirent_uri.h"
35 #include "svn_checksum.h"
36 #include "svn_props.h"
37 #include "svn_sorts.h"
39 #include "private/svn_mergeinfo_private.h"
40 #include "private/svn_fs_private.h"
42 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
44 /*----------------------------------------------------------------------*/
48 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
49 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
50 in which case the delta will be computed against an empty file, as
51 per the svn_fs_get_file_delta_stream docstring. Record the length
52 of the temporary file in *LEN, and rewind the file before
55 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
56 svn_fs_root_t *oldroot, const char *oldpath,
57 svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
59 svn_stream_t *temp_stream;
61 svn_txdelta_stream_t *delta_stream;
62 svn_txdelta_window_handler_t wh;
65 /* Create a temporary file and open a stream to it. Note that we need
66 the file handle in order to rewind it. */
67 SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL,
68 svn_io_file_del_on_pool_cleanup,
70 temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool);
72 /* Compute the delta and send it to the temporary file. */
73 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
74 newroot, newpath, pool));
75 svn_txdelta_to_svndiff3(&wh, &whb, temp_stream, 0,
76 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
77 SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
79 /* Get the length of the temporary file and rewind it. */
80 SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
83 return svn_io_file_seek(*tempfile, APR_SET, &offset, pool);
87 /*----------------------------------------------------------------------*/
89 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
91 /* Look, mom! No file batons! */
95 /* The relpath which implicitly prepends all full paths coming into
96 this editor. This will almost always be "". */
99 /* The stream to dump to. */
100 svn_stream_t *stream;
102 /* Send feedback here, if non-NULL */
103 svn_repos_notify_func_t notify_func;
106 /* The fs revision root, so we can read the contents of paths. */
107 svn_fs_root_t *fs_root;
108 svn_revnum_t current_rev;
110 /* The fs, so we can grab historic information if needed. */
113 /* True if dumped nodes should output deltas instead of full text. */
114 svn_boolean_t use_deltas;
116 /* True if this "dump" is in fact a verify. */
117 svn_boolean_t verify;
119 /* The first revision dumped in this dumpstream. */
120 svn_revnum_t oldest_dumped_rev;
122 /* If not NULL, set to true if any references to revisions older than
123 OLDEST_DUMPED_REV were found in the dumpstream. */
124 svn_boolean_t *found_old_reference;
126 /* If not NULL, set to true if any mergeinfo was dumped which contains
127 revisions older than OLDEST_DUMPED_REV. */
128 svn_boolean_t *found_old_mergeinfo;
130 /* reusable buffer for writing file contents */
131 char buffer[SVN__STREAM_CHUNK_SIZE];
137 struct edit_baton *edit_baton;
138 struct dir_baton *parent_dir_baton;
140 /* is this directory a new addition to this revision? */
143 /* has this directory been written to the output stream? */
144 svn_boolean_t written_out;
146 /* the repository relpath associated with this directory */
149 /* The comparison repository relpath and revision of this directory.
150 If both of these are valid, use them as a source against which to
151 compare the directory instead of the default comparison source of
152 PATH in the previous revision. */
153 const char *cmp_path;
154 svn_revnum_t cmp_rev;
156 /* hash of paths that need to be deleted, though some -might- be
157 replaced. maps const char * paths to this dir_baton. (they're
158 full paths, because that's what the editor driver gives us. but
159 really, they're all within this directory.) */
160 apr_hash_t *deleted_entries;
162 /* pool to be used for deleting the hash items */
167 /* Make a directory baton to represent the directory was path
168 (relative to EDIT_BATON's path) is PATH.
170 CMP_PATH/CMP_REV are the path/revision against which this directory
171 should be compared for changes. If either is omitted (NULL for the
172 path, SVN_INVALID_REVNUM for the rev), just compare this directory
173 PATH against itself in the previous revision.
175 PARENT_DIR_BATON is the directory baton of this directory's parent,
176 or NULL if this is the top-level directory of the edit. ADDED
177 indicated if this directory is newly added in this revision.
178 Perform all allocations in POOL. */
179 static struct dir_baton *
180 make_dir_baton(const char *path,
181 const char *cmp_path,
182 svn_revnum_t cmp_rev,
184 void *parent_dir_baton,
188 struct edit_baton *eb = edit_baton;
189 struct dir_baton *pb = parent_dir_baton;
190 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
191 const char *full_path;
193 /* A path relative to nothing? I don't think so. */
194 SVN_ERR_ASSERT_NO_RETURN(!path || pb);
196 /* Construct the full path of this node. */
198 full_path = svn_relpath_join(eb->path, path, pool);
200 full_path = apr_pstrdup(pool, eb->path);
202 /* Remove leading slashes from copyfrom paths. */
204 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
206 new_db->edit_baton = eb;
207 new_db->parent_dir_baton = pb;
208 new_db->path = full_path;
209 new_db->cmp_path = cmp_path;
210 new_db->cmp_rev = cmp_rev;
211 new_db->added = added;
212 new_db->written_out = FALSE;
213 new_db->deleted_entries = apr_hash_make(pool);
220 /* This helper is the main "meat" of the editor -- it does all the
221 work of writing a node record.
223 Write out a node record for PATH of type KIND under EB->FS_ROOT.
224 ACTION describes what is happening to the node (see enum svn_node_action).
225 Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
227 If the node was itself copied, IS_COPY is TRUE and the
228 path/revision of the copy source are in CMP_PATH/CMP_REV. If
229 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
233 dump_node(struct edit_baton *eb,
235 svn_node_kind_t kind,
236 enum svn_node_action action,
237 svn_boolean_t is_copy,
238 const char *cmp_path,
239 svn_revnum_t cmp_rev,
242 svn_stringbuf_t *propstring;
243 svn_filesize_t content_length = 0;
245 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
246 const char *compare_path = path;
247 svn_revnum_t compare_rev = eb->current_rev - 1;
248 svn_fs_root_t *compare_root = NULL;
249 apr_file_t *delta_file = NULL;
251 /* Maybe validate the path. */
252 if (eb->verify || eb->notify_func)
254 svn_error_t *err = svn_fs__path_valid(path, pool);
260 char errbuf[512]; /* ### svn_strerror() magic number */
261 svn_repos_notify_t *notify;
262 notify = svn_repos_notify_create(svn_repos_notify_warning, pool);
264 notify->warning = svn_repos_notify_warning_invalid_fspath;
265 notify->warning_str = apr_psprintf(
267 _("E%06d: While validating fspath '%s': %s"),
269 svn_err_best_message(err, errbuf, sizeof(errbuf)));
271 eb->notify_func(eb->notify_baton, notify, pool);
274 /* Return the error in addition to notifying about it. */
276 return svn_error_trace(err);
278 svn_error_clear(err);
282 /* Write out metadata headers for this file node. */
283 SVN_ERR(svn_stream_printf(eb->stream, pool,
284 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
286 if (kind == svn_node_file)
287 SVN_ERR(svn_stream_puts(eb->stream,
288 SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
289 else if (kind == svn_node_dir)
290 SVN_ERR(svn_stream_puts(eb->stream,
291 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
293 /* Remove leading slashes from copyfrom paths. */
295 cmp_path = svn_relpath_canonicalize(cmp_path, pool);
297 /* Validate the comparison path/rev. */
298 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
300 compare_path = cmp_path;
301 compare_rev = cmp_rev;
304 if (action == svn_node_action_change)
306 SVN_ERR(svn_stream_puts(eb->stream,
307 SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
309 /* either the text or props changed, or possibly both. */
310 SVN_ERR(svn_fs_revision_root(&compare_root,
311 svn_fs_root_fs(eb->fs_root),
314 SVN_ERR(svn_fs_props_changed(&must_dump_props,
315 compare_root, compare_path,
316 eb->fs_root, path, pool));
317 if (kind == svn_node_file)
318 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
319 compare_root, compare_path,
320 eb->fs_root, path, pool));
322 else if (action == svn_node_action_replace)
326 /* a simple delete+add, implied by a single 'replace' action. */
327 SVN_ERR(svn_stream_puts(eb->stream,
328 SVN_REPOS_DUMPFILE_NODE_ACTION
331 /* definitely need to dump all content for a replace. */
332 if (kind == svn_node_file)
333 must_dump_text = TRUE;
334 must_dump_props = TRUE;
338 /* more complex: delete original, then add-with-history. */
340 /* the path & kind headers have already been printed; just
341 add a delete action, and end the current record.*/
342 SVN_ERR(svn_stream_puts(eb->stream,
343 SVN_REPOS_DUMPFILE_NODE_ACTION
346 /* recurse: print an additional add-with-history record. */
347 SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
348 is_copy, compare_path, compare_rev, pool));
350 /* we can leave this routine quietly now, don't need to dump
351 any content; that was already done in the second record. */
352 must_dump_text = FALSE;
353 must_dump_props = FALSE;
356 else if (action == svn_node_action_delete)
358 SVN_ERR(svn_stream_puts(eb->stream,
359 SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
361 /* we can leave this routine quietly now, don't need to dump
363 must_dump_text = FALSE;
364 must_dump_props = FALSE;
366 else if (action == svn_node_action_add)
368 SVN_ERR(svn_stream_puts(eb->stream,
369 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
373 /* Dump all contents for a simple 'add'. */
374 if (kind == svn_node_file)
375 must_dump_text = TRUE;
376 must_dump_props = TRUE;
380 if (!eb->verify && cmp_rev < eb->oldest_dumped_rev
383 svn_repos_notify_t *notify =
384 svn_repos_notify_create(svn_repos_notify_warning, pool);
386 notify->warning = svn_repos_notify_warning_found_old_reference;
387 notify->warning_str = apr_psprintf(
389 _("Referencing data in revision %ld,"
390 " which is older than the oldest"
391 " dumped revision (r%ld). Loading this dump"
392 " into an empty repository"
394 cmp_rev, eb->oldest_dumped_rev);
395 if (eb->found_old_reference)
396 *eb->found_old_reference = TRUE;
397 eb->notify_func(eb->notify_baton, notify, pool);
400 SVN_ERR(svn_stream_printf(eb->stream, pool,
401 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
403 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
407 SVN_ERR(svn_fs_revision_root(&compare_root,
408 svn_fs_root_fs(eb->fs_root),
411 /* Need to decide if the copied node had any extra textual or
412 property mods as well. */
413 SVN_ERR(svn_fs_props_changed(&must_dump_props,
414 compare_root, compare_path,
415 eb->fs_root, path, pool));
416 if (kind == svn_node_file)
418 svn_checksum_t *checksum;
419 const char *hex_digest;
420 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
421 compare_root, compare_path,
422 eb->fs_root, path, pool));
424 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
425 compare_root, compare_path,
427 hex_digest = svn_checksum_to_cstring(checksum, pool);
429 SVN_ERR(svn_stream_printf(eb->stream, pool,
430 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_MD5
431 ": %s\n", hex_digest));
433 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
434 compare_root, compare_path,
436 hex_digest = svn_checksum_to_cstring(checksum, pool);
438 SVN_ERR(svn_stream_printf(eb->stream, pool,
439 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_SHA1
440 ": %s\n", hex_digest));
445 if ((! must_dump_text) && (! must_dump_props))
447 /* If we're not supposed to dump text or props, so be it, we can
448 just go home. However, if either one needs to be dumped,
449 then our dumpstream format demands that at a *minimum*, we
450 see a lone "PROPS-END" as a divider between text and props
451 content within the content-block. */
453 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
456 /*** Start prepping content to dump... ***/
458 /* If we are supposed to dump properties, write out a property
459 length header and generate a stringbuf that contains those
460 property values here. */
463 apr_hash_t *prophash, *oldhash = NULL;
465 svn_stream_t *propstream;
467 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
469 /* If this is a partial dump, then issue a warning if we dump mergeinfo
470 properties that refer to revisions older than the first revision
472 if (!eb->verify && eb->notify_func && eb->oldest_dumped_rev > 1)
474 svn_string_t *mergeinfo_str = svn_hash_gets(prophash,
478 svn_mergeinfo_t mergeinfo, old_mergeinfo;
480 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, mergeinfo_str->data,
482 SVN_ERR(svn_mergeinfo__filter_mergeinfo_by_ranges(
483 &old_mergeinfo, mergeinfo,
484 eb->oldest_dumped_rev - 1, 0,
486 if (apr_hash_count(old_mergeinfo))
488 svn_repos_notify_t *notify =
489 svn_repos_notify_create(svn_repos_notify_warning, pool);
491 notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
492 notify->warning_str = apr_psprintf(
494 _("Mergeinfo referencing revision(s) prior "
495 "to the oldest dumped revision (r%ld). "
496 "Loading this dump may result in invalid "
498 eb->oldest_dumped_rev);
500 if (eb->found_old_mergeinfo)
501 *eb->found_old_mergeinfo = TRUE;
502 eb->notify_func(eb->notify_baton, notify, pool);
507 if (eb->use_deltas && compare_root)
509 /* Fetch the old property hash to diff against and output a header
510 saying that our property contents are a delta. */
511 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
513 SVN_ERR(svn_stream_puts(eb->stream,
514 SVN_REPOS_DUMPFILE_PROP_DELTA ": true\n"));
517 oldhash = apr_hash_make(pool);
518 propstring = svn_stringbuf_create_ensure(0, pool);
519 propstream = svn_stream_from_stringbuf(propstring, pool);
520 SVN_ERR(svn_hash_write_incremental(prophash, oldhash, propstream,
522 SVN_ERR(svn_stream_close(propstream));
523 proplen = propstring->len;
524 content_length += proplen;
525 SVN_ERR(svn_stream_printf(eb->stream, pool,
526 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
527 ": %" APR_SIZE_T_FMT "\n", proplen));
530 /* If we are supposed to dump text, write out a text length header
531 here, and an MD5 checksum (if available). */
532 if (must_dump_text && (kind == svn_node_file))
534 svn_checksum_t *checksum;
535 const char *hex_digest;
536 svn_filesize_t textlen;
540 /* Compute the text delta now and write it into a temporary
541 file, so that we can find its length. Output a header
542 saying our text contents are a delta. */
543 SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
544 compare_path, eb->fs_root, path, pool));
545 SVN_ERR(svn_stream_puts(eb->stream,
546 SVN_REPOS_DUMPFILE_TEXT_DELTA ": true\n"));
550 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
551 compare_root, compare_path,
553 hex_digest = svn_checksum_to_cstring(checksum, pool);
555 SVN_ERR(svn_stream_printf(eb->stream, pool,
556 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
557 ": %s\n", hex_digest));
559 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
560 compare_root, compare_path,
562 hex_digest = svn_checksum_to_cstring(checksum, pool);
564 SVN_ERR(svn_stream_printf(eb->stream, pool,
565 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_SHA1
566 ": %s\n", hex_digest));
571 /* Just fetch the length of the file. */
572 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
575 content_length += textlen;
576 SVN_ERR(svn_stream_printf(eb->stream, pool,
577 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
578 ": %" SVN_FILESIZE_T_FMT "\n", textlen));
580 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_md5,
581 eb->fs_root, path, FALSE, pool));
582 hex_digest = svn_checksum_to_cstring(checksum, pool);
584 SVN_ERR(svn_stream_printf(eb->stream, pool,
585 SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
586 ": %s\n", hex_digest));
588 SVN_ERR(svn_fs_file_checksum(&checksum, svn_checksum_sha1,
589 eb->fs_root, path, FALSE, pool));
590 hex_digest = svn_checksum_to_cstring(checksum, pool);
592 SVN_ERR(svn_stream_printf(eb->stream, pool,
593 SVN_REPOS_DUMPFILE_TEXT_CONTENT_SHA1
594 ": %s\n", hex_digest));
597 /* 'Content-length:' is the last header before we dump the content,
598 and is the sum of the text and prop contents lengths. We write
599 this only for the benefit of non-Subversion RFC-822 parsers. */
600 SVN_ERR(svn_stream_printf(eb->stream, pool,
601 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
602 ": %" SVN_FILESIZE_T_FMT "\n\n",
605 /* Dump property content if we're supposed to do so. */
608 len = propstring->len;
609 SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
612 /* Dump text content */
613 if (must_dump_text && (kind == svn_node_file))
615 svn_stream_t *contents;
619 /* Make sure to close the underlying file when the stream is
621 contents = svn_stream_from_aprfile2(delta_file, FALSE, pool);
624 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
626 SVN_ERR(svn_stream_copy3(contents, svn_stream_disown(eb->stream, pool),
631 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
636 open_root(void *edit_baton,
637 svn_revnum_t base_revision,
641 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
642 edit_baton, NULL, FALSE, pool);
648 delete_entry(const char *path,
649 svn_revnum_t revision,
653 struct dir_baton *pb = parent_baton;
654 const char *mypath = apr_pstrdup(pb->pool, path);
656 /* remember this path needs to be deleted. */
657 svn_hash_sets(pb->deleted_entries, mypath, pb);
664 add_directory(const char *path,
666 const char *copyfrom_path,
667 svn_revnum_t copyfrom_rev,
671 struct dir_baton *pb = parent_baton;
672 struct edit_baton *eb = pb->edit_baton;
674 svn_boolean_t is_copy = FALSE;
675 struct dir_baton *new_db
676 = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
678 /* This might be a replacement -- is the path already deleted? */
679 val = svn_hash_gets(pb->deleted_entries, path);
681 /* Detect an add-with-history. */
682 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
685 SVN_ERR(dump_node(eb, path,
687 val ? svn_node_action_replace : svn_node_action_add,
689 is_copy ? copyfrom_path : NULL,
690 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
694 /* Delete the path, it's now been dumped. */
695 svn_hash_sets(pb->deleted_entries, path, NULL);
697 new_db->written_out = TRUE;
699 *child_baton = new_db;
705 open_directory(const char *path,
707 svn_revnum_t base_revision,
711 struct dir_baton *pb = parent_baton;
712 struct edit_baton *eb = pb->edit_baton;
713 struct dir_baton *new_db;
714 const char *cmp_path = NULL;
715 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
717 /* If the parent directory has explicit comparison path and rev,
718 record the same for this one. */
719 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
721 cmp_path = svn_relpath_join(pb->cmp_path,
722 svn_relpath_basename(path, pool), pool);
723 cmp_rev = pb->cmp_rev;
726 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
727 *child_baton = new_db;
733 close_directory(void *dir_baton,
736 struct dir_baton *db = dir_baton;
737 struct edit_baton *eb = db->edit_baton;
738 apr_pool_t *subpool = svn_pool_create(pool);
740 apr_array_header_t *sorted_entries;
742 /* Sort entries lexically instead of as paths. Even though the entries
743 * are full paths they're all in the same directory (see comment in struct
744 * dir_baton definition). So we really want to sort by basename, in which
745 * case the lexical sort function is more efficient. */
746 sorted_entries = svn_sort__hash(db->deleted_entries,
747 svn_sort_compare_items_lexically, pool);
748 for (i = 0; i < sorted_entries->nelts; i++)
750 const char *path = APR_ARRAY_IDX(sorted_entries, i,
751 svn_sort__item_t).key;
753 svn_pool_clear(subpool);
755 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
756 be written out. No big deal at all, really. The loader
758 SVN_ERR(dump_node(eb, path,
759 svn_node_unknown, svn_node_action_delete,
760 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
763 svn_pool_destroy(subpool);
769 add_file(const char *path,
771 const char *copyfrom_path,
772 svn_revnum_t copyfrom_rev,
776 struct dir_baton *pb = parent_baton;
777 struct edit_baton *eb = pb->edit_baton;
779 svn_boolean_t is_copy = FALSE;
781 /* This might be a replacement -- is the path already deleted? */
782 val = svn_hash_gets(pb->deleted_entries, path);
784 /* Detect add-with-history. */
785 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
788 SVN_ERR(dump_node(eb, path,
790 val ? svn_node_action_replace : svn_node_action_add,
792 is_copy ? copyfrom_path : NULL,
793 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
797 /* delete the path, it's now been dumped. */
798 svn_hash_sets(pb->deleted_entries, path, NULL);
800 *file_baton = NULL; /* muhahahaha */
806 open_file(const char *path,
808 svn_revnum_t ancestor_revision,
812 struct dir_baton *pb = parent_baton;
813 struct edit_baton *eb = pb->edit_baton;
814 const char *cmp_path = NULL;
815 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
817 /* If the parent directory has explicit comparison path and rev,
818 record the same for this one. */
819 if (ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
821 cmp_path = svn_relpath_join(pb->cmp_path,
822 svn_relpath_basename(path, pool), pool);
823 cmp_rev = pb->cmp_rev;
826 SVN_ERR(dump_node(eb, path,
827 svn_node_file, svn_node_action_change,
828 FALSE, cmp_path, cmp_rev, pool));
830 *file_baton = NULL; /* muhahahaha again */
836 change_dir_prop(void *parent_baton,
838 const svn_string_t *value,
841 struct dir_baton *db = parent_baton;
842 struct edit_baton *eb = db->edit_baton;
844 /* This function is what distinguishes between a directory that is
845 opened to merely get somewhere, vs. one that is opened because it
846 *actually* changed by itself. */
847 if (! db->written_out)
849 SVN_ERR(dump_node(eb, db->path,
850 svn_node_dir, svn_node_action_change,
851 FALSE, db->cmp_path, db->cmp_rev, pool));
852 db->written_out = TRUE;
858 fetch_props_func(apr_hash_t **props,
861 svn_revnum_t base_revision,
862 apr_pool_t *result_pool,
863 apr_pool_t *scratch_pool)
865 struct edit_baton *eb = baton;
867 svn_fs_root_t *fs_root;
869 if (!SVN_IS_VALID_REVNUM(base_revision))
870 base_revision = eb->current_rev - 1;
872 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
874 err = svn_fs_node_proplist(props, fs_root, path, result_pool);
875 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
877 svn_error_clear(err);
878 *props = apr_hash_make(result_pool);
882 return svn_error_trace(err);
888 fetch_kind_func(svn_node_kind_t *kind,
891 svn_revnum_t base_revision,
892 apr_pool_t *scratch_pool)
894 struct edit_baton *eb = baton;
895 svn_fs_root_t *fs_root;
897 if (!SVN_IS_VALID_REVNUM(base_revision))
898 base_revision = eb->current_rev - 1;
900 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
902 SVN_ERR(svn_fs_check_path(kind, fs_root, path, scratch_pool));
908 fetch_base_func(const char **filename,
911 svn_revnum_t base_revision,
912 apr_pool_t *result_pool,
913 apr_pool_t *scratch_pool)
915 struct edit_baton *eb = baton;
916 svn_stream_t *contents;
917 svn_stream_t *file_stream;
918 const char *tmp_filename;
920 svn_fs_root_t *fs_root;
922 if (!SVN_IS_VALID_REVNUM(base_revision))
923 base_revision = eb->current_rev - 1;
925 SVN_ERR(svn_fs_revision_root(&fs_root, eb->fs, base_revision, scratch_pool));
927 err = svn_fs_file_contents(&contents, fs_root, path, scratch_pool);
928 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
930 svn_error_clear(err);
935 return svn_error_trace(err);
936 SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
937 svn_io_file_del_on_pool_cleanup,
938 scratch_pool, scratch_pool));
939 SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
941 *filename = apr_pstrdup(result_pool, tmp_filename);
948 get_dump_editor(const svn_delta_editor_t **editor,
952 const char *root_path,
953 svn_stream_t *stream,
954 svn_boolean_t *found_old_reference,
955 svn_boolean_t *found_old_mergeinfo,
956 svn_error_t *(*custom_close_directory)(void *dir_baton,
957 apr_pool_t *scratch_pool),
958 svn_repos_notify_func_t notify_func,
960 svn_revnum_t oldest_dumped_rev,
961 svn_boolean_t use_deltas,
962 svn_boolean_t verify,
965 /* Allocate an edit baton to be stored in every directory baton.
966 Set it up for the directory baton we create here, which is the
968 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
969 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
970 svn_delta_shim_callbacks_t *shim_callbacks =
971 svn_delta_shim_callbacks_default(pool);
973 /* Set up the edit baton. */
975 eb->notify_func = notify_func;
976 eb->notify_baton = notify_baton;
977 eb->oldest_dumped_rev = oldest_dumped_rev;
978 eb->bufsize = sizeof(eb->buffer);
979 eb->path = apr_pstrdup(pool, root_path);
980 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
982 eb->current_rev = to_rev;
983 eb->use_deltas = use_deltas;
985 eb->found_old_reference = found_old_reference;
986 eb->found_old_mergeinfo = found_old_mergeinfo;
988 /* Set up the editor. */
989 dump_editor->open_root = open_root;
990 dump_editor->delete_entry = delete_entry;
991 dump_editor->add_directory = add_directory;
992 dump_editor->open_directory = open_directory;
993 if (custom_close_directory)
994 dump_editor->close_directory = custom_close_directory;
996 dump_editor->close_directory = close_directory;
997 dump_editor->change_dir_prop = change_dir_prop;
998 dump_editor->add_file = add_file;
999 dump_editor->open_file = open_file;
1002 *editor = dump_editor;
1004 shim_callbacks->fetch_kind_func = fetch_kind_func;
1005 shim_callbacks->fetch_props_func = fetch_props_func;
1006 shim_callbacks->fetch_base_func = fetch_base_func;
1007 shim_callbacks->fetch_baton = eb;
1009 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1010 NULL, NULL, shim_callbacks, pool, pool));
1012 return SVN_NO_ERROR;
1015 /*----------------------------------------------------------------------*/
1017 /** The main dumping routine, svn_repos_dump_fs. **/
1020 /* Helper for svn_repos_dump_fs.
1022 Write a revision record of REV in FS to writable STREAM, using POOL.
1024 static svn_error_t *
1025 write_revision_record(svn_stream_t *stream,
1032 svn_stringbuf_t *encoded_prophash;
1033 apr_time_t timetemp;
1034 svn_string_t *datevalue;
1035 svn_stream_t *propstream;
1037 /* Read the revision props even if we're aren't going to dump
1038 them for verification purposes */
1039 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
1041 /* Run revision date properties through the time conversion to
1042 canonicalize them. */
1043 /* ### Remove this when it is no longer needed for sure. */
1044 datevalue = svn_hash_gets(props, SVN_PROP_REVISION_DATE);
1047 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
1048 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
1050 svn_hash_sets(props, SVN_PROP_REVISION_DATE, datevalue);
1053 encoded_prophash = svn_stringbuf_create_ensure(0, pool);
1054 propstream = svn_stream_from_stringbuf(encoded_prophash, pool);
1055 SVN_ERR(svn_hash_write2(props, propstream, "PROPS-END", pool));
1056 SVN_ERR(svn_stream_close(propstream));
1058 /* ### someday write a revision-content-checksum */
1060 SVN_ERR(svn_stream_printf(stream, pool,
1061 SVN_REPOS_DUMPFILE_REVISION_NUMBER
1063 SVN_ERR(svn_stream_printf(stream, pool,
1064 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
1065 ": %" APR_SIZE_T_FMT "\n",
1066 encoded_prophash->len));
1068 /* Write out a regular Content-length header for the benefit of
1069 non-Subversion RFC-822 parsers. */
1070 SVN_ERR(svn_stream_printf(stream, pool,
1071 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1072 ": %" APR_SIZE_T_FMT "\n\n",
1073 encoded_prophash->len));
1075 len = encoded_prophash->len;
1076 SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
1079 return svn_stream_write(stream, "\n", &len);
1084 /* The main dumper. */
1086 svn_repos_dump_fs3(svn_repos_t *repos,
1087 svn_stream_t *stream,
1088 svn_revnum_t start_rev,
1089 svn_revnum_t end_rev,
1090 svn_boolean_t incremental,
1091 svn_boolean_t use_deltas,
1092 svn_repos_notify_func_t notify_func,
1094 svn_cancel_func_t cancel_func,
1098 const svn_delta_editor_t *dump_editor;
1099 void *dump_edit_baton = NULL;
1101 svn_fs_t *fs = svn_repos_fs(repos);
1102 apr_pool_t *subpool = svn_pool_create(pool);
1103 svn_revnum_t youngest;
1106 svn_boolean_t found_old_reference = FALSE;
1107 svn_boolean_t found_old_mergeinfo = FALSE;
1108 svn_repos_notify_t *notify;
1110 /* Determine the current youngest revision of the filesystem. */
1111 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1113 /* Use default vals if necessary. */
1114 if (! SVN_IS_VALID_REVNUM(start_rev))
1116 if (! SVN_IS_VALID_REVNUM(end_rev))
1119 stream = svn_stream_empty(pool);
1121 /* Validate the revisions. */
1122 if (start_rev > end_rev)
1123 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1124 _("Start revision %ld"
1125 " is greater than end revision %ld"),
1126 start_rev, end_rev);
1127 if (end_rev > youngest)
1128 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1129 _("End revision %ld is invalid "
1130 "(youngest revision is %ld)"),
1132 if ((start_rev == 0) && incremental)
1133 incremental = FALSE; /* revision 0 looks the same regardless of
1134 whether or not this is an incremental
1135 dump, so just simplify things. */
1137 /* Write out the UUID. */
1138 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
1140 /* If we're not using deltas, use the previous version, for
1141 compatibility with svn 1.0.x. */
1142 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
1146 /* Write out "general" metadata for the dumpfile. In this case, a
1147 magic header followed by a dumpfile format version. */
1148 SVN_ERR(svn_stream_printf(stream, pool,
1149 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
1151 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
1154 /* Create a notify object that we can reuse in the loop. */
1156 notify = svn_repos_notify_create(svn_repos_notify_dump_rev_end,
1159 /* Main loop: we're going to dump revision i. */
1160 for (i = start_rev; i <= end_rev; i++)
1162 svn_revnum_t from_rev, to_rev;
1163 svn_fs_root_t *to_root;
1164 svn_boolean_t use_deltas_for_rev;
1166 svn_pool_clear(subpool);
1168 /* Check for cancellation. */
1170 SVN_ERR(cancel_func(cancel_baton));
1172 /* Special-case the initial revision dump: it needs to contain
1173 *all* nodes, because it's the foundation of all future
1174 revisions in the dumpfile. */
1175 if ((i == start_rev) && (! incremental))
1177 /* Special-special-case a dump of revision 0. */
1180 /* Just write out the one revision 0 record and move on.
1181 The parser might want to use its properties. */
1182 SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1187 /* Compare START_REV to revision 0, so that everything
1188 appears to be added. */
1194 /* In the normal case, we want to compare consecutive revs. */
1199 /* Write the revision record. */
1200 SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1202 /* Fetch the editor which dumps nodes to a file. Regardless of
1203 what we've been told, don't use deltas for the first rev of a
1204 non-incremental dump. */
1205 use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1206 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1207 "", stream, &found_old_reference,
1208 &found_old_mergeinfo, NULL,
1209 notify_func, notify_baton,
1210 start_rev, use_deltas_for_rev, FALSE, subpool));
1212 /* Drive the editor in one way or another. */
1213 SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1215 /* If this is the first revision of a non-incremental dump,
1216 we're in for a full tree dump. Otherwise, we want to simply
1217 replay the revision. */
1218 if ((i == start_rev) && (! incremental))
1220 svn_fs_root_t *from_root;
1221 SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1222 SVN_ERR(svn_repos_dir_delta2(from_root, "", "",
1224 dump_editor, dump_edit_baton,
1227 FALSE, /* don't send text-deltas */
1229 FALSE, /* don't send entry props */
1230 FALSE, /* don't ignore ancestry */
1235 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1236 dump_editor, dump_edit_baton,
1237 NULL, NULL, subpool));
1239 /* While our editor close_edit implementation is a no-op, we still
1240 do this for completeness. */
1241 SVN_ERR(dump_editor->close_edit(dump_edit_baton, subpool));
1247 notify->revision = to_rev;
1248 notify_func(notify_baton, notify, subpool);
1254 /* Did we issue any warnings about references to revisions older than
1255 the oldest dumped revision? If so, then issue a final generic
1256 warning, since the inline warnings already issued might easily be
1259 notify = svn_repos_notify_create(svn_repos_notify_dump_end, subpool);
1260 notify_func(notify_baton, notify, subpool);
1262 if (found_old_reference)
1264 notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1266 notify->warning = svn_repos_notify_warning_found_old_reference;
1267 notify->warning_str = _("The range of revisions dumped "
1268 "contained references to "
1269 "copy sources outside that "
1271 notify_func(notify_baton, notify, subpool);
1274 /* Ditto if we issued any warnings about old revisions referenced
1275 in dumped mergeinfo. */
1276 if (found_old_mergeinfo)
1278 notify = svn_repos_notify_create(svn_repos_notify_warning, subpool);
1280 notify->warning = svn_repos_notify_warning_found_old_mergeinfo;
1281 notify->warning_str = _("The range of revisions dumped "
1282 "contained mergeinfo "
1283 "which reference revisions outside "
1285 notify_func(notify_baton, notify, subpool);
1289 svn_pool_destroy(subpool);
1291 return SVN_NO_ERROR;
1295 /*----------------------------------------------------------------------*/
1297 /* verify, based on dump */
1300 /* Creating a new revision that changes /A/B/E/bravo means creating new
1301 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1302 each entry not changed in the new revision a link back to the entry in a
1303 previous revision. svn_repos_replay()ing a revision does not verify that
1304 those links are correct.
1306 For paths actually changed in the revision we verify, we get directory
1307 contents or file length twice: once in the dump editor, and once here.
1308 We could create a new verify baton, store in it the changed paths, and
1309 skip those here, but that means building an entire wrapper editor and
1310 managing two levels of batons. The impact from checking these entries
1311 twice should be minimal, while the code to avoid it is not.
1314 static svn_error_t *
1315 verify_directory_entry(void *baton, const void *key, apr_ssize_t klen,
1316 void *val, apr_pool_t *pool)
1318 struct dir_baton *db = baton;
1319 svn_fs_dirent_t *dirent = (svn_fs_dirent_t *)val;
1320 char *path = svn_relpath_join(db->path, (const char *)key, pool);
1321 apr_hash_t *dirents;
1324 /* since we can't access the directory entries directly by their ID,
1325 we need to navigate from the FS_ROOT to them (relatively expensive
1326 because we may start at a never rev than the last change to node). */
1327 switch (dirent->kind) {
1329 /* Getting this directory's contents is enough to ensure that our
1330 link to it is correct. */
1331 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root, path, pool));
1334 /* Getting this file's size is enough to ensure that our link to it
1336 SVN_ERR(svn_fs_file_length(&len, db->edit_baton->fs_root, path, pool));
1339 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1340 _("Unexpected node kind %d for '%s'"),
1341 dirent->kind, path);
1344 return SVN_NO_ERROR;
1347 static svn_error_t *
1348 verify_close_directory(void *dir_baton,
1351 struct dir_baton *db = dir_baton;
1352 apr_hash_t *dirents;
1353 SVN_ERR(svn_fs_dir_entries(&dirents, db->edit_baton->fs_root,
1355 SVN_ERR(svn_iter_apr_hash(NULL, dirents, verify_directory_entry,
1357 return close_directory(dir_baton, pool);
1360 /* Baton type used for forwarding notifications from FS API to REPOS API. */
1361 struct verify_fs2_notify_func_baton_t
1363 /* notification function to call (must not be NULL) */
1364 svn_repos_notify_func_t notify_func;
1366 /* baton to use for it */
1369 /* type of notification to send (we will simply plug in the revision) */
1370 svn_repos_notify_t *notify;
1373 /* Forward the notification to BATON. */
1375 verify_fs2_notify_func(svn_revnum_t revision,
1379 struct verify_fs2_notify_func_baton_t *notify_baton = baton;
1381 notify_baton->notify->revision = revision;
1382 notify_baton->notify_func(notify_baton->notify_baton,
1383 notify_baton->notify, pool);
1387 svn_repos_verify_fs2(svn_repos_t *repos,
1388 svn_revnum_t start_rev,
1389 svn_revnum_t end_rev,
1390 svn_repos_notify_func_t notify_func,
1392 svn_cancel_func_t cancel_func,
1396 svn_fs_t *fs = svn_repos_fs(repos);
1397 svn_revnum_t youngest;
1399 apr_pool_t *iterpool = svn_pool_create(pool);
1400 svn_repos_notify_t *notify;
1401 svn_fs_progress_notify_func_t verify_notify = NULL;
1402 struct verify_fs2_notify_func_baton_t *verify_notify_baton = NULL;
1404 /* Determine the current youngest revision of the filesystem. */
1405 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
1407 /* Use default vals if necessary. */
1408 if (! SVN_IS_VALID_REVNUM(start_rev))
1410 if (! SVN_IS_VALID_REVNUM(end_rev))
1413 /* Validate the revisions. */
1414 if (start_rev > end_rev)
1415 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1416 _("Start revision %ld"
1417 " is greater than end revision %ld"),
1418 start_rev, end_rev);
1419 if (end_rev > youngest)
1420 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
1421 _("End revision %ld is invalid "
1422 "(youngest revision is %ld)"),
1425 /* Create a notify object that we can reuse within the loop and a
1426 forwarding structure for notifications from inside svn_fs_verify(). */
1429 notify = svn_repos_notify_create(svn_repos_notify_verify_rev_end,
1432 verify_notify = verify_fs2_notify_func;
1433 verify_notify_baton = apr_palloc(pool, sizeof(*verify_notify_baton));
1434 verify_notify_baton->notify_func = notify_func;
1435 verify_notify_baton->notify_baton = notify_baton;
1436 verify_notify_baton->notify
1437 = svn_repos_notify_create(svn_repos_notify_verify_rev_structure, pool);
1440 /* Verify global metadata and backend-specific data first. */
1441 SVN_ERR(svn_fs_verify(svn_fs_path(fs, pool), svn_fs_config(fs, pool),
1443 verify_notify, verify_notify_baton,
1444 cancel_func, cancel_baton, pool));
1446 for (rev = start_rev; rev <= end_rev; rev++)
1448 const svn_delta_editor_t *dump_editor;
1449 void *dump_edit_baton;
1450 const svn_delta_editor_t *cancel_editor;
1451 void *cancel_edit_baton;
1452 svn_fs_root_t *to_root;
1455 svn_pool_clear(iterpool);
1457 /* Get cancellable dump editor, but with our close_directory handler. */
1458 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton,
1460 svn_stream_empty(iterpool),
1462 verify_close_directory,
1463 notify_func, notify_baton,
1465 FALSE, TRUE, /* use_deltas, verify */
1467 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1468 dump_editor, dump_edit_baton,
1473 SVN_ERR(svn_fs_revision_root(&to_root, fs, rev, iterpool));
1474 SVN_ERR(svn_fs_verify_root(to_root, iterpool));
1476 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1477 cancel_editor, cancel_edit_baton,
1478 NULL, NULL, iterpool));
1479 /* While our editor close_edit implementation is a no-op, we still
1480 do this for completeness. */
1481 SVN_ERR(cancel_editor->close_edit(cancel_edit_baton, iterpool));
1483 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, iterpool));
1487 notify->revision = rev;
1488 notify_func(notify_baton, notify, iterpool);
1495 notify = svn_repos_notify_create(svn_repos_notify_verify_end, iterpool);
1496 notify_func(notify_baton, notify, iterpool);
1499 /* Per-backend verification. */
1500 svn_pool_destroy(iterpool);
1502 return SVN_NO_ERROR;