2 * dump_editor.c: The svn_delta_editor_t editor used by svnrdump to
5 * ====================================================================
6 * Licensed to the Apache Software Foundation (ASF) under one
7 * or more contributor license agreements. See the NOTICE file
8 * distributed with this work for additional information
9 * regarding copyright ownership. The ASF licenses this file
10 * to you under the Apache License, Version 2.0 (the
11 * "License"); you may not use this file except in compliance
12 * with the License. You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing,
17 * software distributed under the License is distributed on an
18 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
19 * KIND, either express or implied. See the License for the
20 * specific language governing permissions and limitations
22 * ====================================================================
26 #include "svn_pools.h"
27 #include "svn_repos.h"
29 #include "svn_props.h"
30 #include "svn_subst.h"
31 #include "svn_dirent_uri.h"
33 #include "private/svn_subr_private.h"
34 #include "private/svn_dep_compat.h"
35 #include "private/svn_editor.h"
40 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
43 #define LDR_DBG(x) SVN_DBG(x)
45 #define LDR_DBG(x) while(0)
48 /* A directory baton used by all directory-related callback functions
49 * in the dump editor. */
52 struct dump_edit_baton *eb;
53 struct dir_baton *parent_dir_baton;
55 /* Pool for per-directory allocations */
58 /* is this directory a new addition to this revision? */
61 /* has this directory been written to the output stream? */
62 svn_boolean_t written_out;
64 /* the path to this directory */
65 const char *repos_relpath; /* a relpath */
67 /* Copyfrom info for the node, if any. */
68 const char *copyfrom_path; /* a relpath */
69 svn_revnum_t copyfrom_rev;
71 /* Properties which were modified during change_dir_prop. */
74 /* Properties which were deleted during change_dir_prop. */
75 apr_hash_t *deleted_props;
77 /* Hash of paths that need to be deleted, though some -might- be
78 replaced. Maps const char * paths to this dir_baton. Note that
79 they're full paths, because that's what the editor driver gives
80 us, although they're all really within this directory. */
81 apr_hash_t *deleted_entries;
83 /* Flags to trigger dumping props and record termination newlines. */
84 svn_boolean_t dump_props;
85 svn_boolean_t dump_newlines;
88 /* A file baton used by all file-related callback functions in the dump
92 struct dump_edit_baton *eb;
93 struct dir_baton *parent_dir_baton;
95 /* Pool for per-file allocations */
98 /* the path to this file */
99 const char *repos_relpath; /* a relpath */
101 /* Properties which were modified during change_file_prop. */
104 /* Properties which were deleted during change_file_prop. */
105 apr_hash_t *deleted_props;
107 /* The checksum of the file the delta is being applied to */
108 const char *base_checksum;
110 /* Copy state and source information (if any). */
111 svn_boolean_t is_copy;
112 const char *copyfrom_path;
113 svn_revnum_t copyfrom_rev;
115 /* The action associate with this node. */
116 enum svn_node_action action;
118 /* Flags to trigger dumping props and text. */
119 svn_boolean_t dump_text;
120 svn_boolean_t dump_props;
123 /* A handler baton to be used in window_handler(). */
126 svn_txdelta_window_handler_t apply_handler;
130 /* The baton used by the dump editor. */
131 struct dump_edit_baton {
132 /* The output stream we write the dumpfile to */
133 svn_stream_t *stream;
135 /* A backdoor ra session to fetch additional information during the edit. */
136 svn_ra_session_t *ra_session;
138 /* The repository relpath of the anchor of the editor when driven
139 via the RA update mechanism; NULL otherwise. (When the editor is
140 driven via the RA "replay" mechanism instead, the editor is
141 always anchored at the repository, we don't need to prepend an
142 anchor path to the dumped node paths, and open_root() doesn't
143 need to manufacture directory additions.) */
144 const char *update_anchor_relpath;
146 /* Pool for per-revision allocations */
149 /* Temporary file used for textdelta application along with its
150 absolute path; these two variables should be allocated in the
151 per-edit-session pool */
152 const char *delta_abspath;
153 apr_file_t *delta_file;
155 /* The revision we're currently dumping. */
156 svn_revnum_t current_revision;
158 /* The kind (file or directory) and baton of the item whose block of
159 dump stream data has not been fully completed; NULL if there's no
161 svn_node_kind_t pending_kind;
165 /* Make a directory baton to represent the directory at PATH (relative
166 * to the EDIT_BATON).
168 * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
169 * directory should be compared for changes. If the copyfrom
170 * information is valid, the directory will be compared against its
173 * PB is the directory baton of this directory's parent, or NULL if
174 * this is the top-level directory of the edit. ADDED indicates if
175 * this directory is newly added in this revision. Perform all
176 * allocations in POOL. */
177 static struct dir_baton *
178 make_dir_baton(const char *path,
179 const char *copyfrom_path,
180 svn_revnum_t copyfrom_rev,
182 struct dir_baton *pb,
186 struct dump_edit_baton *eb = edit_baton;
187 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
188 const char *repos_relpath;
190 /* Construct the full path of this node. */
192 repos_relpath = svn_relpath_canonicalize(path, pool);
196 /* Strip leading slash from copyfrom_path so that the path is
197 canonical and svn_relpath_join can be used */
199 copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
202 new_db->parent_dir_baton = pb;
204 new_db->repos_relpath = repos_relpath;
205 new_db->copyfrom_path = copyfrom_path
206 ? svn_relpath_canonicalize(copyfrom_path, pool)
208 new_db->copyfrom_rev = copyfrom_rev;
209 new_db->added = added;
210 new_db->written_out = FALSE;
211 new_db->props = apr_hash_make(pool);
212 new_db->deleted_props = apr_hash_make(pool);
213 new_db->deleted_entries = apr_hash_make(pool);
218 /* Make a file baton to represent the directory at PATH (relative to
219 * PB->eb). PB is the directory baton of this directory's parent, or
220 * NULL if this is the top-level directory of the edit. Perform all
221 * allocations in POOL. */
222 static struct file_baton *
223 make_file_baton(const char *path,
224 struct dir_baton *pb,
227 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
230 new_fb->parent_dir_baton = pb;
232 new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
233 new_fb->props = apr_hash_make(pool);
234 new_fb->deleted_props = apr_hash_make(pool);
235 new_fb->is_copy = FALSE;
236 new_fb->copyfrom_path = NULL;
237 new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
238 new_fb->action = svn_node_action_change;
243 /* Return in *HEADER and *CONTENT the headers and content for PROPS. */
245 get_props_content(svn_stringbuf_t **header,
246 svn_stringbuf_t **content,
248 apr_hash_t *deleted_props,
249 apr_pool_t *result_pool,
250 apr_pool_t *scratch_pool)
252 svn_stream_t *content_stream;
253 apr_hash_t *normal_props;
256 *content = svn_stringbuf_create_empty(result_pool);
257 *header = svn_stringbuf_create_empty(result_pool);
259 content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
261 SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool));
262 SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
263 content_stream, "PROPS-END",
265 SVN_ERR(svn_stream_close(content_stream));
267 /* Prop-delta: true */
268 *header = svn_stringbuf_createf(result_pool, SVN_REPOS_DUMPFILE_PROP_DELTA
271 /* Prop-content-length: 193 */
272 buf = apr_psprintf(scratch_pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
273 ": %" APR_SIZE_T_FMT "\n", (*content)->len);
274 svn_stringbuf_appendcstr(*header, buf);
279 /* Extract and dump properties stored in PROPS and property deletions
280 * stored in DELETED_PROPS. If TRIGGER_VAR is not NULL, it is set to
283 * If PROPSTRING is non-NULL, set *PROPSTRING to a string containing
284 * the content block of the property changes; otherwise, dump that to
288 do_dump_props(svn_stringbuf_t **propstring,
289 svn_stream_t *stream,
291 apr_hash_t *deleted_props,
292 svn_boolean_t *trigger_var,
293 apr_pool_t *result_pool,
294 apr_pool_t *scratch_pool)
296 svn_stringbuf_t *header;
297 svn_stringbuf_t *content;
300 if (trigger_var && !*trigger_var)
303 SVN_ERR(get_props_content(&header, &content, props, deleted_props,
304 result_pool, scratch_pool));
306 SVN_ERR(svn_stream_write(stream, header->data, &len));
310 *propstring = content;
314 /* Content-length: 14 */
315 SVN_ERR(svn_stream_printf(stream, scratch_pool,
316 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
317 ": %" APR_SIZE_T_FMT "\n\n",
321 SVN_ERR(svn_stream_write(stream, content->data, &len));
323 /* No text is going to be dumped. Write a couple of newlines and
324 wait for the next node/ revision. */
325 SVN_ERR(svn_stream_puts(stream, "\n\n"));
327 /* Cleanup so that data is never dumped twice. */
328 apr_hash_clear(props);
329 apr_hash_clear(deleted_props);
331 *trigger_var = FALSE;
338 do_dump_newlines(struct dump_edit_baton *eb,
339 svn_boolean_t *trigger_var,
342 if (trigger_var && *trigger_var)
344 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
345 *trigger_var = FALSE;
351 * Write out a node record for PATH of type KIND under EB->FS_ROOT.
352 * ACTION describes what is happening to the node (see enum
353 * svn_node_action). Write record to writable EB->STREAM, using
354 * EB->BUFFER to write in chunks.
356 * If the node was itself copied, IS_COPY is TRUE and the
357 * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
358 * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
359 * node is part of a copied subtree.
362 dump_node(struct dump_edit_baton *eb,
363 const char *repos_relpath,
364 struct dir_baton *db,
365 struct file_baton *fb,
366 enum svn_node_action action,
367 svn_boolean_t is_copy,
368 const char *copyfrom_path,
369 svn_revnum_t copyfrom_rev,
372 const char *node_relpath = repos_relpath;
374 assert(svn_relpath_is_canonical(repos_relpath));
375 assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
376 assert(! (db && fb));
378 /* Add the edit root relpath prefix if necessary. */
379 if (eb->update_anchor_relpath)
380 node_relpath = svn_relpath_join(eb->update_anchor_relpath,
384 SVN_ERR(svn_stream_printf(eb->stream, pool,
385 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
388 /* Node-kind: "file" | "dir" */
390 SVN_ERR(svn_stream_printf(eb->stream, pool,
391 SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
393 SVN_ERR(svn_stream_printf(eb->stream, pool,
394 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
397 /* Write the appropriate Node-action header */
400 case svn_node_action_change:
401 /* We are here after a change_file_prop or change_dir_prop. They
402 set up whatever dump_props they needed to- nothing to
403 do here but print node action information.
405 Node-action: change. */
406 SVN_ERR(svn_stream_puts(eb->stream,
407 SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
410 case svn_node_action_replace:
413 /* Delete the original, and then re-add the replacement as a
414 copy using recursive calls into this function. */
415 SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_delete,
416 FALSE, NULL, SVN_INVALID_REVNUM, pool));
417 SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_add,
418 is_copy, copyfrom_path, copyfrom_rev, pool));
422 /* Node-action: replace */
423 SVN_ERR(svn_stream_puts(eb->stream,
424 SVN_REPOS_DUMPFILE_NODE_ACTION
427 /* Wait for a change_*_prop to be called before dumping
430 fb->dump_props = TRUE;
432 db->dump_props = TRUE;
436 case svn_node_action_delete:
437 /* Node-action: delete */
438 SVN_ERR(svn_stream_puts(eb->stream,
439 SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
441 /* We can leave this routine quietly now. Nothing more to do-
442 print a couple of newlines because we're not dumping props or
444 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
448 case svn_node_action_add:
449 /* Node-action: add */
450 SVN_ERR(svn_stream_puts(eb->stream,
451 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
455 /* Node-copyfrom-rev / Node-copyfrom-path */
456 SVN_ERR(svn_stream_printf(eb->stream, pool,
457 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
459 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
461 copyfrom_rev, copyfrom_path));
463 /* Ugly hack: If a directory was copied from a previous
464 revision, nothing like close_file() will be called to write two
465 blank lines. If change_dir_prop() is called, props are dumped
466 (along with the necessary PROPS-END\n\n and we're good. So
467 set DUMP_NEWLINES here to print the newlines unless
468 change_dir_prop() is called next otherwise the `svnadmin load`
471 db->dump_newlines = TRUE;
475 /* fb->dump_props (for files) is handled in close_file()
476 which is called immediately.
478 However, directories are not closed until all the work
479 inside them has been done; db->dump_props (for directories)
480 is handled (via dump_pending()) in all the functions that
481 can possibly be called after add_directory():
490 change_dir_prop() is a special case. */
492 fb->dump_props = TRUE;
494 db->dump_props = TRUE;
503 dump_mkdir(struct dump_edit_baton *eb,
504 const char *repos_relpath,
507 svn_stringbuf_t *prop_header, *prop_content;
512 SVN_ERR(svn_stream_printf(eb->stream, pool,
513 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
517 SVN_ERR(svn_stream_printf(eb->stream, pool,
518 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
520 /* Node-action: add */
521 SVN_ERR(svn_stream_puts(eb->stream,
522 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
524 /* Dump the (empty) property block. */
525 SVN_ERR(get_props_content(&prop_header, &prop_content,
526 apr_hash_make(pool), apr_hash_make(pool),
528 len = prop_header->len;
529 SVN_ERR(svn_stream_write(eb->stream, prop_header->data, &len));
530 len = prop_content->len;
531 buf = apr_psprintf(pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH
532 ": %" APR_SIZE_T_FMT "\n", len);
533 SVN_ERR(svn_stream_puts(eb->stream, buf));
534 SVN_ERR(svn_stream_puts(eb->stream, "\n"));
535 SVN_ERR(svn_stream_write(eb->stream, prop_content->data, &len));
537 /* Newlines to tie it all off. */
538 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
543 /* Dump pending items from the specified node, to allow starting the dump
546 dump_pending(struct dump_edit_baton *eb,
547 apr_pool_t *scratch_pool)
549 if (! eb->pending_baton)
552 if (eb->pending_kind == svn_node_dir)
554 struct dir_baton *db = eb->pending_baton;
556 /* Some pending properties to dump? */
557 SVN_ERR(do_dump_props(NULL, eb->stream, db->props, db->deleted_props,
558 &(db->dump_props), db->pool, scratch_pool));
560 /* Some pending newlines to dump? */
561 SVN_ERR(do_dump_newlines(eb, &(db->dump_newlines), scratch_pool));
563 else if (eb->pending_kind == svn_node_file)
565 struct file_baton *fb = eb->pending_baton;
567 /* Some pending properties to dump? */
568 SVN_ERR(do_dump_props(NULL, eb->stream, fb->props, fb->deleted_props,
569 &(fb->dump_props), fb->pool, scratch_pool));
574 /* Anything that was pending is pending no longer. */
575 eb->pending_baton = NULL;
576 eb->pending_kind = svn_node_none;
583 /*** Editor Function Implementations ***/
586 open_root(void *edit_baton,
587 svn_revnum_t base_revision,
591 struct dump_edit_baton *eb = edit_baton;
592 struct dir_baton *new_db = NULL;
594 /* Clear the per-revision pool after each revision */
595 svn_pool_clear(eb->pool);
597 LDR_DBG(("open_root %p\n", *root_baton));
599 if (eb->update_anchor_relpath)
602 const char *parent_path = eb->update_anchor_relpath;
603 apr_array_header_t *dirs_to_add =
604 apr_array_make(pool, 4, sizeof(const char *));
605 apr_pool_t *iterpool = svn_pool_create(pool);
607 while (! svn_path_is_empty(parent_path))
609 APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
610 parent_path = svn_relpath_dirname(parent_path, pool);
613 for (i = dirs_to_add->nelts; i; --i)
615 const char *dir_to_add =
616 APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
618 svn_pool_clear(iterpool);
620 /* For parents of the source directory, we just manufacture
621 the adds ourselves. */
624 SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
628 /* ... but for the source directory itself, we'll defer
629 to letting the typical plumbing handle this task. */
630 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
631 edit_baton, NULL, TRUE, pool);
632 SVN_ERR(dump_node(eb, new_db->repos_relpath, new_db,
633 NULL, svn_node_action_add, FALSE,
634 NULL, SVN_INVALID_REVNUM, pool));
636 /* Remember that we've started but not yet finished
637 handling this directory. */
638 new_db->written_out = TRUE;
639 eb->pending_baton = new_db;
640 eb->pending_kind = svn_node_dir;
643 svn_pool_destroy(iterpool);
648 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
649 edit_baton, NULL, FALSE, pool);
652 *root_baton = new_db;
657 delete_entry(const char *path,
658 svn_revnum_t revision,
662 struct dir_baton *pb = parent_baton;
664 LDR_DBG(("delete_entry %s\n", path));
666 SVN_ERR(dump_pending(pb->eb, pool));
668 /* We don't dump this deletion immediate. Rather, we add this path
669 to the deleted_entries of the parent directory baton. That way,
670 we can tell (later) an addition from a replacement. All the real
671 deletions get handled in close_directory(). */
672 svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), pb);
678 add_directory(const char *path,
680 const char *copyfrom_path,
681 svn_revnum_t copyfrom_rev,
685 struct dir_baton *pb = parent_baton;
687 struct dir_baton *new_db;
688 svn_boolean_t is_copy;
690 LDR_DBG(("add_directory %s\n", path));
692 SVN_ERR(dump_pending(pb->eb, pool));
694 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
695 pb, TRUE, pb->eb->pool);
697 /* This might be a replacement -- is the path already deleted? */
698 val = svn_hash_gets(pb->deleted_entries, path);
700 /* Detect an add-with-history */
701 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
704 SVN_ERR(dump_node(pb->eb, new_db->repos_relpath, new_db, NULL,
705 val ? svn_node_action_replace : svn_node_action_add,
707 is_copy ? new_db->copyfrom_path : NULL,
708 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
712 /* Delete the path, it's now been dumped */
713 svn_hash_sets(pb->deleted_entries, path, NULL);
715 /* Remember that we've started, but not yet finished handling this
717 new_db->written_out = TRUE;
718 pb->eb->pending_baton = new_db;
719 pb->eb->pending_kind = svn_node_dir;
721 *child_baton = new_db;
726 open_directory(const char *path,
728 svn_revnum_t base_revision,
732 struct dir_baton *pb = parent_baton;
733 struct dir_baton *new_db;
734 const char *copyfrom_path = NULL;
735 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
737 LDR_DBG(("open_directory %s\n", path));
739 SVN_ERR(dump_pending(pb->eb, pool));
741 /* If the parent directory has explicit comparison path and rev,
742 record the same for this one. */
743 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
745 copyfrom_path = svn_relpath_join(pb->copyfrom_path,
746 svn_relpath_basename(path, NULL),
748 copyfrom_rev = pb->copyfrom_rev;
751 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
752 FALSE, pb->eb->pool);
754 *child_baton = new_db;
759 close_directory(void *dir_baton,
762 struct dir_baton *db = dir_baton;
763 apr_hash_index_t *hi;
764 svn_boolean_t this_pending;
766 LDR_DBG(("close_directory %p\n", dir_baton));
768 /* Remember if this directory is the one currently pending. */
769 this_pending = (db->eb->pending_baton == db);
771 SVN_ERR(dump_pending(db->eb, pool));
773 /* If this directory was pending, then dump_pending() should have
774 taken care of all the props and such. Of course, the only way
775 that would be the case is if this directory was added/replaced.
777 Otherwise, if stuff for this directory has already been written
778 out (at some point in the past, prior to our handling other
779 nodes), we might need to generate a second "change" record just
780 to carry the information we've since learned about the
782 if ((! this_pending) && (db->dump_props))
784 SVN_ERR(dump_node(db->eb, db->repos_relpath, db, NULL,
785 svn_node_action_change, FALSE,
786 NULL, SVN_INVALID_REVNUM, pool));
787 db->eb->pending_baton = db;
788 db->eb->pending_kind = svn_node_dir;
789 SVN_ERR(dump_pending(db->eb, pool));
792 /* Dump the deleted directory entries */
793 for (hi = apr_hash_first(pool, db->deleted_entries); hi;
794 hi = apr_hash_next(hi))
796 const char *path = svn__apr_hash_index_key(hi);
798 SVN_ERR(dump_node(db->eb, path, NULL, NULL, svn_node_action_delete,
799 FALSE, NULL, SVN_INVALID_REVNUM, pool));
802 /* ### should be unnecessary */
803 apr_hash_clear(db->deleted_entries);
809 add_file(const char *path,
811 const char *copyfrom_path,
812 svn_revnum_t copyfrom_rev,
816 struct dir_baton *pb = parent_baton;
817 struct file_baton *fb;
820 LDR_DBG(("add_file %s\n", path));
822 SVN_ERR(dump_pending(pb->eb, pool));
824 /* Make the file baton. */
825 fb = make_file_baton(path, pb, pool);
827 /* This might be a replacement -- is the path already deleted? */
828 val = svn_hash_gets(pb->deleted_entries, path);
830 /* Detect add-with-history. */
831 if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
833 fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
834 fb->copyfrom_rev = copyfrom_rev;
837 fb->action = val ? svn_node_action_replace : svn_node_action_add;
839 /* Delete the path, it's now been dumped. */
841 svn_hash_sets(pb->deleted_entries, path, NULL);
848 open_file(const char *path,
850 svn_revnum_t ancestor_revision,
854 struct dir_baton *pb = parent_baton;
855 struct file_baton *fb;
857 LDR_DBG(("open_file %s\n", path));
859 SVN_ERR(dump_pending(pb->eb, pool));
861 /* Make the file baton. */
862 fb = make_file_baton(path, pb, pool);
864 /* If the parent directory has explicit copyfrom path and rev,
865 record the same for this one. */
866 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
868 fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
869 svn_relpath_basename(path, NULL),
871 fb->copyfrom_rev = pb->copyfrom_rev;
879 change_dir_prop(void *parent_baton,
881 const svn_string_t *value,
884 struct dir_baton *db = parent_baton;
885 svn_boolean_t this_pending;
887 LDR_DBG(("change_dir_prop %p\n", parent_baton));
889 /* This directory is not pending, but something else is, so handle
890 the "something else". */
891 this_pending = (db->eb->pending_baton == db);
893 SVN_ERR(dump_pending(db->eb, pool));
895 if (svn_property_kind2(name) != svn_prop_regular_kind)
899 svn_hash_sets(db->props,
900 apr_pstrdup(db->pool, name),
901 svn_string_dup(value, db->pool));
903 svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
905 /* Make sure we eventually output the props, and disable printing
906 a couple of extra newlines */
907 db->dump_newlines = FALSE;
908 db->dump_props = TRUE;
914 change_file_prop(void *file_baton,
916 const svn_string_t *value,
919 struct file_baton *fb = file_baton;
921 LDR_DBG(("change_file_prop %p\n", file_baton));
923 if (svn_property_kind2(name) != svn_prop_regular_kind)
927 svn_hash_sets(fb->props,
928 apr_pstrdup(fb->pool, name),
929 svn_string_dup(value, fb->pool));
931 svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
933 /* Dump the property headers and wait; close_file might need
934 to write text headers too depending on whether
935 apply_textdelta is called */
936 fb->dump_props = TRUE;
942 window_handler(svn_txdelta_window_t *window, void *baton)
944 struct handler_baton *hb = baton;
945 static svn_error_t *err;
947 err = hb->apply_handler(window, hb->apply_baton);
948 if (window != NULL && !err)
958 apply_textdelta(void *file_baton, const char *base_checksum,
960 svn_txdelta_window_handler_t *handler,
961 void **handler_baton)
963 struct file_baton *fb = file_baton;
964 struct dump_edit_baton *eb = fb->eb;
965 struct handler_baton *hb;
966 svn_stream_t *delta_filestream;
968 LDR_DBG(("apply_textdelta %p\n", file_baton));
970 /* This is custom handler_baton, allocated from a separate pool. */
971 hb = apr_pcalloc(eb->pool, sizeof(*hb));
973 /* Use a temporary file to measure the Text-content-length */
974 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
976 /* Prepare to write the delta to the delta_filestream */
977 svn_txdelta_to_svndiff3(&(hb->apply_handler), &(hb->apply_baton),
979 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
981 /* Record that there's text to be dumped, and its base checksum. */
982 fb->dump_text = TRUE;
983 fb->base_checksum = apr_pstrdup(eb->pool, base_checksum);
985 /* The actual writing takes place when this function has
986 finished. Set handler and handler_baton now so for
988 *handler = window_handler;
995 close_file(void *file_baton,
996 const char *text_checksum,
999 struct file_baton *fb = file_baton;
1000 struct dump_edit_baton *eb = fb->eb;
1001 apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
1002 svn_stringbuf_t *propstring;
1004 LDR_DBG(("close_file %p\n", file_baton));
1006 SVN_ERR(dump_pending(eb, pool));
1008 /* Dump the node. */
1009 SVN_ERR(dump_node(eb, fb->repos_relpath, NULL, fb,
1010 fb->action, fb->is_copy, fb->copyfrom_path,
1011 fb->copyfrom_rev, pool));
1013 /* Some pending properties to dump? We'll dump just the headers for
1014 now, then dump the actual propchange content only after dumping
1015 the text headers too (if present). */
1016 SVN_ERR(do_dump_props(&propstring, eb->stream, fb->props, fb->deleted_props,
1017 &(fb->dump_props), pool, pool));
1019 /* Dump the text headers */
1024 /* Text-delta: true */
1025 SVN_ERR(svn_stream_puts(eb->stream,
1026 SVN_REPOS_DUMPFILE_TEXT_DELTA
1029 err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file);
1031 SVN_ERR(svn_error_wrap_apr(err, NULL));
1033 if (fb->base_checksum)
1034 /* Text-delta-base-md5: */
1035 SVN_ERR(svn_stream_printf(eb->stream, pool,
1036 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
1038 fb->base_checksum));
1040 /* Text-content-length: 39 */
1041 SVN_ERR(svn_stream_printf(eb->stream, pool,
1042 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
1044 (unsigned long)info->size));
1046 /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
1047 SVN_ERR(svn_stream_printf(eb->stream, pool,
1048 SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
1053 /* Content-length: 1549 */
1054 /* If both text and props are absent, skip this header */
1056 SVN_ERR(svn_stream_printf(eb->stream, pool,
1057 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1059 (unsigned long)info->size + propstring->len));
1060 else if (fb->dump_text)
1061 SVN_ERR(svn_stream_printf(eb->stream, pool,
1062 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1064 (unsigned long)info->size));
1066 /* Dump the props now */
1069 SVN_ERR(svn_stream_write(eb->stream, propstring->data,
1070 &(propstring->len)));
1073 fb->dump_props = FALSE;
1074 apr_hash_clear(fb->props);
1075 apr_hash_clear(fb->deleted_props);
1081 /* Seek to the beginning of the delta file, map it to a stream,
1082 and copy the stream to eb->stream. Then close the stream and
1083 truncate the file so we can reuse it for the next textdelta
1084 application. Note that the file isn't created, opened or
1086 svn_stream_t *delta_filestream;
1087 apr_off_t offset = 0;
1089 SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
1090 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
1091 SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
1094 SVN_ERR(svn_stream_close(delta_filestream));
1095 SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
1098 /* Write a couple of blank lines for matching output with `svnadmin
1100 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
1102 return SVN_NO_ERROR;
1105 static svn_error_t *
1106 close_edit(void *edit_baton, apr_pool_t *pool)
1108 return SVN_NO_ERROR;
1111 static svn_error_t *
1112 fetch_base_func(const char **filename,
1115 svn_revnum_t base_revision,
1116 apr_pool_t *result_pool,
1117 apr_pool_t *scratch_pool)
1119 struct dump_edit_baton *eb = baton;
1120 svn_stream_t *fstream;
1126 if (! SVN_IS_VALID_REVNUM(base_revision))
1127 base_revision = eb->current_revision - 1;
1129 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1130 svn_io_file_del_on_pool_cleanup,
1131 result_pool, scratch_pool));
1133 err = svn_ra_get_file(eb->ra_session, path, base_revision,
1134 fstream, NULL, NULL, scratch_pool);
1135 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1137 svn_error_clear(err);
1138 SVN_ERR(svn_stream_close(fstream));
1141 return SVN_NO_ERROR;
1144 return svn_error_trace(err);
1146 SVN_ERR(svn_stream_close(fstream));
1148 return SVN_NO_ERROR;
1151 static svn_error_t *
1152 fetch_props_func(apr_hash_t **props,
1155 svn_revnum_t base_revision,
1156 apr_pool_t *result_pool,
1157 apr_pool_t *scratch_pool)
1159 struct dump_edit_baton *eb = baton;
1160 svn_node_kind_t node_kind;
1165 if (! SVN_IS_VALID_REVNUM(base_revision))
1166 base_revision = eb->current_revision - 1;
1168 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1171 if (node_kind == svn_node_file)
1173 SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1174 NULL, NULL, props, result_pool));
1176 else if (node_kind == svn_node_dir)
1178 apr_array_header_t *tmp_props;
1180 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1181 base_revision, 0 /* Dirent fields */,
1183 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1184 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1186 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1190 *props = apr_hash_make(result_pool);
1193 return SVN_NO_ERROR;
1196 static svn_error_t *
1197 fetch_kind_func(svn_node_kind_t *kind,
1200 svn_revnum_t base_revision,
1201 apr_pool_t *scratch_pool)
1203 struct dump_edit_baton *eb = baton;
1208 if (! SVN_IS_VALID_REVNUM(base_revision))
1209 base_revision = eb->current_revision - 1;
1211 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1214 return SVN_NO_ERROR;
1218 svn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
1220 svn_revnum_t revision,
1221 svn_stream_t *stream,
1222 svn_ra_session_t *ra_session,
1223 const char *update_anchor_relpath,
1224 svn_cancel_func_t cancel_func,
1228 struct dump_edit_baton *eb;
1229 svn_delta_editor_t *de;
1230 svn_delta_shim_callbacks_t *shim_callbacks =
1231 svn_delta_shim_callbacks_default(pool);
1233 eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
1234 eb->stream = stream;
1235 eb->ra_session = ra_session;
1236 eb->update_anchor_relpath = update_anchor_relpath;
1237 eb->current_revision = revision;
1238 eb->pending_kind = svn_node_none;
1240 /* Create a special per-revision pool */
1241 eb->pool = svn_pool_create(pool);
1243 /* Open a unique temporary file for all textdelta applications in
1244 this edit session. The file is automatically closed and cleaned
1245 up when the edit session is done. */
1246 SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
1247 NULL, svn_io_file_del_on_close, pool, pool));
1249 de = svn_delta_default_editor(pool);
1250 de->open_root = open_root;
1251 de->delete_entry = delete_entry;
1252 de->add_directory = add_directory;
1253 de->open_directory = open_directory;
1254 de->close_directory = close_directory;
1255 de->change_dir_prop = change_dir_prop;
1256 de->change_file_prop = change_file_prop;
1257 de->apply_textdelta = apply_textdelta;
1258 de->add_file = add_file;
1259 de->open_file = open_file;
1260 de->close_file = close_file;
1261 de->close_edit = close_edit;
1263 /* Set the edit_baton and editor. */
1267 /* Wrap this editor in a cancellation editor. */
1268 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1269 de, eb, editor, edit_baton, pool));
1271 shim_callbacks->fetch_base_func = fetch_base_func;
1272 shim_callbacks->fetch_props_func = fetch_props_func;
1273 shim_callbacks->fetch_kind_func = fetch_kind_func;
1274 shim_callbacks->fetch_baton = eb;
1276 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1277 NULL, NULL, shim_callbacks, pool, pool));
1279 return SVN_NO_ERROR;