In the command-line client, forbid
[svn.git] / subversion / libsvn_repos / dump.c
blob7b555b194e25ab483e9036ff641fd6b46dd2660e
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"
22 #include "svn_fs.h"
23 #include "svn_repos.h"
24 #include "svn_string.h"
25 #include "svn_path.h"
26 #include "svn_time.h"
27 #include "svn_md5.h"
28 #include "svn_props.h"
31 #define ARE_VALID_COPY_ARGS(p,r) ((p && SVN_IS_VALID_REVNUM(r)) ? 1 : 0)
33 /*----------------------------------------------------------------------*/
35 /** A variant of our hash-writing routine in libsvn_subr; this one
36 writes to a stringbuf instead of a file, and outputs PROPS-END
37 instead of END. If OLDHASH is not NULL, then only properties
38 which vary from OLDHASH will be written, and properties which
39 exist only in OLDHASH will be written out with "D" entries
40 (like "K" entries but with no corresponding value). **/
42 static void
43 write_hash_to_stringbuf(apr_hash_t *hash,
44 apr_hash_t *oldhash,
45 svn_stringbuf_t **strbuf,
46 apr_pool_t *pool)
48 apr_hash_index_t *this; /* current hash entry */
50 *strbuf = svn_stringbuf_create("", pool);
52 for (this = apr_hash_first(pool, hash); this; this = apr_hash_next(this))
54 const void *key;
55 void *val;
56 apr_ssize_t keylen;
57 svn_string_t *value;
59 /* Get this key and val. */
60 apr_hash_this(this, &key, &keylen, &val);
61 value = val;
63 /* Don't output properties equal to the ones in oldhash, if present. */
64 if (oldhash)
66 svn_string_t *oldvalue = apr_hash_get(oldhash, key, keylen);
68 if (oldvalue && svn_string_compare(value, oldvalue))
69 continue;
72 /* Output name length, then name. */
74 svn_stringbuf_appendcstr(*strbuf,
75 apr_psprintf(pool, "K %" APR_SSIZE_T_FMT "\n",
76 keylen));
78 svn_stringbuf_appendbytes(*strbuf, (const char *) key, keylen);
79 svn_stringbuf_appendbytes(*strbuf, "\n", 1);
81 /* Output value length, then value. */
83 svn_stringbuf_appendcstr(*strbuf,
84 apr_psprintf(pool, "V %" APR_SIZE_T_FMT "\n",
85 value->len));
87 svn_stringbuf_appendbytes(*strbuf, value->data, value->len);
88 svn_stringbuf_appendbytes(*strbuf, "\n", 1);
91 if (oldhash)
93 /* Output a "D " entry for each property in oldhash but not hash. */
94 for (this = apr_hash_first(pool, oldhash); this;
95 this = apr_hash_next(this))
97 const void *key;
98 apr_ssize_t keylen;
100 /* Get this key. */
101 apr_hash_this(this, &key, &keylen, NULL);
103 /* Only output values deleted in hash. */
104 if (apr_hash_get(hash, key, keylen))
105 continue;
107 /* Output name length, then name. */
109 svn_stringbuf_appendcstr(*strbuf,
110 apr_psprintf(pool,
111 "D %" APR_SSIZE_T_FMT "\n",
112 keylen));
114 svn_stringbuf_appendbytes(*strbuf, (const char *) key, keylen);
115 svn_stringbuf_appendbytes(*strbuf, "\n", 1);
118 svn_stringbuf_appendbytes(*strbuf, "PROPS-END\n", 10);
122 /* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and
123 store it into a new temporary file *TEMPFILE. OLDROOT may be NULL,
124 in which case the delta will be computed against an empty file, as
125 per the svn_fs_get_file_delta_stream docstring. Record the length
126 of the temporary file in *LEN, and rewind the file before
127 returning. */
128 static svn_error_t *
129 store_delta(apr_file_t **tempfile, svn_filesize_t *len,
130 svn_fs_root_t *oldroot, const char *oldpath,
131 svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool)
133 const char *tempdir;
134 svn_stream_t *temp_stream;
135 apr_off_t offset = 0;
136 svn_txdelta_stream_t *delta_stream;
137 svn_txdelta_window_handler_t wh;
138 void *whb;
140 /* Create a temporary file and open a stream to it. */
141 SVN_ERR(svn_io_temp_dir(&tempdir, pool));
142 SVN_ERR(svn_io_open_unique_file2(tempfile, NULL,
143 apr_psprintf(pool, "%s/dump", tempdir),
144 ".tmp", svn_io_file_del_on_close, pool));
145 temp_stream = svn_stream_from_aprfile(*tempfile, pool);
147 /* Compute the delta and send it to the temporary file. */
148 SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath,
149 newroot, newpath, pool));
150 svn_txdelta_to_svndiff2(&wh, &whb, temp_stream, 0, pool);
151 SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool));
153 /* Get the length of the temporary file and rewind it. */
154 SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool));
155 *len = offset;
156 offset = 0;
157 SVN_ERR(svn_io_file_seek(*tempfile, APR_SET, &offset, pool));
158 return SVN_NO_ERROR;
162 /*----------------------------------------------------------------------*/
164 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
166 /* Look, mom! No file batons! */
168 struct edit_baton
170 /* The path which implicitly prepends all full paths coming into
171 this editor. This will almost always be "" or "/". */
172 const char *path;
174 /* The stream to dump to. */
175 svn_stream_t *stream;
177 /* Send feedback here, if non-NULL */
178 svn_stream_t *feedback_stream;
180 /* The fs revision root, so we can read the contents of paths. */
181 svn_fs_root_t *fs_root;
182 svn_revnum_t current_rev;
184 /* True if dumped nodes should output deltas instead of full text. */
185 svn_boolean_t use_deltas;
187 /* The first revision dumped in this dumpstream. */
188 svn_revnum_t oldest_dumped_rev;
190 /* reusable buffer for writing file contents */
191 char buffer[SVN__STREAM_CHUNK_SIZE];
192 apr_size_t bufsize;
195 struct dir_baton
197 struct edit_baton *edit_baton;
198 struct dir_baton *parent_dir_baton;
200 /* is this directory a new addition to this revision? */
201 svn_boolean_t added;
203 /* has this directory been written to the output stream? */
204 svn_boolean_t written_out;
206 /* the absolute path to this directory */
207 const char *path;
209 /* the comparison path and revision of this directory. if both of
210 these are valid, use them as a source against which to compare
211 the directory instead of the default comparison source of PATH in
212 the previous revision. */
213 const char *cmp_path;
214 svn_revnum_t cmp_rev;
216 /* hash of paths that need to be deleted, though some -might- be
217 replaced. maps const char * paths to this dir_baton. (they're
218 full paths, because that's what the editor driver gives us. but
219 really, they're all within this directory.) */
220 apr_hash_t *deleted_entries;
222 /* pool to be used for deleting the hash items */
223 apr_pool_t *pool;
227 /* Make a directory baton to represent the directory was path
228 (relative to EDIT_BATON's path) is PATH.
230 CMP_PATH/CMP_REV are the path/revision against which this directory
231 should be compared for changes. If either is omitted (NULL for the
232 path, SVN_INVALID_REVNUM for the rev), just compare this directory
233 PATH against itself in the previous revision.
235 PARENT_DIR_BATON is the directory baton of this directory's parent,
236 or NULL if this is the top-level directory of the edit. ADDED
237 indicated if this directory is newly added in this revision.
238 Perform all allocations in POOL. */
239 static struct dir_baton *
240 make_dir_baton(const char *path,
241 const char *cmp_path,
242 svn_revnum_t cmp_rev,
243 void *edit_baton,
244 void *parent_dir_baton,
245 svn_boolean_t added,
246 apr_pool_t *pool)
248 struct edit_baton *eb = edit_baton;
249 struct dir_baton *pb = parent_dir_baton;
250 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
251 const char *full_path;
253 /* A path relative to nothing? I don't think so. */
254 if (path && (! pb))
255 abort();
257 /* Construct the full path of this node. */
258 if (pb)
259 full_path = svn_path_join(eb->path, path, pool);
260 else
261 full_path = apr_pstrdup(pool, eb->path);
263 /* Remove leading slashes from copyfrom paths. */
264 if (cmp_path)
265 cmp_path = ((*cmp_path == '/') ? cmp_path + 1 : cmp_path);
267 new_db->edit_baton = eb;
268 new_db->parent_dir_baton = pb;
269 new_db->path = full_path;
270 new_db->cmp_path = cmp_path ? apr_pstrdup(pool, cmp_path) : NULL;
271 new_db->cmp_rev = cmp_rev;
272 new_db->added = added;
273 new_db->written_out = FALSE;
274 new_db->deleted_entries = apr_hash_make(pool);
275 new_db->pool = pool;
277 return new_db;
281 /* This helper is the main "meat" of the editor -- it does all the
282 work of writing a node record.
284 Write out a node record for PATH of type KIND under EB->FS_ROOT.
285 ACTION describes what is happening to the node (see enum svn_node_action).
286 Write record to writable EB->STREAM, using EB->BUFFER to write in chunks.
288 If the node was itself copied, IS_COPY is TRUE and the
289 path/revision of the copy source are in CMP_PATH/CMP_REV. If
290 IS_COPY is FALSE, yet CMP_PATH/CMP_REV are valid, this node is part
291 of a copied subtree.
293 static svn_error_t *
294 dump_node(struct edit_baton *eb,
295 const char *path, /* an absolute path. */
296 svn_node_kind_t kind,
297 enum svn_node_action action,
298 svn_boolean_t is_copy,
299 const char *cmp_path,
300 svn_revnum_t cmp_rev,
301 apr_pool_t *pool)
303 svn_stringbuf_t *propstring;
304 svn_filesize_t content_length = 0;
305 apr_size_t len;
306 svn_boolean_t must_dump_text = FALSE, must_dump_props = FALSE;
307 const char *compare_path = path;
308 svn_revnum_t compare_rev = eb->current_rev - 1;
309 svn_fs_root_t *compare_root = NULL;
310 apr_file_t *delta_file = NULL;
312 /* Write out metadata headers for this file node. */
313 SVN_ERR(svn_stream_printf(eb->stream, pool,
314 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
315 (*path == '/') ? path + 1 : path));
316 if (kind == svn_node_file)
317 SVN_ERR(svn_stream_printf(eb->stream, pool,
318 SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
319 else if (kind == svn_node_dir)
320 SVN_ERR(svn_stream_printf(eb->stream, pool,
321 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
323 /* Remove leading slashes from copyfrom paths. */
324 if (cmp_path)
325 cmp_path = ((*cmp_path == '/') ? cmp_path + 1 : cmp_path);
327 /* Validate the comparison path/rev. */
328 if (ARE_VALID_COPY_ARGS(cmp_path, cmp_rev))
330 compare_path = cmp_path;
331 compare_rev = cmp_rev;
334 if (action == svn_node_action_change)
336 SVN_ERR(svn_stream_printf(eb->stream, pool,
337 SVN_REPOS_DUMPFILE_NODE_ACTION
338 ": change\n"));
340 /* either the text or props changed, or possibly both. */
341 SVN_ERR(svn_fs_revision_root(&compare_root,
342 svn_fs_root_fs(eb->fs_root),
343 compare_rev, pool));
345 SVN_ERR(svn_fs_props_changed(&must_dump_props,
346 compare_root, compare_path,
347 eb->fs_root, path, pool));
348 if (kind == svn_node_file)
349 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
350 compare_root, compare_path,
351 eb->fs_root, path, pool));
353 else if (action == svn_node_action_replace)
355 if (! is_copy)
357 /* a simple delete+add, implied by a single 'replace' action. */
358 SVN_ERR(svn_stream_printf(eb->stream, pool,
359 SVN_REPOS_DUMPFILE_NODE_ACTION
360 ": replace\n"));
362 /* definitely need to dump all content for a replace. */
363 if (kind == svn_node_file)
364 must_dump_text = TRUE;
365 must_dump_props = TRUE;
367 else
369 /* more complex: delete original, then add-with-history. */
371 /* the path & kind headers have already been printed; just
372 add a delete action, and end the current record.*/
373 SVN_ERR(svn_stream_printf(eb->stream, pool,
374 SVN_REPOS_DUMPFILE_NODE_ACTION
375 ": delete\n\n"));
377 /* recurse: print an additional add-with-history record. */
378 SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
379 is_copy, compare_path, compare_rev, pool));
381 /* we can leave this routine quietly now, don't need to dump
382 any content; that was already done in the second record. */
383 must_dump_text = FALSE;
384 must_dump_props = FALSE;
387 else if (action == svn_node_action_delete)
389 SVN_ERR(svn_stream_printf(eb->stream, pool,
390 SVN_REPOS_DUMPFILE_NODE_ACTION
391 ": delete\n"));
393 /* we can leave this routine quietly now, don't need to dump
394 any content. */
395 must_dump_text = FALSE;
396 must_dump_props = FALSE;
398 else if (action == svn_node_action_add)
400 SVN_ERR(svn_stream_printf(eb->stream, pool,
401 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
403 if (! is_copy)
405 /* Dump all contents for a simple 'add'. */
406 if (kind == svn_node_file)
407 must_dump_text = TRUE;
408 must_dump_props = TRUE;
410 else
412 if (cmp_rev < eb->oldest_dumped_rev)
413 SVN_ERR(svn_stream_printf
414 (eb->feedback_stream, pool,
415 _("WARNING: Referencing data in revision %ld"
416 ", which is older than the oldest\nWARNING: dumped revision "
417 "(%ld). Loading this dump into an empty "
418 "repository\nWARNING: will fail.\n"),
419 cmp_rev, eb->oldest_dumped_rev));
421 SVN_ERR(svn_stream_printf(eb->stream, pool,
422 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
423 ": %ld\n"
424 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
425 ": %s\n",
426 cmp_rev, cmp_path));
428 SVN_ERR(svn_fs_revision_root(&compare_root,
429 svn_fs_root_fs(eb->fs_root),
430 compare_rev, pool));
432 /* Need to decide if the copied node had any extra textual or
433 property mods as well. */
434 SVN_ERR(svn_fs_props_changed(&must_dump_props,
435 compare_root, compare_path,
436 eb->fs_root, path, pool));
437 if (kind == svn_node_file)
439 unsigned char md5_digest[APR_MD5_DIGESTSIZE];
440 const char *hex_digest;
441 SVN_ERR(svn_fs_contents_changed(&must_dump_text,
442 compare_root, compare_path,
443 eb->fs_root, path, pool));
445 SVN_ERR(svn_fs_file_md5_checksum(md5_digest, compare_root,
446 compare_path, pool));
447 hex_digest = svn_md5_digest_to_cstring(md5_digest, pool);
448 if (hex_digest)
449 SVN_ERR(svn_stream_printf(eb->stream, pool,
450 SVN_REPOS_DUMPFILE_TEXT_COPY_SOURCE_CHECKSUM
451 ": %s\n", hex_digest));
456 if ((! must_dump_text) && (! must_dump_props))
458 /* If we're not supposed to dump text or props, so be it, we can
459 just go home. However, if either one needs to be dumped,
460 then our dumpstream format demands that at a *minimum*, we
461 see a lone "PROPS-END" as a divider between text and props
462 content within the content-block. */
463 len = 2;
464 return svn_stream_write(eb->stream, "\n\n", &len); /* ### needed? */
467 /*** Start prepping content to dump... ***/
469 /* If we are supposed to dump properties, write out a property
470 length header and generate a stringbuf that contains those
471 property values here. */
472 if (must_dump_props)
474 apr_hash_t *prophash, *oldhash = NULL;
475 apr_size_t proplen;
477 SVN_ERR(svn_fs_node_proplist(&prophash, eb->fs_root, path, pool));
478 if (eb->use_deltas && compare_root)
480 /* Fetch the old property hash to diff against and output a header
481 saying that our property contents are a delta. */
482 SVN_ERR(svn_fs_node_proplist(&oldhash, compare_root, compare_path,
483 pool));
484 SVN_ERR(svn_stream_printf(eb->stream, pool,
485 SVN_REPOS_DUMPFILE_PROP_DELTA
486 ": true\n"));
488 write_hash_to_stringbuf(prophash, oldhash, &propstring, pool);
489 proplen = propstring->len;
490 content_length += proplen;
491 SVN_ERR(svn_stream_printf(eb->stream, pool,
492 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
493 ": %" APR_SIZE_T_FMT "\n", proplen));
496 /* If we are supposed to dump text, write out a text length header
497 here, and an MD5 checksum (if available). */
498 if (must_dump_text && (kind == svn_node_file))
500 unsigned char md5_digest[APR_MD5_DIGESTSIZE];
501 const char *hex_digest;
502 svn_filesize_t textlen;
504 if (eb->use_deltas)
506 /* Compute the text delta now and write it into a temporary
507 file, so that we can find its length. Output a header
508 saying our text contents are a delta. */
509 SVN_ERR(store_delta(&delta_file, &textlen, compare_root,
510 compare_path, eb->fs_root, path, pool));
511 SVN_ERR(svn_stream_printf(eb->stream, pool,
512 SVN_REPOS_DUMPFILE_TEXT_DELTA
513 ": true\n"));
515 if (compare_root)
517 SVN_ERR(svn_fs_file_md5_checksum(md5_digest, compare_root,
518 compare_path, pool));
519 hex_digest = svn_md5_digest_to_cstring(md5_digest, pool);
520 if (hex_digest)
521 SVN_ERR(svn_stream_printf(eb->stream, pool,
522 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM
523 ": %s\n", hex_digest));
526 else
528 /* Just fetch the length of the file. */
529 SVN_ERR(svn_fs_file_length(&textlen, eb->fs_root, path, pool));
532 content_length += textlen;
533 SVN_ERR(svn_stream_printf(eb->stream, pool,
534 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
535 ": %" SVN_FILESIZE_T_FMT "\n", textlen));
537 SVN_ERR(svn_fs_file_md5_checksum(md5_digest, eb->fs_root, path, pool));
538 hex_digest = svn_md5_digest_to_cstring(md5_digest, pool);
539 if (hex_digest)
540 SVN_ERR(svn_stream_printf(eb->stream, pool,
541 SVN_REPOS_DUMPFILE_TEXT_CONTENT_CHECKSUM
542 ": %s\n", hex_digest));
545 /* 'Content-length:' is the last header before we dump the content,
546 and is the sum of the text and prop contents lengths. We write
547 this only for the benefit of non-Subversion RFC-822 parsers. */
548 SVN_ERR(svn_stream_printf(eb->stream, pool,
549 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
550 ": %" SVN_FILESIZE_T_FMT "\n\n",
551 content_length));
553 /* Dump property content if we're supposed to do so. */
554 if (must_dump_props)
556 len = propstring->len;
557 SVN_ERR(svn_stream_write(eb->stream, propstring->data, &len));
560 /* Dump text content */
561 if (must_dump_text && (kind == svn_node_file))
563 svn_stream_t *contents;
565 if (delta_file)
566 contents = svn_stream_from_aprfile(delta_file, pool);
567 else
568 SVN_ERR(svn_fs_file_contents(&contents, eb->fs_root, path, pool));
570 SVN_ERR(svn_stream_copy(contents, eb->stream, pool));
573 len = 2;
574 SVN_ERR(svn_stream_write(eb->stream, "\n\n", &len)); /* ### needed? */
576 return SVN_NO_ERROR;
580 static svn_error_t *
581 open_root(void *edit_baton,
582 svn_revnum_t base_revision,
583 apr_pool_t *pool,
584 void **root_baton)
586 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
587 edit_baton, NULL, FALSE, pool);
588 return SVN_NO_ERROR;
592 static svn_error_t *
593 delete_entry(const char *path,
594 svn_revnum_t revision,
595 void *parent_baton,
596 apr_pool_t *pool)
598 struct dir_baton *pb = parent_baton;
599 const char *mypath = apr_pstrdup(pb->pool, path);
601 /* remember this path needs to be deleted. */
602 apr_hash_set(pb->deleted_entries, mypath, APR_HASH_KEY_STRING, pb);
604 return SVN_NO_ERROR;
608 static svn_error_t *
609 add_directory(const char *path,
610 void *parent_baton,
611 const char *copyfrom_path,
612 svn_revnum_t copyfrom_rev,
613 apr_pool_t *pool,
614 void **child_baton)
616 struct dir_baton *pb = parent_baton;
617 struct edit_baton *eb = pb->edit_baton;
618 void *val;
619 svn_boolean_t is_copy = FALSE;
620 struct dir_baton *new_db
621 = make_dir_baton(path, copyfrom_path, copyfrom_rev, eb, pb, TRUE, pool);
623 /* This might be a replacement -- is the path already deleted? */
624 val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
626 /* Detect an add-with-history. */
627 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev) ? TRUE : FALSE;
629 /* Dump the node. */
630 SVN_ERR(dump_node(eb, path,
631 svn_node_dir,
632 val ? svn_node_action_replace : svn_node_action_add,
633 is_copy,
634 is_copy ? copyfrom_path : NULL,
635 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
636 pool));
638 if (val)
639 /* Delete the path, it's now been dumped. */
640 apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
642 new_db->written_out = TRUE;
644 *child_baton = new_db;
645 return SVN_NO_ERROR;
649 static svn_error_t *
650 open_directory(const char *path,
651 void *parent_baton,
652 svn_revnum_t base_revision,
653 apr_pool_t *pool,
654 void **child_baton)
656 struct dir_baton *pb = parent_baton;
657 struct edit_baton *eb = pb->edit_baton;
658 struct dir_baton *new_db;
659 const char *cmp_path = NULL;
660 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
662 /* If the parent directory has explicit comparison path and rev,
663 record the same for this one. */
664 if (pb && ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
666 cmp_path = svn_path_join(pb->cmp_path,
667 svn_path_basename(path, pool), pool);
668 cmp_rev = pb->cmp_rev;
671 new_db = make_dir_baton(path, cmp_path, cmp_rev, eb, pb, FALSE, pool);
672 *child_baton = new_db;
673 return SVN_NO_ERROR;
677 static svn_error_t *
678 close_directory(void *dir_baton,
679 apr_pool_t *pool)
681 struct dir_baton *db = dir_baton;
682 struct edit_baton *eb = db->edit_baton;
683 apr_hash_index_t *hi;
684 apr_pool_t *subpool = svn_pool_create(pool);
686 for (hi = apr_hash_first(pool, db->deleted_entries);
688 hi = apr_hash_next(hi))
690 const void *key;
691 const char *path;
692 apr_hash_this(hi, &key, NULL, NULL);
693 path = key;
695 svn_pool_clear(subpool);
697 /* By sending 'svn_node_unknown', the Node-kind: header simply won't
698 be written out. No big deal at all, really. The loader
699 shouldn't care. */
700 SVN_ERR(dump_node(eb, path,
701 svn_node_unknown, svn_node_action_delete,
702 FALSE, NULL, SVN_INVALID_REVNUM, subpool));
705 svn_pool_destroy(subpool);
706 return SVN_NO_ERROR;
710 static svn_error_t *
711 add_file(const char *path,
712 void *parent_baton,
713 const char *copyfrom_path,
714 svn_revnum_t copyfrom_rev,
715 apr_pool_t *pool,
716 void **file_baton)
718 struct dir_baton *pb = parent_baton;
719 struct edit_baton *eb = pb->edit_baton;
720 void *val;
721 svn_boolean_t is_copy = FALSE;
723 /* This might be a replacement -- is the path already deleted? */
724 val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
726 /* Detect add-with-history. */
727 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev) ? TRUE : FALSE;
729 /* Dump the node. */
730 SVN_ERR(dump_node(eb, path,
731 svn_node_file,
732 val ? svn_node_action_replace : svn_node_action_add,
733 is_copy,
734 is_copy ? copyfrom_path : NULL,
735 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
736 pool));
738 if (val)
739 /* delete the path, it's now been dumped. */
740 apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
742 *file_baton = NULL; /* muhahahaha */
743 return SVN_NO_ERROR;
747 static svn_error_t *
748 open_file(const char *path,
749 void *parent_baton,
750 svn_revnum_t ancestor_revision,
751 apr_pool_t *pool,
752 void **file_baton)
754 struct dir_baton *pb = parent_baton;
755 struct edit_baton *eb = pb->edit_baton;
756 const char *cmp_path = NULL;
757 svn_revnum_t cmp_rev = SVN_INVALID_REVNUM;
759 /* If the parent directory has explicit comparison path and rev,
760 record the same for this one. */
761 if (pb && ARE_VALID_COPY_ARGS(pb->cmp_path, pb->cmp_rev))
763 cmp_path = svn_path_join(pb->cmp_path,
764 svn_path_basename(path, pool), pool);
765 cmp_rev = pb->cmp_rev;
768 SVN_ERR(dump_node(eb, path,
769 svn_node_file, svn_node_action_change,
770 FALSE, cmp_path, cmp_rev, pool));
772 *file_baton = NULL; /* muhahahaha again */
773 return SVN_NO_ERROR;
777 static svn_error_t *
778 change_dir_prop(void *parent_baton,
779 const char *name,
780 const svn_string_t *value,
781 apr_pool_t *pool)
783 struct dir_baton *db = parent_baton;
784 struct edit_baton *eb = db->edit_baton;
786 /* This function is what distinguishes between a directory that is
787 opened to merely get somewhere, vs. one that is opened because it
788 *actually* changed by itself. */
789 if (! db->written_out)
791 SVN_ERR(dump_node(eb, db->path,
792 svn_node_dir, svn_node_action_change,
793 FALSE, db->cmp_path, db->cmp_rev, pool));
794 db->written_out = TRUE;
796 return SVN_NO_ERROR;
801 static svn_error_t *
802 get_dump_editor(const svn_delta_editor_t **editor,
803 void **edit_baton,
804 svn_fs_t *fs,
805 svn_revnum_t to_rev,
806 const char *root_path,
807 svn_stream_t *stream,
808 svn_stream_t *feedback_stream,
809 svn_revnum_t oldest_dumped_rev,
810 svn_boolean_t use_deltas,
811 apr_pool_t *pool)
813 /* Allocate an edit baton to be stored in every directory baton.
814 Set it up for the directory baton we create here, which is the
815 root baton. */
816 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
817 svn_delta_editor_t *dump_editor = svn_delta_default_editor(pool);
819 /* Set up the edit baton. */
820 eb->stream = stream;
821 eb->feedback_stream = feedback_stream;
822 eb->oldest_dumped_rev = oldest_dumped_rev;
823 eb->bufsize = sizeof(eb->buffer);
824 eb->path = apr_pstrdup(pool, root_path);
825 SVN_ERR(svn_fs_revision_root(&(eb->fs_root), fs, to_rev, pool));
826 eb->current_rev = to_rev;
827 eb->use_deltas = use_deltas;
829 /* Set up the editor. */
830 dump_editor->open_root = open_root;
831 dump_editor->delete_entry = delete_entry;
832 dump_editor->add_directory = add_directory;
833 dump_editor->open_directory = open_directory;
834 dump_editor->close_directory = close_directory;
835 dump_editor->change_dir_prop = change_dir_prop;
836 dump_editor->add_file = add_file;
837 dump_editor->open_file = open_file;
839 *edit_baton = eb;
840 *editor = dump_editor;
842 return SVN_NO_ERROR;
845 /*----------------------------------------------------------------------*/
847 /** The main dumping routine, svn_repos_dump_fs. **/
850 /* Helper for svn_repos_dump_fs.
852 Write a revision record of REV in FS to writable STREAM, using POOL.
854 static svn_error_t *
855 write_revision_record(svn_stream_t *stream,
856 svn_fs_t *fs,
857 svn_revnum_t rev,
858 apr_pool_t *pool)
860 apr_size_t len;
861 apr_hash_t *props;
862 svn_stringbuf_t *encoded_prophash;
863 apr_time_t timetemp;
864 svn_string_t *datevalue;
866 /* Read the revision props even if we're aren't going to dump
867 them for verification purposes */
868 SVN_ERR(svn_fs_revision_proplist(&props, fs, rev, pool));
870 /* Run revision date properties through the time conversion to
871 canonicalize them. */
872 /* ### Remove this when it is no longer needed for sure. */
873 datevalue = apr_hash_get(props, SVN_PROP_REVISION_DATE,
874 APR_HASH_KEY_STRING);
875 if (datevalue)
877 SVN_ERR(svn_time_from_cstring(&timetemp, datevalue->data, pool));
878 datevalue = svn_string_create(svn_time_to_cstring(timetemp, pool),
879 pool);
880 apr_hash_set(props, SVN_PROP_REVISION_DATE, APR_HASH_KEY_STRING,
881 datevalue);
884 write_hash_to_stringbuf(props, NULL, &encoded_prophash, pool);
886 /* ### someday write a revision-content-checksum */
888 SVN_ERR(svn_stream_printf(stream, pool,
889 SVN_REPOS_DUMPFILE_REVISION_NUMBER
890 ": %ld\n", rev));
891 SVN_ERR(svn_stream_printf(stream, pool,
892 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
893 ": %" APR_SIZE_T_FMT "\n",
894 encoded_prophash->len));
896 /* Write out a regular Content-length header for the benefit of
897 non-Subversion RFC-822 parsers. */
898 SVN_ERR(svn_stream_printf(stream, pool,
899 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
900 ": %" APR_SIZE_T_FMT "\n\n",
901 encoded_prophash->len));
903 len = encoded_prophash->len;
904 SVN_ERR(svn_stream_write(stream, encoded_prophash->data, &len));
906 len = 1;
907 SVN_ERR(svn_stream_write(stream, "\n", &len));
909 return SVN_NO_ERROR;
914 /* The main dumper. */
915 svn_error_t *
916 svn_repos_dump_fs2(svn_repos_t *repos,
917 svn_stream_t *stream,
918 svn_stream_t *feedback_stream,
919 svn_revnum_t start_rev,
920 svn_revnum_t end_rev,
921 svn_boolean_t incremental,
922 svn_boolean_t use_deltas,
923 svn_cancel_func_t cancel_func,
924 void *cancel_baton,
925 apr_pool_t *pool)
927 const svn_delta_editor_t *dump_editor;
928 void *dump_edit_baton;
929 svn_revnum_t i;
930 svn_fs_t *fs = svn_repos_fs(repos);
931 apr_pool_t *subpool = svn_pool_create(pool);
932 svn_revnum_t youngest;
933 const char *uuid;
934 int version;
935 svn_boolean_t dumping = (stream != NULL);
937 /* Determine the current youngest revision of the filesystem. */
938 SVN_ERR(svn_fs_youngest_rev(&youngest, fs, pool));
940 /* Use default vals if necessary. */
941 if (! SVN_IS_VALID_REVNUM(start_rev))
942 start_rev = 0;
943 if (! SVN_IS_VALID_REVNUM(end_rev))
944 end_rev = youngest;
945 if (! stream)
946 stream = svn_stream_empty(pool);
947 if (! feedback_stream)
948 feedback_stream = svn_stream_empty(pool);
950 /* Validate the revisions. */
951 if (start_rev > end_rev)
952 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
953 _("Start revision %ld"
954 " is greater than end revision %ld"),
955 start_rev, end_rev);
956 if (end_rev > youngest)
957 return svn_error_createf(SVN_ERR_REPOS_BAD_ARGS, NULL,
958 _("End revision %ld is invalid "
959 "(youngest revision is %ld)"),
960 end_rev, youngest);
961 if ((start_rev == 0) && incremental)
962 incremental = FALSE; /* revision 0 looks the same regardless of
963 whether or not this is an incremental
964 dump, so just simplify things. */
966 /* Write out the UUID. */
967 SVN_ERR(svn_fs_get_uuid(fs, &uuid, pool));
969 /* If we're not using deltas, use the previous version, for
970 compatibility with svn 1.0.x. */
971 version = SVN_REPOS_DUMPFILE_FORMAT_VERSION;
972 if (!use_deltas)
973 version--;
975 /* Write out "general" metadata for the dumpfile. In this case, a
976 magic header followed by a dumpfile format version. */
977 SVN_ERR(svn_stream_printf(stream, pool,
978 SVN_REPOS_DUMPFILE_MAGIC_HEADER ": %d\n\n",
979 version));
980 SVN_ERR(svn_stream_printf(stream, pool, SVN_REPOS_DUMPFILE_UUID
981 ": %s\n\n", uuid));
983 /* Main loop: we're going to dump revision i. */
984 for (i = start_rev; i <= end_rev; i++)
986 svn_revnum_t from_rev, to_rev;
987 svn_fs_root_t *to_root;
988 svn_boolean_t use_deltas_for_rev;
990 svn_pool_clear(subpool);
992 /* Check for cancellation. */
993 if (cancel_func)
994 SVN_ERR(cancel_func(cancel_baton));
996 /* Special-case the initial revision dump: it needs to contain
997 *all* nodes, because it's the foundation of all future
998 revisions in the dumpfile. */
999 if ((i == start_rev) && (! incremental))
1001 /* Special-special-case a dump of revision 0. */
1002 if (i == 0)
1004 /* Just write out the one revision 0 record and move on.
1005 The parser might want to use its properties. */
1006 SVN_ERR(write_revision_record(stream, fs, 0, subpool));
1007 to_rev = 0;
1008 goto loop_end;
1011 /* Compare START_REV to revision 0, so that everything
1012 appears to be added. */
1013 from_rev = 0;
1014 to_rev = i;
1016 else
1018 /* In the normal case, we want to compare consecutive revs. */
1019 from_rev = i - 1;
1020 to_rev = i;
1023 /* Write the revision record. */
1024 SVN_ERR(write_revision_record(stream, fs, to_rev, subpool));
1026 /* Fetch the editor which dumps nodes to a file. Regardless of
1027 what we've been told, don't use deltas for the first rev of a
1028 non-incremental dump. */
1029 use_deltas_for_rev = use_deltas && (incremental || i != start_rev);
1030 SVN_ERR(get_dump_editor(&dump_editor, &dump_edit_baton, fs, to_rev,
1031 "/", stream, feedback_stream, start_rev,
1032 use_deltas_for_rev, subpool));
1034 /* Drive the editor in one way or another. */
1035 SVN_ERR(svn_fs_revision_root(&to_root, fs, to_rev, subpool));
1037 /* If this is the first revision of a non-incremental dump,
1038 we're in for a full tree dump. Otherwise, we want to simply
1039 replay the revision. */
1040 if ((i == start_rev) && (! incremental))
1042 svn_fs_root_t *from_root;
1043 SVN_ERR(svn_fs_revision_root(&from_root, fs, from_rev, subpool));
1044 SVN_ERR(svn_repos_dir_delta2(from_root, "/", "",
1045 to_root, "/",
1046 dump_editor, dump_edit_baton,
1047 NULL,
1048 NULL,
1049 FALSE, /* don't send text-deltas */
1050 svn_depth_infinity,
1051 FALSE, /* don't send entry props */
1052 FALSE, /* don't ignore ancestry */
1053 subpool));
1055 else
1057 SVN_ERR(svn_repos_replay2(to_root, "", SVN_INVALID_REVNUM, FALSE,
1058 dump_editor, dump_edit_baton,
1059 NULL, NULL, subpool));
1062 loop_end:
1063 SVN_ERR(svn_stream_printf(feedback_stream, pool,
1064 dumping
1065 ? _("* Dumped revision %ld.\n")
1066 : _("* Verified revision %ld.\n"),
1067 to_rev));
1070 svn_pool_destroy(subpool);
1072 return SVN_NO_ERROR;
1075 svn_error_t *
1076 svn_repos_dump_fs(svn_repos_t *repos,
1077 svn_stream_t *stream,
1078 svn_stream_t *feedback_stream,
1079 svn_revnum_t start_rev,
1080 svn_revnum_t end_rev,
1081 svn_boolean_t incremental,
1082 svn_cancel_func_t cancel_func,
1083 void *cancel_baton,
1084 apr_pool_t *pool)
1086 return svn_repos_dump_fs2(repos, stream, feedback_stream, start_rev,
1087 end_rev, incremental, FALSE, cancel_func,
1088 cancel_baton, pool);