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_repos_private.h"
34 #include "private/svn_subr_private.h"
35 #include "private/svn_dep_compat.h"
36 #include "private/svn_editor.h"
41 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
44 /* A directory baton used by all directory-related callback functions
45 * in the dump editor. */
48 struct dump_edit_baton *eb;
50 /* Pool for per-directory allocations */
53 /* the path to this directory */
54 const char *repos_relpath; /* a relpath */
56 /* Copyfrom info for the node, if any. */
57 const char *copyfrom_path; /* a relpath */
58 svn_revnum_t copyfrom_rev;
60 /* Headers accumulated so far for this directory */
61 svn_repos__dumpfile_headers_t *headers;
63 /* Properties which were modified during change_dir_prop. */
66 /* Properties which were deleted during change_dir_prop. */
67 apr_hash_t *deleted_props;
69 /* Hash of paths that need to be deleted, though some -might- be
70 replaced. Maps const char * paths to this dir_baton. Note that
71 they're full paths, because that's what the editor driver gives
72 us, although they're all really within this directory. */
73 apr_hash_t *deleted_entries;
75 /* Flag to trigger dumping props. */
76 svn_boolean_t dump_props;
79 /* A file baton used by all file-related callback functions in the dump
83 struct dump_edit_baton *eb;
85 /* Pool for per-file allocations */
88 /* the path to this file */
89 const char *repos_relpath; /* a relpath */
91 /* Properties which were modified during change_file_prop. */
94 /* Properties which were deleted during change_file_prop. */
95 apr_hash_t *deleted_props;
97 /* The checksum of the file the delta is being applied to */
98 const char *base_checksum;
100 /* Copy state and source information (if any). */
101 svn_boolean_t is_copy;
102 const char *copyfrom_path;
103 svn_revnum_t copyfrom_rev;
105 /* The action associate with this node. */
106 enum svn_node_action action;
108 /* Flags to trigger dumping props and text. */
109 svn_boolean_t dump_text;
110 svn_boolean_t dump_props;
113 /* The baton used by the dump editor. */
114 struct dump_edit_baton {
115 /* The output stream we write the dumpfile to */
116 svn_stream_t *stream;
118 /* A backdoor ra session to fetch additional information during the edit. */
119 svn_ra_session_t *ra_session;
121 /* The repository relpath of the anchor of the editor when driven
122 via the RA update mechanism; NULL otherwise. (When the editor is
123 driven via the RA "replay" mechanism instead, the editor is
124 always anchored at the repository, we don't need to prepend an
125 anchor path to the dumped node paths, and open_root() doesn't
126 need to manufacture directory additions.) */
127 const char *update_anchor_relpath;
129 /* Pool for per-revision allocations */
132 /* Temporary file used for textdelta application along with its
133 absolute path; these two variables should be allocated in the
134 per-edit-session pool */
135 const char *delta_abspath;
136 apr_file_t *delta_file;
138 /* The revision we're currently dumping. */
139 svn_revnum_t current_revision;
141 /* The baton of the directory node whose block of
142 dump stream data has not been fully completed; NULL if there's no
144 struct dir_baton *pending_db;
147 /* Make a directory baton to represent the directory at PATH (relative
148 * to the EDIT_BATON).
150 * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
151 * directory should be compared for changes. If the copyfrom
152 * information is valid, the directory will be compared against its
155 * PB is the directory baton of this directory's parent, or NULL if
156 * this is the top-level directory of the edit.
158 * Perform all allocations in POOL. */
159 static struct dir_baton *
160 make_dir_baton(const char *path,
161 const char *copyfrom_path,
162 svn_revnum_t copyfrom_rev,
164 struct dir_baton *pb,
167 struct dump_edit_baton *eb = edit_baton;
168 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
169 const char *repos_relpath;
171 /* Construct the full path of this node. */
173 repos_relpath = svn_relpath_canonicalize(path, pool);
177 /* Strip leading slash from copyfrom_path so that the path is
178 canonical and svn_relpath_join can be used */
180 copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
184 new_db->repos_relpath = repos_relpath;
185 new_db->copyfrom_path = copyfrom_path
186 ? svn_relpath_canonicalize(copyfrom_path, pool)
188 new_db->copyfrom_rev = copyfrom_rev;
189 new_db->headers = NULL;
190 new_db->props = apr_hash_make(pool);
191 new_db->deleted_props = apr_hash_make(pool);
192 new_db->deleted_entries = apr_hash_make(pool);
197 /* Make a file baton to represent the directory at PATH (relative to
198 * PB->eb). PB is the directory baton of this directory's parent, or
199 * NULL if this is the top-level directory of the edit. Perform all
200 * allocations in POOL. */
201 static struct file_baton *
202 make_file_baton(const char *path,
203 struct dir_baton *pb,
206 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
210 new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
211 new_fb->props = apr_hash_make(pool);
212 new_fb->deleted_props = apr_hash_make(pool);
213 new_fb->is_copy = FALSE;
214 new_fb->copyfrom_path = NULL;
215 new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
216 new_fb->action = svn_node_action_change;
221 /* Append to HEADERS the required headers, and set *CONTENT to the property
222 * content section, to represent the property delta of PROPS/DELETED_PROPS.
225 get_props_content(svn_repos__dumpfile_headers_t *headers,
226 svn_stringbuf_t **content,
228 apr_hash_t *deleted_props,
229 apr_pool_t *result_pool,
230 apr_pool_t *scratch_pool)
232 svn_stream_t *content_stream;
233 apr_hash_t *normal_props;
235 *content = svn_stringbuf_create_empty(result_pool);
237 content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
239 SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool));
240 SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
241 content_stream, "PROPS-END",
243 SVN_ERR(svn_stream_close(content_stream));
245 /* Prop-delta: true */
246 svn_repos__dumpfile_header_push(
247 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
252 /* A special case of dump_node(), for a delete record.
254 * The only thing special about this version is it only writes one blank
255 * line, not two, after the headers. Why? Historical precedent for the
256 * case where a delete record is used as part of a (delete + add-with-history)
257 * in implementing a replacement.
260 dump_node_delete(svn_stream_t *stream,
261 const char *node_relpath,
264 svn_repos__dumpfile_headers_t *headers
265 = svn_repos__dumpfile_headers_create(pool);
267 assert(svn_relpath_is_canonical(node_relpath));
270 svn_repos__dumpfile_header_push(
271 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
273 /* Node-action: delete */
274 svn_repos__dumpfile_header_push(
275 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
277 SVN_ERR(svn_repos__dump_node_record(stream, headers,
278 NULL, FALSE, 0, /* props & text */
279 FALSE /*content_length_always*/, pool));
283 /* Set *HEADERS_P to contain some headers for the node at PATH of type KIND.
285 * ACTION describes what is happening to the node (see enum
288 * If the node was itself copied, IS_COPY is TRUE and the
289 * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
290 * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
291 * node is part of a copied subtree.
293 * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a
294 * complete deletion record to the dump stream.
296 * If ACTION is svn_node_action_delete, then the node record will be
297 * complete. (The caller may want to write two blank lines after the
301 dump_node(svn_repos__dumpfile_headers_t **headers_p,
302 struct dump_edit_baton *eb,
303 const char *repos_relpath,
304 struct dir_baton *db,
305 struct file_baton *fb,
306 enum svn_node_action action,
307 svn_boolean_t is_copy,
308 const char *copyfrom_path,
309 svn_revnum_t copyfrom_rev,
312 const char *node_relpath = repos_relpath;
313 svn_repos__dumpfile_headers_t *headers
314 = svn_repos__dumpfile_headers_create(pool);
316 assert(svn_relpath_is_canonical(repos_relpath));
317 assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
318 assert(! (db && fb));
320 /* Add the edit root relpath prefix if necessary. */
321 if (eb->update_anchor_relpath)
322 node_relpath = svn_relpath_join(eb->update_anchor_relpath,
326 svn_repos__dumpfile_header_push(
327 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
329 /* Node-kind: "file" | "dir" */
331 svn_repos__dumpfile_header_push(
332 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
334 svn_repos__dumpfile_header_push(
335 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
338 /* Write the appropriate Node-action header */
341 case svn_node_action_change:
342 /* We are here after a change_file_prop or change_dir_prop. They
343 set up whatever dump_props they needed to- nothing to
344 do here but print node action information.
346 Node-action: change. */
347 svn_repos__dumpfile_header_push(
348 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
351 case svn_node_action_delete:
352 /* Node-action: delete */
353 svn_repos__dumpfile_header_push(
354 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
357 case svn_node_action_replace:
360 /* Node-action: replace */
361 svn_repos__dumpfile_header_push(
362 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
364 /* Wait for a change_*_prop to be called before dumping
367 fb->dump_props = TRUE;
369 db->dump_props = TRUE;
374 /* More complex case: is_copy is true, and copyfrom_path/
375 copyfrom_rev are present: delete the original, and then re-add
377 /* ### Why not write a 'replace' record? Don't know. */
379 /* ### Unusually, we end this 'delete' node record with only a single
380 blank line after the header block -- no extra blank line. */
381 SVN_ERR(dump_node_delete(eb->stream, repos_relpath, pool));
383 /* The remaining action is a non-replacing add-with-history */
384 /* action = svn_node_action_add; */
386 /* FALL THROUGH to 'add' */
388 case svn_node_action_add:
389 /* Node-action: add */
390 svn_repos__dumpfile_header_push(
391 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
395 /* Node-copyfrom-rev / Node-copyfrom-path */
396 svn_repos__dumpfile_header_pushf(
397 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", copyfrom_rev);
398 svn_repos__dumpfile_header_push(
399 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path);
403 /* fb->dump_props (for files) is handled in close_file()
404 which is called immediately.
406 However, directories are not closed until all the work
407 inside them has been done; db->dump_props (for directories)
408 is handled (via dump_pending()) in all the functions that
409 can possibly be called after add_directory():
418 change_dir_prop() is a special case. */
420 fb->dump_props = TRUE;
422 db->dump_props = TRUE;
428 /* Return the headers so far. We don't necessarily have all the headers
429 yet -- there may be property-related and content length headers to
430 come, if this was not a 'delete' record. */
431 *headers_p = headers;
436 dump_mkdir(struct dump_edit_baton *eb,
437 const char *repos_relpath,
440 svn_stringbuf_t *prop_content;
441 svn_repos__dumpfile_headers_t *headers
442 = svn_repos__dumpfile_headers_create(pool);
445 svn_repos__dumpfile_header_push(
446 headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath);
449 svn_repos__dumpfile_header_push(
450 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
452 /* Node-action: add */
453 svn_repos__dumpfile_header_push(
454 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
456 /* Dump the (empty) property block. */
457 SVN_ERR(get_props_content(headers, &prop_content,
458 apr_hash_make(pool), apr_hash_make(pool),
460 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content,
461 FALSE, 0, FALSE /*content_length_always*/,
464 /* Newlines to tie it all off. */
465 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
470 /* Dump pending headers and properties for the directory EB->pending_db (if
471 * not null), to allow starting the dump of a child node */
473 dump_pending_dir(struct dump_edit_baton *eb,
474 apr_pool_t *scratch_pool)
476 struct dir_baton *db = eb->pending_db;
477 svn_stringbuf_t *prop_content = NULL;
482 /* Some pending properties to dump? */
485 SVN_ERR(get_props_content(db->headers, &prop_content,
486 db->props, db->deleted_props,
487 scratch_pool, scratch_pool));
489 SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content,
490 FALSE, 0, FALSE /*content_length_always*/,
493 /* No text is going to be dumped. Write a couple of newlines and
494 wait for the next node/ revision. */
495 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
499 /* Cleanup so that data is never dumped twice. */
500 apr_hash_clear(db->props);
501 apr_hash_clear(db->deleted_props);
502 db->dump_props = FALSE;
505 /* Anything that was pending is pending no longer. */
506 eb->pending_db = NULL;
513 /*** Editor Function Implementations ***/
516 open_root(void *edit_baton,
517 svn_revnum_t base_revision,
521 struct dump_edit_baton *eb = edit_baton;
522 struct dir_baton *new_db = NULL;
524 /* Clear the per-revision pool after each revision */
525 svn_pool_clear(eb->pool);
527 if (eb->update_anchor_relpath)
530 const char *parent_path = eb->update_anchor_relpath;
531 apr_array_header_t *dirs_to_add =
532 apr_array_make(pool, 4, sizeof(const char *));
533 apr_pool_t *iterpool = svn_pool_create(pool);
535 while (! svn_path_is_empty(parent_path))
537 APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
538 parent_path = svn_relpath_dirname(parent_path, pool);
541 for (i = dirs_to_add->nelts; i; --i)
543 const char *dir_to_add =
544 APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
546 svn_pool_clear(iterpool);
548 /* For parents of the source directory, we just manufacture
549 the adds ourselves. */
552 SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
556 /* ... but for the source directory itself, we'll defer
557 to letting the typical plumbing handle this task. */
558 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
559 edit_baton, NULL, pool);
560 SVN_ERR(dump_node(&new_db->headers,
561 eb, new_db->repos_relpath, new_db,
562 NULL, svn_node_action_add, FALSE,
563 NULL, SVN_INVALID_REVNUM, pool));
565 /* Remember that we've started but not yet finished
566 handling this directory. */
567 eb->pending_db = new_db;
570 svn_pool_destroy(iterpool);
575 new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
576 edit_baton, NULL, pool);
579 *root_baton = new_db;
584 delete_entry(const char *path,
585 svn_revnum_t revision,
589 struct dir_baton *pb = parent_baton;
591 SVN_ERR(dump_pending_dir(pb->eb, pool));
593 /* We don't dump this deletion immediate. Rather, we add this path
594 to the deleted_entries of the parent directory baton. That way,
595 we can tell (later) an addition from a replacement. All the real
596 deletions get handled in close_directory(). */
597 svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->pool, path), pb);
603 add_directory(const char *path,
605 const char *copyfrom_path,
606 svn_revnum_t copyfrom_rev,
610 struct dir_baton *pb = parent_baton;
612 struct dir_baton *new_db;
613 svn_boolean_t is_copy;
615 SVN_ERR(dump_pending_dir(pb->eb, pool));
617 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
620 /* This might be a replacement -- is the path already deleted? */
621 was_deleted = svn_hash_gets(pb->deleted_entries, path);
623 /* Detect an add-with-history */
624 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
627 SVN_ERR(dump_node(&new_db->headers,
628 pb->eb, new_db->repos_relpath, new_db, NULL,
629 was_deleted ? svn_node_action_replace : svn_node_action_add,
631 is_copy ? new_db->copyfrom_path : NULL,
632 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
636 /* Delete the path, it's now been dumped */
637 svn_hash_sets(pb->deleted_entries, path, NULL);
639 /* Remember that we've started, but not yet finished handling this
641 pb->eb->pending_db = new_db;
643 *child_baton = new_db;
648 open_directory(const char *path,
650 svn_revnum_t base_revision,
654 struct dir_baton *pb = parent_baton;
655 struct dir_baton *new_db;
656 const char *copyfrom_path = NULL;
657 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
659 SVN_ERR(dump_pending_dir(pb->eb, pool));
661 /* If the parent directory has explicit comparison path and rev,
662 record the same for this one. */
663 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
665 copyfrom_path = svn_relpath_join(pb->copyfrom_path,
666 svn_relpath_basename(path, NULL),
668 copyfrom_rev = pb->copyfrom_rev;
671 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
674 *child_baton = new_db;
679 close_directory(void *dir_baton,
682 struct dir_baton *db = dir_baton;
683 apr_hash_index_t *hi;
684 svn_boolean_t this_pending;
686 /* Remember if this directory is the one currently pending. */
687 this_pending = (db->eb->pending_db == db);
689 SVN_ERR(dump_pending_dir(db->eb, pool));
691 /* If this directory was pending, then dump_pending() should have
692 taken care of all the props and such. Of course, the only way
693 that would be the case is if this directory was added/replaced.
695 Otherwise, if stuff for this directory has already been written
696 out (at some point in the past, prior to our handling other
697 nodes), we might need to generate a second "change" record just
698 to carry the information we've since learned about the
700 if ((! this_pending) && (db->dump_props))
702 SVN_ERR(dump_node(&db->headers,
703 db->eb, db->repos_relpath, db, NULL,
704 svn_node_action_change, FALSE,
705 NULL, SVN_INVALID_REVNUM, pool));
706 db->eb->pending_db = db;
707 SVN_ERR(dump_pending_dir(db->eb, pool));
710 /* Dump the deleted directory entries */
711 for (hi = apr_hash_first(pool, db->deleted_entries); hi;
712 hi = apr_hash_next(hi))
714 const char *path = apr_hash_this_key(hi);
716 SVN_ERR(dump_node_delete(db->eb->stream, path, pool));
717 /* This deletion record is complete -- write an extra newline */
718 SVN_ERR(svn_stream_puts(db->eb->stream, "\n"));
721 /* ### should be unnecessary */
722 apr_hash_clear(db->deleted_entries);
728 add_file(const char *path,
730 const char *copyfrom_path,
731 svn_revnum_t copyfrom_rev,
735 struct dir_baton *pb = parent_baton;
736 struct file_baton *fb;
739 SVN_ERR(dump_pending_dir(pb->eb, pool));
741 /* Make the file baton. */
742 fb = make_file_baton(path, pb, pool);
744 /* This might be a replacement -- is the path already deleted? */
745 was_deleted = svn_hash_gets(pb->deleted_entries, path);
747 /* Detect add-with-history. */
748 if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
750 fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
751 fb->copyfrom_rev = copyfrom_rev;
754 fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add;
756 /* Delete the path, it's now been dumped. */
758 svn_hash_sets(pb->deleted_entries, path, NULL);
765 open_file(const char *path,
767 svn_revnum_t ancestor_revision,
771 struct dir_baton *pb = parent_baton;
772 struct file_baton *fb;
774 SVN_ERR(dump_pending_dir(pb->eb, pool));
776 /* Make the file baton. */
777 fb = make_file_baton(path, pb, pool);
779 /* If the parent directory has explicit copyfrom path and rev,
780 record the same for this one. */
781 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
783 fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
784 svn_relpath_basename(path, NULL),
786 fb->copyfrom_rev = pb->copyfrom_rev;
794 change_dir_prop(void *parent_baton,
796 const svn_string_t *value,
799 struct dir_baton *db = parent_baton;
800 svn_boolean_t this_pending;
802 /* This directory is not pending, but something else is, so handle
803 the "something else". */
804 this_pending = (db->eb->pending_db == db);
806 SVN_ERR(dump_pending_dir(db->eb, pool));
808 if (svn_property_kind2(name) != svn_prop_regular_kind)
812 svn_hash_sets(db->props,
813 apr_pstrdup(db->pool, name),
814 svn_string_dup(value, db->pool));
816 svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
818 /* Make sure we eventually output the props */
819 db->dump_props = TRUE;
825 change_file_prop(void *file_baton,
827 const svn_string_t *value,
830 struct file_baton *fb = file_baton;
832 if (svn_property_kind2(name) != svn_prop_regular_kind)
836 svn_hash_sets(fb->props,
837 apr_pstrdup(fb->pool, name),
838 svn_string_dup(value, fb->pool));
840 svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
842 /* Dump the property headers and wait; close_file might need
843 to write text headers too depending on whether
844 apply_textdelta is called */
845 fb->dump_props = TRUE;
851 apply_textdelta(void *file_baton, const char *base_checksum,
853 svn_txdelta_window_handler_t *handler,
854 void **handler_baton)
856 struct file_baton *fb = file_baton;
857 struct dump_edit_baton *eb = fb->eb;
858 svn_stream_t *delta_filestream;
860 /* Use a temporary file to measure the Text-content-length */
861 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
863 /* Prepare to write the delta to the delta_filestream */
864 svn_txdelta_to_svndiff3(handler, handler_baton,
866 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
868 /* Record that there's text to be dumped, and its base checksum. */
869 fb->dump_text = TRUE;
870 fb->base_checksum = apr_pstrdup(fb->pool, base_checksum);
876 close_file(void *file_baton,
877 const char *text_checksum,
880 struct file_baton *fb = file_baton;
881 struct dump_edit_baton *eb = fb->eb;
882 svn_filesize_t text_content_length = 0;
883 svn_stringbuf_t *propstring = NULL;
884 svn_repos__dumpfile_headers_t *headers;
886 SVN_ERR(dump_pending_dir(eb, pool));
888 /* Start dumping this node, by collecting some basic headers for it. */
889 SVN_ERR(dump_node(&headers, eb, fb->repos_relpath, NULL, fb,
890 fb->action, fb->is_copy, fb->copyfrom_path,
891 fb->copyfrom_rev, pool));
893 /* Some pending properties to dump? We'll dump just the headers for
894 now, then dump the actual propchange content only after dumping
895 the text headers too (if present). */
898 SVN_ERR(get_props_content(headers, &propstring,
899 fb->props, fb->deleted_props,
903 /* Dump the text headers */
906 /* Text-delta: true */
907 svn_repos__dumpfile_header_push(
908 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
910 SVN_ERR(svn_io_file_size_get(&text_content_length, eb->delta_file,
913 if (fb->base_checksum)
914 /* Text-delta-base-md5: */
915 svn_repos__dumpfile_header_push(
916 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, fb->base_checksum);
918 /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
919 svn_repos__dumpfile_header_push(
920 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum);
923 /* Dump the headers and props now */
924 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, propstring,
925 fb->dump_text, text_content_length,
926 FALSE /*content_length_always*/,
932 fb->dump_props = FALSE;
933 apr_hash_clear(fb->props);
934 apr_hash_clear(fb->deleted_props);
940 /* Seek to the beginning of the delta file, map it to a stream,
941 and copy the stream to eb->stream. Then close the stream and
942 truncate the file so we can reuse it for the next textdelta
943 application. Note that the file isn't created, opened or
945 svn_stream_t *delta_filestream;
946 apr_off_t offset = 0;
948 SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
949 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
950 SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
953 SVN_ERR(svn_stream_close(delta_filestream));
954 SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
957 /* Write a couple of blank lines for matching output with `svnadmin
959 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
965 close_edit(void *edit_baton, apr_pool_t *pool)
971 fetch_base_func(const char **filename,
974 svn_revnum_t base_revision,
975 apr_pool_t *result_pool,
976 apr_pool_t *scratch_pool)
978 struct dump_edit_baton *eb = baton;
979 svn_stream_t *fstream;
985 if (! SVN_IS_VALID_REVNUM(base_revision))
986 base_revision = eb->current_revision - 1;
988 SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
989 svn_io_file_del_on_pool_cleanup,
990 result_pool, scratch_pool));
992 err = svn_ra_get_file(eb->ra_session, path, base_revision,
993 fstream, NULL, NULL, scratch_pool);
994 if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
996 svn_error_clear(err);
997 SVN_ERR(svn_stream_close(fstream));
1000 return SVN_NO_ERROR;
1003 return svn_error_trace(err);
1005 SVN_ERR(svn_stream_close(fstream));
1007 return SVN_NO_ERROR;
1010 static svn_error_t *
1011 fetch_props_func(apr_hash_t **props,
1014 svn_revnum_t base_revision,
1015 apr_pool_t *result_pool,
1016 apr_pool_t *scratch_pool)
1018 struct dump_edit_baton *eb = baton;
1019 svn_node_kind_t node_kind;
1024 if (! SVN_IS_VALID_REVNUM(base_revision))
1025 base_revision = eb->current_revision - 1;
1027 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1030 if (node_kind == svn_node_file)
1032 SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1033 NULL, NULL, props, result_pool));
1035 else if (node_kind == svn_node_dir)
1037 apr_array_header_t *tmp_props;
1039 SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1040 base_revision, 0 /* Dirent fields */,
1042 tmp_props = svn_prop_hash_to_array(*props, result_pool);
1043 SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1045 *props = svn_prop_array_to_hash(tmp_props, result_pool);
1049 *props = apr_hash_make(result_pool);
1052 return SVN_NO_ERROR;
1055 static svn_error_t *
1056 fetch_kind_func(svn_node_kind_t *kind,
1059 svn_revnum_t base_revision,
1060 apr_pool_t *scratch_pool)
1062 struct dump_edit_baton *eb = baton;
1067 if (! SVN_IS_VALID_REVNUM(base_revision))
1068 base_revision = eb->current_revision - 1;
1070 SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1073 return SVN_NO_ERROR;
1077 svn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
1079 svn_revnum_t revision,
1080 svn_stream_t *stream,
1081 svn_ra_session_t *ra_session,
1082 const char *update_anchor_relpath,
1083 svn_cancel_func_t cancel_func,
1087 struct dump_edit_baton *eb;
1088 svn_delta_editor_t *de;
1089 svn_delta_shim_callbacks_t *shim_callbacks =
1090 svn_delta_shim_callbacks_default(pool);
1092 eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
1093 eb->stream = stream;
1094 eb->ra_session = ra_session;
1095 eb->update_anchor_relpath = update_anchor_relpath;
1096 eb->current_revision = revision;
1097 eb->pending_db = NULL;
1099 /* Create a special per-revision pool */
1100 eb->pool = svn_pool_create(pool);
1102 /* Open a unique temporary file for all textdelta applications in
1103 this edit session. The file is automatically closed and cleaned
1104 up when the edit session is done. */
1105 SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
1106 NULL, svn_io_file_del_on_close, pool, pool));
1108 de = svn_delta_default_editor(pool);
1109 de->open_root = open_root;
1110 de->delete_entry = delete_entry;
1111 de->add_directory = add_directory;
1112 de->open_directory = open_directory;
1113 de->close_directory = close_directory;
1114 de->change_dir_prop = change_dir_prop;
1115 de->change_file_prop = change_file_prop;
1116 de->apply_textdelta = apply_textdelta;
1117 de->add_file = add_file;
1118 de->open_file = open_file;
1119 de->close_file = close_file;
1120 de->close_edit = close_edit;
1122 /* Set the edit_baton and editor. */
1126 /* Wrap this editor in a cancellation editor. */
1127 SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1128 de, eb, editor, edit_baton, pool));
1130 shim_callbacks->fetch_base_func = fetch_base_func;
1131 shim_callbacks->fetch_props_func = fetch_props_func;
1132 shim_callbacks->fetch_kind_func = fetch_kind_func;
1133 shim_callbacks->fetch_baton = eb;
1135 SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1136 NULL, NULL, shim_callbacks, pool, pool));
1138 return SVN_NO_ERROR;