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"
23 #include "svn_repos.h"
24 #include "svn_string.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). **/
43 write_hash_to_stringbuf(apr_hash_t
*hash
,
45 svn_stringbuf_t
**strbuf
,
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))
59 /* Get this key and val. */
60 apr_hash_this(this, &key
, &keylen
, &val
);
63 /* Don't output properties equal to the ones in oldhash, if present. */
66 svn_string_t
*oldvalue
= apr_hash_get(oldhash
, key
, keylen
);
68 if (oldvalue
&& svn_string_compare(value
, oldvalue
))
72 /* Output name length, then name. */
74 svn_stringbuf_appendcstr(*strbuf
,
75 apr_psprintf(pool
, "K %" APR_SSIZE_T_FMT
"\n",
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",
87 svn_stringbuf_appendbytes(*strbuf
, value
->data
, value
->len
);
88 svn_stringbuf_appendbytes(*strbuf
, "\n", 1);
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))
101 apr_hash_this(this, &key
, &keylen
, NULL
);
103 /* Only output values deleted in hash. */
104 if (apr_hash_get(hash
, key
, keylen
))
107 /* Output name length, then name. */
109 svn_stringbuf_appendcstr(*strbuf
,
111 "D %" APR_SSIZE_T_FMT
"\n",
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
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
)
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
;
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
));
157 SVN_ERR(svn_io_file_seek(*tempfile
, APR_SET
, &offset
, pool
));
162 /*----------------------------------------------------------------------*/
164 /** An editor which dumps node-data in 'dumpfile format' to a file. **/
166 /* Look, mom! No file batons! */
170 /* The path which implicitly prepends all full paths coming into
171 this editor. This will almost always be "" or "/". */
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
];
197 struct edit_baton
*edit_baton
;
198 struct dir_baton
*parent_dir_baton
;
200 /* is this directory a new addition to this revision? */
203 /* has this directory been written to the output stream? */
204 svn_boolean_t written_out
;
206 /* the absolute path to this directory */
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 */
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
,
244 void *parent_dir_baton
,
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. */
257 /* Construct the full path of this node. */
259 full_path
= svn_path_join(eb
->path
, path
, pool
);
261 full_path
= apr_pstrdup(pool
, eb
->path
);
263 /* Remove leading slashes from copyfrom paths. */
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
);
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
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
,
303 svn_stringbuf_t
*propstring
;
304 svn_filesize_t content_length
= 0;
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. */
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
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
),
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
)
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
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
;
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
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
393 /* we can leave this routine quietly now, don't need to dump
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"));
405 /* Dump all contents for a simple 'add'. */
406 if (kind
== svn_node_file
)
407 must_dump_text
= TRUE
;
408 must_dump_props
= TRUE
;
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
424 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
428 SVN_ERR(svn_fs_revision_root(&compare_root
,
429 svn_fs_root_fs(eb
->fs_root
),
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
);
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. */
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. */
474 apr_hash_t
*prophash
, *oldhash
= NULL
;
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
,
484 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
485 SVN_REPOS_DUMPFILE_PROP_DELTA
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
;
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
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
);
521 SVN_ERR(svn_stream_printf(eb
->stream
, pool
,
522 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_CHECKSUM
523 ": %s\n", hex_digest
));
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
);
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",
553 /* Dump property content if we're supposed to do so. */
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
;
566 contents
= svn_stream_from_aprfile(delta_file
, pool
);
568 SVN_ERR(svn_fs_file_contents(&contents
, eb
->fs_root
, path
, pool
));
570 SVN_ERR(svn_stream_copy(contents
, eb
->stream
, pool
));
574 SVN_ERR(svn_stream_write(eb
->stream
, "\n\n", &len
)); /* ### needed? */
581 open_root(void *edit_baton
,
582 svn_revnum_t base_revision
,
586 *root_baton
= make_dir_baton(NULL
, NULL
, SVN_INVALID_REVNUM
,
587 edit_baton
, NULL
, FALSE
, pool
);
593 delete_entry(const char *path
,
594 svn_revnum_t revision
,
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
);
609 add_directory(const char *path
,
611 const char *copyfrom_path
,
612 svn_revnum_t copyfrom_rev
,
616 struct dir_baton
*pb
= parent_baton
;
617 struct edit_baton
*eb
= pb
->edit_baton
;
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
;
630 SVN_ERR(dump_node(eb
, path
,
632 val
? svn_node_action_replace
: svn_node_action_add
,
634 is_copy
? copyfrom_path
: NULL
,
635 is_copy
? copyfrom_rev
: SVN_INVALID_REVNUM
,
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
;
650 open_directory(const char *path
,
652 svn_revnum_t base_revision
,
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
;
678 close_directory(void *dir_baton
,
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
))
692 apr_hash_this(hi
, &key
, NULL
, NULL
);
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
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
);
711 add_file(const char *path
,
713 const char *copyfrom_path
,
714 svn_revnum_t copyfrom_rev
,
718 struct dir_baton
*pb
= parent_baton
;
719 struct edit_baton
*eb
= pb
->edit_baton
;
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
;
730 SVN_ERR(dump_node(eb
, path
,
732 val
? svn_node_action_replace
: svn_node_action_add
,
734 is_copy
? copyfrom_path
: NULL
,
735 is_copy
? copyfrom_rev
: SVN_INVALID_REVNUM
,
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 */
748 open_file(const char *path
,
750 svn_revnum_t ancestor_revision
,
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 */
778 change_dir_prop(void *parent_baton
,
780 const svn_string_t
*value
,
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
;
802 get_dump_editor(const svn_delta_editor_t
**editor
,
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
,
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
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. */
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
;
840 *editor
= dump_editor
;
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.
855 write_revision_record(svn_stream_t
*stream
,
862 svn_stringbuf_t
*encoded_prophash
;
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
);
877 SVN_ERR(svn_time_from_cstring(&timetemp
, datevalue
->data
, pool
));
878 datevalue
= svn_string_create(svn_time_to_cstring(timetemp
, pool
),
880 apr_hash_set(props
, SVN_PROP_REVISION_DATE
, APR_HASH_KEY_STRING
,
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
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
));
907 SVN_ERR(svn_stream_write(stream
, "\n", &len
));
914 /* The main dumper. */
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
,
927 const svn_delta_editor_t
*dump_editor
;
928 void *dump_edit_baton
;
930 svn_fs_t
*fs
= svn_repos_fs(repos
);
931 apr_pool_t
*subpool
= svn_pool_create(pool
);
932 svn_revnum_t youngest
;
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
))
943 if (! SVN_IS_VALID_REVNUM(end_rev
))
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"),
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)"),
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
;
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",
980 SVN_ERR(svn_stream_printf(stream
, pool
, SVN_REPOS_DUMPFILE_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. */
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. */
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
));
1011 /* Compare START_REV to revision 0, so that everything
1012 appears to be added. */
1018 /* In the normal case, we want to compare consecutive revs. */
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
, "/", "",
1046 dump_editor
, dump_edit_baton
,
1049 FALSE
, /* don't send text-deltas */
1051 FALSE
, /* don't send entry props */
1052 FALSE
, /* don't ignore ancestry */
1057 SVN_ERR(svn_repos_replay2(to_root
, "", SVN_INVALID_REVNUM
, FALSE
,
1058 dump_editor
, dump_edit_baton
,
1059 NULL
, NULL
, subpool
));
1063 SVN_ERR(svn_stream_printf(feedback_stream
, pool
,
1065 ? _("* Dumped revision %ld.\n")
1066 : _("* Verified revision %ld.\n"),
1070 svn_pool_destroy(subpool
);
1072 return SVN_NO_ERROR
;
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
,
1086 return svn_repos_dump_fs2(repos
, stream
, feedback_stream
, start_rev
,
1087 end_rev
, incremental
, FALSE
, cancel_func
,
1088 cancel_baton
, pool
);