1 /* dump.c --- writing filesystem contents into a portable 'dumpfile' format.
3 * ====================================================================
4 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
6 * This software is licensed as described in the file COPYING, which
7 * you should have received as part of this distribution. The terms
8 * are also available at http://subversion.tigris.org/license-1.html.
9 * If newer versions of this license are posted there, you may use a
10 * newer version instead, at your option.
12 * This software consists of voluntary contributions made by many
13 * individuals. For exact contribution history, see the revision
14 * history and logs, available at http://subversion.tigris.org/.
15 * ====================================================================
19 #include "svn_private_config.h"
20 #include "svn_pools.h"
21 #include "svn_error.h"
24 #include "svn_repos.h"
25 #include "svn_string.h"
29 #include "svn_props.h"
32 #define ARE_VALID_COPY_ARGS(p,r) ((p && SVN_IS_VALID_REVNUM(r)) ? 1 : 0)
34 /*----------------------------------------------------------------------*/
36 /** A variant of our hash-writing routine in libsvn_subr; this one
37 writes to a stringbuf instead of a file, and outputs PROPS-END
38 instead of END. If OLDHASH is not NULL, then only properties
39 which vary from OLDHASH will be written, and properties which
40 exist only in OLDHASH will be written out with "D" entries
41 (like "K" entries but with no corresponding value). **/
44 write_hash_to_stringbuf(apr_hash_t
*hash
,
46 svn_stringbuf_t
**strbuf
,
49 apr_hash_index_t
*this; /* current hash entry */
51 *strbuf
= svn_stringbuf_create("", pool
);
53 for (this = apr_hash_first(pool
, hash
); this; this = apr_hash_next(this))
60 /* Get this key and val. */
61 apr_hash_this(this, &key
, &keylen
, &val
);
64 /* Don't output properties equal to the ones in oldhash, if present. */
67 svn_string_t
*oldvalue
= apr_hash_get(oldhash
, key
, keylen
);
69 if (oldvalue
&& svn_string_compare(value
, oldvalue
))
73 /* Output name length, then name. */
75 svn_stringbuf_appendcstr(*strbuf
,
76 apr_psprintf(pool
, "K %" APR_SSIZE_T_FMT
"\n",
79 svn_stringbuf_appendbytes(*strbuf
, (const char *) key
, keylen
);
80 svn_stringbuf_appendbytes(*strbuf
, "\n", 1);
82 /* Output value length, then value. */
84 svn_stringbuf_appendcstr(*strbuf
,
85 apr_psprintf(pool
, "V %" APR_SIZE_T_FMT
"\n",
88 svn_stringbuf_appendbytes(*strbuf
, value
->data
, value
->len
);
89 svn_stringbuf_appendbytes(*strbuf
, "\n", 1);
94 /* Output a "D " entry for each property in oldhash but not hash. */
95 for (this = apr_hash_first(pool
, oldhash
); this;
96 this = apr_hash_next(this))
102 apr_hash_this(this, &key
, &keylen
, NULL
);
104 /* Only output values deleted in hash. */
105 if (apr_hash_get(hash
, key
, keylen
))
108 /* Output name length, then name. */
110 svn_stringbuf_appendcstr(*strbuf
,
112 "D %" APR_SSIZE_T_FMT
"\n",
115 svn_stringbuf_appendbytes(*strbuf
, (const char *) key
, keylen
);
116 svn_stringbuf_appendbytes(*strbuf
, "\n", 1);
119 svn_stringbuf_appendbytes(*strbuf
, "PROPS-END\n", 10);
123 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
124 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
125 in which case the delta will be computed against an empty file, as
126 per the svn_fs_get_file_delta_stream docstring. Record the length
127 of the temporary file in *LEN, and rewind the file before
130 store_delta(apr_file_t
**tempfile
, svn_filesize_t
*len
,
131 svn_fs_root_t
*oldroot
, const char *oldpath
,
132 svn_fs_root_t
*newroot
, const char *newpath
, apr_pool_t
*pool
)
135 svn_stream_t
*temp_stream
;
136 apr_off_t offset
= 0;
137 svn_txdelta_stream_t
*delta_stream
;
138 svn_txdelta_window_handler_t wh
;
141 /* Create a temporary file and open a stream to it. */
142 SVN_ERR(svn_io_temp_dir(&tempdir
, pool
));
143 SVN_ERR(svn_io_open_unique_file2(tempfile
, NULL
,
144 apr_psprintf(pool
, "%s/dump", tempdir
),
145 ".tmp", svn_io_file_del_on_close
, pool
));
146 temp_stream
= svn_stream_from_aprfile(*tempfile
, pool
);
148 /* Compute the delta and send it to the temporary file. */
149 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream
, oldroot
, oldpath
,
150 newroot
, newpath
, pool
));
151 svn_txdelta_to_svndiff2(&wh
, &whb
, temp_stream
, 0, pool
);
152 SVN_ERR(svn_txdelta_send_txstream(delta_stream
, wh
, whb
, pool
));
154 /* Get the length of the temporary file and rewind it. */
155 SVN_ERR(svn_io_file_seek(*tempfile
, APR_CUR
, &offset
, pool
));
158 SVN_ERR(svn_io_file_seek(*tempfile
, APR_SET
, &offset
, pool
));
163 /*----------------------------------------------------------------------*/
165 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
167 /* Look, mom! No file batons! */
171 /* The path which implicitly prepends all full paths coming into
172 this editor. This will almost always be "" or "/". */
175 /* The stream to dump to. */
176 svn_stream_t
*stream
;
178 /* Send feedback here, if non-NULL */
179 svn_stream_t
*feedback_stream
;
181 /* The fs revision root, so we can read the contents of paths. */
182 svn_fs_root_t
*fs_root
;
183 svn_revnum_t current_rev
;
185 /* True if dumped nodes should output deltas instead of full text. */
186 svn_boolean_t use_deltas
;
188 /* The first revision dumped in this dumpstream. */
189 svn_revnum_t oldest_dumped_rev
;
191 /* reusable buffer for writing file contents */
192 char buffer
[SVN__STREAM_CHUNK_SIZE
];
198 struct edit_baton
*edit_baton
;
199 struct dir_baton
*parent_dir_baton
;
201 /* is this directory a new addition to this revision? */
204 /* has this directory been written to the output stream? */
205 svn_boolean_t written_out
;
207 /* the absolute path to this directory */
210 /* the comparison path and revision of this directory. if both of
211 these are valid, use them as a source against which to compare
212 the directory instead of the default comparison source of PATH in
213 the previous revision. */
214 const char *cmp_path
;
215 svn_revnum_t cmp_rev
;
217 /* hash of paths that need to be deleted, though some -might- be
218 replaced. maps const char * paths to this dir_baton. (they're
219 full paths, because that's what the editor driver gives us. but
220 really, they're all within this directory.) */
221 apr_hash_t
*deleted_entries
;
223 /* pool to be used for deleting the hash items */
228 /* Make a directory baton to represent the directory was path
229 (relative to EDIT_BATON's path) is PATH.
231 CMP_PATH/CMP_REV are the path/revision against which this directory
232 should be compared for changes. If either is omitted (NULL for the
233 path, SVN_INVALID_REVNUM for the rev), just compare this directory
234 PATH against itself in the previous revision.
236 PARENT_DIR_BATON is the directory baton of this directory's parent,
237 or NULL if this is the top-level directory of the edit. ADDED
238 indicated if this directory is newly added in this revision.
239 Perform all allocations in POOL. */
240 static struct dir_baton
*
241 make_dir_baton(const char *path
,
242 const char *cmp_path
,
243 svn_revnum_t cmp_rev
,
245 void *parent_dir_baton
,
249 struct edit_baton
*eb
= edit_baton
;
250 struct dir_baton
*pb
= parent_dir_baton
;
251 struct dir_baton
*new_db
= apr_pcalloc(pool
, sizeof(*new_db
));
252 const char *full_path
;
254 /* A path relative to nothing? I don't think so. */
258 /* Construct the full path of this node. */
260 full_path
= svn_path_join(eb
->path
, path
, pool
);
262 full_path
= apr_pstrdup(pool
, eb
->path
);
264 /* Remove leading slashes from copyfrom paths. */
266 cmp_path
= ((*cmp_path
== '/') ? cmp_path
+ 1 : cmp_path
);
268 new_db
->edit_baton
= eb
;
269 new_db
->parent_dir_baton
= pb
;
270 new_db
->path
= full_path
;
271 new_db
->cmp_path
= cmp_path
? apr_pstrdup(pool
, cmp_path
) : NULL
;
272 new_db
->cmp_rev
= cmp_rev
;
273 new_db
->added
= added
;
274 new_db
->written_out
= FALSE
;
275 new_db
->deleted_entries
= apr_hash_make(pool
);
282 /* This helper is the main "meat" of the editor -- it does all the
283 work of writing a node record.
285 Write out a node record for PATH of type KIND under EB->FS_ROOT.
286 ACTION describes what is happening to the node (see enum svn_node_action).
287 Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
289 If the node was itself copied, IS_COPY is TRUE and the
290 path/revision of the copy source are in CMP_PATH/CMP_REV. If
291 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
295 dump_node(struct edit_baton
*eb
,
296 const char *path
, /* an absolute path. */
297 svn_node_kind_t kind
,
298 enum svn_node_action action
,
299 svn_boolean_t is_copy
,
300 const char *cmp_path
,
301 svn_revnum_t cmp_rev
,
304 svn_stringbuf_t
*propstring
;
305 svn_filesize_t content_length
= 0;
307 svn_boolean_t must_dump_text
= FALSE
, must_dump_props
= FALSE
;
308 const char *compare_path
= path
;
309 svn_revnum_t compare_rev
= eb
->current_rev
- 1;
310 svn_fs_root_t
*compare_root
= NULL
;
311 apr_file_t
*delta_file
= NULL
;
313 /* Write out metadata headers for this file node. */
314 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
315 SVN_REPOS_DUMPFILE_NODE_PATH
": %s\n",
316 (*path
== '/') ? path
+ 1 : path
));
317 if (kind
== svn_node_file
)
318 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
319 SVN_REPOS_DUMPFILE_NODE_KIND
": file\n"));
320 else if (kind
== svn_node_dir
)
321 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
322 SVN_REPOS_DUMPFILE_NODE_KIND
": dir\n"));
324 /* Remove leading slashes from copyfrom paths. */
326 cmp_path
= ((*cmp_path
== '/') ? cmp_path
+ 1 : cmp_path
);
328 /* Validate the comparison path/rev. */
329 if (ARE_VALID_COPY_ARGS(cmp_path
, cmp_rev
))
331 compare_path
= cmp_path
;
332 compare_rev
= cmp_rev
;
335 if (action
== svn_node_action_change
)
337 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
338 SVN_REPOS_DUMPFILE_NODE_ACTION
341 /* either the text or props changed, or possibly both. */
342 SVN_ERR(svn_fs_revision_root(&compare_root
,
343 svn_fs_root_fs(eb
->fs_root
),
346 SVN_ERR(svn_fs_props_changed(&must_dump_props
,
347 compare_root
, compare_path
,
348 eb
->fs_root
, path
, pool
));
349 if (kind
== svn_node_file
)
350 SVN_ERR(svn_fs_contents_changed(&must_dump_text
,
351 compare_root
, compare_path
,
352 eb
->fs_root
, path
, pool
));
354 else if (action
== svn_node_action_replace
)
358 /* a simple delete+add, implied by a single 'replace' action. */
359 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
360 SVN_REPOS_DUMPFILE_NODE_ACTION
363 /* definitely need to dump all content for a replace. */
364 if (kind
== svn_node_file
)
365 must_dump_text
= TRUE
;
366 must_dump_props
= TRUE
;
370 /* more complex: delete original, then add-with-history. */
372 /* the path & kind headers have already been printed; just
373 add a delete action, and end the current record.*/
374 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
375 SVN_REPOS_DUMPFILE_NODE_ACTION
378 /* recurse: print an additional add-with-history record. */
379 SVN_ERR(dump_node(eb
, path
, kind
, svn_node_action_add
,
380 is_copy
, compare_path
, compare_rev
, pool
));
382 /* we can leave this routine quietly now, don't need to dump
383 any content; that was already done in the second record. */
384 must_dump_text
= FALSE
;
385 must_dump_props
= FALSE
;
388 else if (action
== svn_node_action_delete
)
390 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
391 SVN_REPOS_DUMPFILE_NODE_ACTION
394 /* we can leave this routine quietly now, don't need to dump
396 must_dump_text
= FALSE
;
397 must_dump_props
= FALSE
;
399 else if (action
== svn_node_action_add
)
401 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
402 SVN_REPOS_DUMPFILE_NODE_ACTION
": add\n"));
406 /* Dump all contents for a simple 'add'. */
407 if (kind
== svn_node_file
)
408 must_dump_text
= TRUE
;
409 must_dump_props
= TRUE
;
413 if (cmp_rev
< eb
->oldest_dumped_rev
)
414 SVN_ERR(svn_stream_printf
415 (eb
->feedback_stream
, pool
,
416 _("WARNING: Referencing data in revision %ld"
417 ", which is older than the oldest\nWARNING: dumped revision "
418 "(%ld). Loading this dump into an empty "
419 "repository\nWARNING: will fail.\n"),
420 cmp_rev
, eb
->oldest_dumped_rev
));
422 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
423 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
425 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
429 SVN_ERR(svn_fs_revision_root(&compare_root
,
430 svn_fs_root_fs(eb
->fs_root
),
433 /* Need to decide if the copied node had any extra textual or
434 property mods as well. */
435 SVN_ERR(svn_fs_props_changed(&must_dump_props
,
436 compare_root
, compare_path
,
437 eb
->fs_root
, path
, pool
));
438 if (kind
== svn_node_file
)
440 unsigned char md5_digest
[APR_MD5_DIGESTSIZE
];
441 const char *hex_digest
;
442 SVN_ERR(svn_fs_contents_changed(&must_dump_text
,
443 compare_root
, compare_path
,
444 eb
->fs_root
, path
, pool
));
446 SVN_ERR(svn_fs_file_md5_checksum(md5_digest
, compare_root
,
447 compare_path
, pool
));
448 hex_digest
= svn_md5_digest_to_cstring(md5_digest
, pool
);
450 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
451 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM
452 ": %s\n", hex_digest
));
457 if ((! must_dump_text
) && (! must_dump_props
))
459 /* If we're not supposed to dump text or props, so be it, we can
460 just go home. However, if either one needs to be dumped,
461 then our dumpstream format demands that at a *minimum*, we
462 see a lone "PROPS-END" as a divider between text and props
463 content within the content-block. */
465 return svn_stream_write(eb
->stream
, "\n\n", &len
); /* ### needed? */
468 /*** Start prepping content to dump... ***/
470 /* If we are supposed to dump properties, write out a property
471 length header and generate a stringbuf that contains those
472 property values here. */
475 apr_hash_t
*prophash
, *oldhash
= NULL
;
478 SVN_ERR(svn_fs_node_proplist(&prophash
, eb
->fs_root
, path
, pool
));
479 if (eb
->use_deltas
&& compare_root
)
481 /* Fetch the old property hash to diff against and output a header
482 saying that our property contents are a delta. */
483 SVN_ERR(svn_fs_node_proplist(&oldhash
, compare_root
, compare_path
,
485 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
486 SVN_REPOS_DUMPFILE_PROP_DELTA
489 write_hash_to_stringbuf(prophash
, oldhash
, &propstring
, pool
);
490 proplen
= propstring
->len
;
491 content_length
+= proplen
;
492 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
493 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
494 ": %" APR_SIZE_T_FMT
"\n", proplen
));
497 /* If we are supposed to dump text, write out a text length header
498 here, and an MD5 checksum (if available). */
499 if (must_dump_text
&& (kind
== svn_node_file
))
501 unsigned char md5_digest
[APR_MD5_DIGESTSIZE
];
502 const char *hex_digest
;
503 svn_filesize_t textlen
;
507 /* Compute the text delta now and write it into a temporary
508 file, so that we can find its length. Output a header
509 saying our text contents are a delta. */
510 SVN_ERR(store_delta(&delta_file
, &textlen
, compare_root
,
511 compare_path
, eb
->fs_root
, path
, pool
));
512 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
513 SVN_REPOS_DUMPFILE_TEXT_DELTA
518 SVN_ERR(svn_fs_file_md5_checksum(md5_digest
, compare_root
,
519 compare_path
, pool
));
520 hex_digest
= svn_md5_digest_to_cstring(md5_digest
, pool
);
522 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
523 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM
524 ": %s\n", hex_digest
));
529 /* Just fetch the length of the file. */
530 SVN_ERR(svn_fs_file_length(&textlen
, eb
->fs_root
, path
, pool
));
533 content_length
+= textlen
;
534 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
535 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
536 ": %" SVN_FILESIZE_T_FMT
"\n", textlen
));
538 SVN_ERR(svn_fs_file_md5_checksum(md5_digest
, eb
->fs_root
, path
, pool
));
539 hex_digest
= svn_md5_digest_to_cstring(md5_digest
, pool
);
541 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
542 SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM
543 ": %s\n", hex_digest
));
546 /* 'Content-length:' is the last header before we dump the content,
547 and is the sum of the text and prop contents lengths. We write
548 this only for the benefit of non-Subversion RFC-822 parsers. */
549 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
550 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
551 ": %" SVN_FILESIZE_T_FMT
"\n\n",
554 /* Dump property content if we're supposed to do so. */
557 len
= propstring
->len
;
558 SVN_ERR(svn_stream_write(eb
->stream
, propstring
->data
, &len
));
561 /* Dump text content */
562 if (must_dump_text
&& (kind
== svn_node_file
))
564 svn_stream_t
*contents
;
567 contents
= svn_stream_from_aprfile(delta_file
, pool
);
569 SVN_ERR(svn_fs_file_contents(&contents
, eb
->fs_root
, path
, pool
));
571 SVN_ERR(svn_stream_copy(contents
, eb
->stream
, pool
));
575 SVN_ERR(svn_stream_write(eb
->stream
, "\n\n", &len
)); /* ### needed? */
582 open_root(void *edit_baton
,
583 svn_revnum_t base_revision
,
587 *root_baton
= make_dir_baton(NULL
, NULL
, SVN_INVALID_REVNUM
,
588 edit_baton
, NULL
, FALSE
, pool
);
594 delete_entry(const char *path
,
595 svn_revnum_t revision
,
599 struct dir_baton
*pb
= parent_baton
;
600 const char *mypath
= apr_pstrdup(pb
->pool
, path
);
602 /* remember this path needs to be deleted. */
603 apr_hash_set(pb
->deleted_entries
, mypath
, APR_HASH_KEY_STRING
, pb
);
610 add_directory(const char *path
,
612 const char *copyfrom_path
,
613 svn_revnum_t copyfrom_rev
,
617 struct dir_baton
*pb
= parent_baton
;
618 struct edit_baton
*eb
= pb
->edit_baton
;
620 svn_boolean_t is_copy
= FALSE
;
621 struct dir_baton
*new_db
622 = make_dir_baton(path
, copyfrom_path
, copyfrom_rev
, eb
, pb
, TRUE
, pool
);
624 /* This might be a replacement -- is the path already deleted? */
625 val
= apr_hash_get(pb
->deleted_entries
, path
, APR_HASH_KEY_STRING
);
627 /* Detect an add-with-history. */
628 is_copy
= ARE_VALID_COPY_ARGS(copyfrom_path
, copyfrom_rev
) ? TRUE
: FALSE
;
631 SVN_ERR(dump_node(eb
, path
,
633 val
? svn_node_action_replace
: svn_node_action_add
,
635 is_copy
? copyfrom_path
: NULL
,
636 is_copy
? copyfrom_rev
: SVN_INVALID_REVNUM
,
640 /* Delete the path, it's now been dumped. */
641 apr_hash_set(pb
->deleted_entries
, path
, APR_HASH_KEY_STRING
, NULL
);
643 new_db
->written_out
= TRUE
;
645 *child_baton
= new_db
;
651 open_directory(const char *path
,
653 svn_revnum_t base_revision
,
657 struct dir_baton
*pb
= parent_baton
;
658 struct edit_baton
*eb
= pb
->edit_baton
;
659 struct dir_baton
*new_db
;
660 const char *cmp_path
= NULL
;
661 svn_revnum_t cmp_rev
= SVN_INVALID_REVNUM
;
663 /* If the parent directory has explicit comparison path and rev,
664 record the same for this one. */
665 if (pb
&& ARE_VALID_COPY_ARGS(pb
->cmp_path
, pb
->cmp_rev
))
667 cmp_path
= svn_path_join(pb
->cmp_path
,
668 svn_path_basename(path
, pool
), pool
);
669 cmp_rev
= pb
->cmp_rev
;
672 new_db
= make_dir_baton(path
, cmp_path
, cmp_rev
, eb
, pb
, FALSE
, pool
);
673 *child_baton
= new_db
;
679 close_directory(void *dir_baton
,
682 struct dir_baton
*db
= dir_baton
;
683 struct edit_baton
*eb
= db
->edit_baton
;
684 apr_hash_index_t
*hi
;
685 apr_pool_t
*subpool
= svn_pool_create(pool
);
687 for (hi
= apr_hash_first(pool
, db
->deleted_entries
);
689 hi
= apr_hash_next(hi
))
693 apr_hash_this(hi
, &key
, NULL
, NULL
);
696 svn_pool_clear(subpool
);
698 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
699 be written out. No big deal at all, really. The loader
701 SVN_ERR(dump_node(eb
, path
,
702 svn_node_unknown
, svn_node_action_delete
,
703 FALSE
, NULL
, SVN_INVALID_REVNUM
, subpool
));
706 svn_pool_destroy(subpool
);
712 add_file(const char *path
,
714 const char *copyfrom_path
,
715 svn_revnum_t copyfrom_rev
,
719 struct dir_baton
*pb
= parent_baton
;
720 struct edit_baton
*eb
= pb
->edit_baton
;
722 svn_boolean_t is_copy
= FALSE
;
724 /* This might be a replacement -- is the path already deleted? */
725 val
= apr_hash_get(pb
->deleted_entries
, path
, APR_HASH_KEY_STRING
);
727 /* Detect add-with-history. */
728 is_copy
= ARE_VALID_COPY_ARGS(copyfrom_path
, copyfrom_rev
) ? TRUE
: FALSE
;
731 SVN_ERR(dump_node(eb
, path
,
733 val
? svn_node_action_replace
: svn_node_action_add
,
735 is_copy
? copyfrom_path
: NULL
,
736 is_copy
? copyfrom_rev
: SVN_INVALID_REVNUM
,
740 /* delete the path, it's now been dumped. */
741 apr_hash_set(pb
->deleted_entries
, path
, APR_HASH_KEY_STRING
, NULL
);
743 *file_baton
= NULL
; /* muhahahaha */
749 open_file(const char *path
,
751 svn_revnum_t ancestor_revision
,
755 struct dir_baton
*pb
= parent_baton
;
756 struct edit_baton
*eb
= pb
->edit_baton
;
757 const char *cmp_path
= NULL
;
758 svn_revnum_t cmp_rev
= SVN_INVALID_REVNUM
;
760 /* If the parent directory has explicit comparison path and rev,
761 record the same for this one. */
762 if (pb
&& ARE_VALID_COPY_ARGS(pb
->cmp_path
, pb
->cmp_rev
))
764 cmp_path
= svn_path_join(pb
->cmp_path
,
765 svn_path_basename(path
, pool
), pool
);
766 cmp_rev
= pb
->cmp_rev
;
769 SVN_ERR(dump_node(eb
, path
,
770 svn_node_file
, svn_node_action_change
,
771 FALSE
, cmp_path
, cmp_rev
, pool
));
773 *file_baton
= NULL
; /* muhahahaha again */
779 change_dir_prop(void *parent_baton
,
781 const svn_string_t
*value
,
784 struct dir_baton
*db
= parent_baton
;
785 struct edit_baton
*eb
= db
->edit_baton
;
787 /* This function is what distinguishes between a directory that is
788 opened to merely get somewhere, vs. one that is opened because it
789 *actually* changed by itself. */
790 if (! db
->written_out
)
792 SVN_ERR(dump_node(eb
, db
->path
,
793 svn_node_dir
, svn_node_action_change
,
794 FALSE
, db
->cmp_path
, db
->cmp_rev
, pool
));
795 db
->written_out
= TRUE
;
803 get_dump_editor(const svn_delta_editor_t
**editor
,
807 const char *root_path
,
808 svn_stream_t
*stream
,
809 svn_stream_t
*feedback_stream
,
810 svn_revnum_t oldest_dumped_rev
,
811 svn_boolean_t use_deltas
,
814 /* Allocate an edit baton to be stored in every directory baton.
815 Set it up for the directory baton we create here, which is the
817 struct edit_baton
*eb
= apr_pcalloc(pool
, sizeof(*eb
));
818 svn_delta_editor_t
*dump_editor
= svn_delta_default_editor(pool
);
820 /* Set up the edit baton. */
822 eb
->feedback_stream
= feedback_stream
;
823 eb
->oldest_dumped_rev
= oldest_dumped_rev
;
824 eb
->bufsize
= sizeof(eb
->buffer
);
825 eb
->path
= apr_pstrdup(pool
, root_path
);
826 SVN_ERR(svn_fs_revision_root(&(eb
->fs_root
), fs
, to_rev
, pool
));
827 eb
->current_rev
= to_rev
;
828 eb
->use_deltas
= use_deltas
;
830 /* Set up the editor. */
831 dump_editor
->open_root
= open_root
;
832 dump_editor
->delete_entry
= delete_entry
;
833 dump_editor
->add_directory
= add_directory
;
834 dump_editor
->open_directory
= open_directory
;
835 dump_editor
->close_directory
= close_directory
;
836 dump_editor
->change_dir_prop
= change_dir_prop
;
837 dump_editor
->add_file
= add_file
;
838 dump_editor
->open_file
= open_file
;
841 *editor
= dump_editor
;
846 /*----------------------------------------------------------------------*/
848 /** The main dumping routine, svn_repos_dump_fs. **/
851 /* Helper for svn_repos_dump_fs.
853 Write a revision record of REV in FS to writable STREAM, using POOL.
856 write_revision_record(svn_stream_t
*stream
,
863 svn_stringbuf_t
*encoded_prophash
;
865 svn_string_t
*datevalue
;
867 /* Read the revision props even if we're aren't going to dump
868 them for verification purposes */
869 SVN_ERR(svn_fs_revision_proplist(&props
, fs
, rev
, pool
));
871 /* Run revision date properties through the time conversion to
872 canonicalize them. */
873 /* ### Remove this when it is no longer needed for sure. */
874 datevalue
= apr_hash_get(props
, SVN_PROP_REVISION_DATE
,
875 APR_HASH_KEY_STRING
);
878 SVN_ERR(svn_time_from_cstring(&timetemp
, datevalue
->data
, pool
));
879 datevalue
= svn_string_create(svn_time_to_cstring(timetemp
, pool
),
881 apr_hash_set(props
, SVN_PROP_REVISION_DATE
, APR_HASH_KEY_STRING
,
885 write_hash_to_stringbuf(props
, NULL
, &encoded_prophash
, pool
);
887 /* ### someday write a revision-content-checksum */
889 SVN_ERR(svn_stream_printf(stream
, pool
,
890 SVN_REPOS_DUMPFILE_REVISION_NUMBER
892 SVN_ERR(svn_stream_printf(stream
, pool
,
893 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
894 ": %" APR_SIZE_T_FMT
"\n",
895 encoded_prophash
->len
));
897 /* Write out a regular Content-length header for the benefit of
898 non-Subversion RFC-822 parsers. */
899 SVN_ERR(svn_stream_printf(stream
, pool
,
900 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
901 ": %" APR_SIZE_T_FMT
"\n\n",
902 encoded_prophash
->len
));
904 len
= encoded_prophash
->len
;
905 SVN_ERR(svn_stream_write(stream
, encoded_prophash
->data
, &len
));
908 SVN_ERR(svn_stream_write(stream
, "\n", &len
));
915 /* The main dumper. */
917 svn_repos_dump_fs2(svn_repos_t
*repos
,
918 svn_stream_t
*stream
,
919 svn_stream_t
*feedback_stream
,
920 svn_revnum_t start_rev
,
921 svn_revnum_t end_rev
,
922 svn_boolean_t incremental
,
923 svn_boolean_t use_deltas
,
924 svn_cancel_func_t cancel_func
,
928 const svn_delta_editor_t
*dump_editor
;
929 void *dump_edit_baton
;
931 svn_fs_t
*fs
= svn_repos_fs(repos
);
932 apr_pool_t
*subpool
= svn_pool_create(pool
);
933 svn_revnum_t youngest
;
936 svn_boolean_t dumping
= (stream
!= NULL
);
938 /* Determine the current youngest revision of the filesystem. */
939 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
941 /* Use default vals if necessary. */
942 if (! SVN_IS_VALID_REVNUM(start_rev
))
944 if (! SVN_IS_VALID_REVNUM(end_rev
))
947 stream
= svn_stream_empty(pool
);
948 if (! feedback_stream
)
949 feedback_stream
= svn_stream_empty(pool
);
951 /* Validate the revisions. */
952 if (start_rev
> end_rev
)
953 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
954 _("Start revision %ld"
955 " is greater than end revision %ld"),
957 if (end_rev
> youngest
)
958 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
959 _("End revision %ld is invalid "
960 "(youngest revision is %ld)"),
962 if ((start_rev
== 0) && incremental
)
963 incremental
= FALSE
; /* revision 0 looks the same regardless of
964 whether or not this is an incremental
965 dump, so just simplify things. */
967 /* Write out the UUID. */
968 SVN_ERR(svn_fs_get_uuid(fs
, &uuid
, pool
));
970 /* If we're not using deltas, use the previous version, for
971 compatibility with svn 1.0.x. */
972 version
= SVN_REPOS_DUMPFILE_FORMAT_VERSION
;
976 /* Write out "general" metadata for the dumpfile. In this case, a
977 magic header followed by a dumpfile format version. */
978 SVN_ERR(svn_stream_printf(stream
, pool
,
979 SVN_REPOS_DUMPFILE_MAGIC_HEADER
": %d\n\n",
981 SVN_ERR(svn_stream_printf(stream
, pool
, SVN_REPOS_DUMPFILE_UUID
984 /* Main loop: we're going to dump revision i. */
985 for (i
= start_rev
; i
<= end_rev
; i
++)
987 svn_revnum_t from_rev
, to_rev
;
988 svn_fs_root_t
*to_root
;
989 svn_boolean_t use_deltas_for_rev
;
991 svn_pool_clear(subpool
);
993 /* Check for cancellation. */
995 SVN_ERR(cancel_func(cancel_baton
));
997 /* Special-case the initial revision dump: it needs to contain
998 *all* nodes, because it's the foundation of all future
999 revisions in the dumpfile. */
1000 if ((i
== start_rev
) && (! incremental
))
1002 /* Special-special-case a dump of revision 0. */
1005 /* Just write out the one revision 0 record and move on.
1006 The parser might want to use its properties. */
1007 SVN_ERR(write_revision_record(stream
, fs
, 0, subpool
));
1012 /* Compare START_REV to revision 0, so that everything
1013 appears to be added. */
1019 /* In the normal case, we want to compare consecutive revs. */
1024 /* Write the revision record. */
1025 SVN_ERR(write_revision_record(stream
, fs
, to_rev
, subpool
));
1027 /* Fetch the editor which dumps nodes to a file. Regardless of
1028 what we've been told, don't use deltas for the first rev of a
1029 non-incremental dump. */
1030 use_deltas_for_rev
= use_deltas
&& (incremental
|| i
!= start_rev
);
1031 SVN_ERR(get_dump_editor(&dump_editor
, &dump_edit_baton
, fs
, to_rev
,
1032 "/", stream
, feedback_stream
, start_rev
,
1033 use_deltas_for_rev
, subpool
));
1035 /* Drive the editor in one way or another. */
1036 SVN_ERR(svn_fs_revision_root(&to_root
, fs
, to_rev
, subpool
));
1038 /* If this is the first revision of a non-incremental dump,
1039 we're in for a full tree dump. Otherwise, we want to simply
1040 replay the revision. */
1041 if ((i
== start_rev
) && (! incremental
))
1043 svn_fs_root_t
*from_root
;
1044 SVN_ERR(svn_fs_revision_root(&from_root
, fs
, from_rev
, subpool
));
1045 SVN_ERR(svn_repos_dir_delta2(from_root
, "/", "",
1047 dump_editor
, dump_edit_baton
,
1050 FALSE
, /* don't send text-deltas */
1052 FALSE
, /* don't send entry props */
1053 FALSE
, /* don't ignore ancestry */
1058 SVN_ERR(svn_repos_replay2(to_root
, "", SVN_INVALID_REVNUM
, FALSE
,
1059 dump_editor
, dump_edit_baton
,
1060 NULL
, NULL
, subpool
));
1064 SVN_ERR(svn_stream_printf(feedback_stream
, pool
,
1066 ? _("* Dumped revision %ld.\n")
1067 : _("* Verified revision %ld.\n"),
1071 svn_pool_destroy(subpool
);
1073 return SVN_NO_ERROR
;
1077 svn_repos_dump_fs(svn_repos_t
*repos
,
1078 svn_stream_t
*stream
,
1079 svn_stream_t
*feedback_stream
,
1080 svn_revnum_t start_rev
,
1081 svn_revnum_t end_rev
,
1082 svn_boolean_t incremental
,
1083 svn_cancel_func_t cancel_func
,
1087 return svn_repos_dump_fs2(repos
, stream
, feedback_stream
, start_rev
,
1088 end_rev
, incremental
, FALSE
, cancel_func
,
1089 cancel_baton
, pool
);
1093 /*----------------------------------------------------------------------*/
1095 /* verify, based on dump */
1098 /* Creating a new revision that changes /A/B/E/bravo means creating new
1099 directory listings for /, /A, /A/B, and /A/B/E in the new revision, with
1100 each entry not changed in the new revision a link back to the entry in a
1101 previous revision. svn_repos_replay()ing a revision does not verify that
1102 those links are correct.
1104 For paths actually changed in the revision we verify, we get directory
1105 contents or file length twice: once in the dump editor, and once here.
1106 We could create a new verify baton, store in it the changed paths, and
1107 skip those here, but that means building an entire wrapper editor and
1108 managing two levels of batons. The impact from checking these entries
1109 twice should be minimal, while the code to avoid it is not.
1112 static svn_error_t
*
1113 verify_directory_entry(void *baton
, const void *key
, apr_ssize_t klen
,
1114 void *val
, apr_pool_t
*pool
)
1116 struct dir_baton
*db
= baton
;
1117 char *path
= svn_path_join(db
->path
, (const char *)key
, pool
);
1118 svn_node_kind_t kind
;
1119 apr_hash_t
*dirents
;
1122 SVN_ERR(svn_fs_check_path(&kind
, db
->edit_baton
->fs_root
, path
, pool
));
1125 /* Getting this directory's contents is enough to ensure that our
1126 link to it is correct. */
1127 SVN_ERR(svn_fs_dir_entries(&dirents
, db
->edit_baton
->fs_root
, path
, pool
));
1130 /* Getting this file's size is enough to ensure that our link to it
1132 SVN_ERR(svn_fs_file_length(&len
, db
->edit_baton
->fs_root
, path
, pool
));
1135 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND
, NULL
,
1136 _("Unexpected node kind %d for '%s'"), kind
, path
);
1139 return SVN_NO_ERROR
;
1142 static svn_error_t
*
1143 verify_close_directory(void *dir_baton
,
1146 struct dir_baton
*db
= dir_baton
;
1147 apr_hash_t
*dirents
;
1148 SVN_ERR(svn_fs_dir_entries(&dirents
, db
->edit_baton
->fs_root
,
1150 SVN_ERR(svn_iter_apr_hash(NULL
, dirents
, verify_directory_entry
,
1152 return close_directory(dir_baton
, pool
);
1156 svn_repos_verify_fs(svn_repos_t
*repos
,
1157 svn_stream_t
*feedback_stream
,
1158 svn_revnum_t start_rev
,
1159 svn_revnum_t end_rev
,
1160 svn_cancel_func_t cancel_func
,
1164 svn_fs_t
*fs
= svn_repos_fs(repos
);
1165 svn_revnum_t youngest
;
1167 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1169 /* Determine the current youngest revision of the filesystem. */
1170 SVN_ERR(svn_fs_youngest_rev(&youngest
, fs
, pool
));
1172 /* Use default vals if necessary. */
1173 if (! SVN_IS_VALID_REVNUM(start_rev
))
1175 if (! SVN_IS_VALID_REVNUM(end_rev
))
1177 if (! feedback_stream
)
1178 feedback_stream
= svn_stream_empty(pool
);
1180 /* Validate the revisions. */
1181 if (start_rev
> end_rev
)
1182 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
1183 _("Start revision %ld"
1184 " is greater than end revision %ld"),
1185 start_rev
, end_rev
);
1186 if (end_rev
> youngest
)
1187 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS
, NULL
,
1188 _("End revision %ld is invalid "
1189 "(youngest revision is %ld)"),
1192 for (rev
= start_rev
; rev
<= end_rev
; rev
++)
1194 svn_delta_editor_t
*dump_editor
;
1195 void *dump_edit_baton
;
1196 const svn_delta_editor_t
*cancel_editor
;
1197 void *cancel_edit_baton
;
1198 svn_fs_root_t
*to_root
;
1200 svn_pool_clear(iterpool
);
1202 /* Get cancellable dump editor, but with our close_directory handler. */
1203 SVN_ERR(get_dump_editor((const svn_delta_editor_t
**)&dump_editor
,
1204 &dump_edit_baton
, fs
, rev
, "",
1205 svn_stream_empty(pool
), feedback_stream
,
1206 start_rev
, FALSE
, iterpool
));
1207 dump_editor
->close_directory
= verify_close_directory
;
1208 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func
, cancel_baton
,
1209 dump_editor
, dump_edit_baton
,
1214 SVN_ERR(svn_fs_revision_root(&to_root
, fs
, rev
, iterpool
));
1215 SVN_ERR(svn_repos_replay2(to_root
, "", SVN_INVALID_REVNUM
, FALSE
,
1216 cancel_editor
, cancel_edit_baton
,
1217 NULL
, NULL
, iterpool
));
1218 SVN_ERR(svn_stream_printf(feedback_stream
, iterpool
,
1219 _("* Verified revision %ld.\n"),
1223 svn_pool_destroy(iterpool
);
1225 return SVN_NO_ERROR
;