2 * dump_editor.c: A svn_delta_editor_t editor used to dump revisions.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 #include "svn_repos.h"
26 #include "svn_pools.h"
28 #include "svn_props.h"
29 #include "svn_subst.h"
30 #include "svn_dirent_uri.h"
32 #include "private/svn_repos_private.h"
36 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
39 /* Normalize the line ending style of the values of properties in PROPS
40 * that "need translation" (according to svn_prop_needs_translation(),
41 * currently all svn:* props) so that they contain only LF (\n) line endings.
43 * Put the normalized props into NORMAL_PROPS, allocated in RESULT_POOL.
46 normalize_props(apr_hash_t **normal_props,
48 apr_pool_t *result_pool)
53 *normal_props = apr_hash_make(result_pool);
55 iterpool = svn_pool_create(result_pool);
56 for (hi = apr_hash_first(result_pool, props); hi; hi = apr_hash_next(hi))
58 const char *key = apr_hash_this_key(hi);
59 const svn_string_t *value = apr_hash_this_val(hi);
61 svn_pool_clear(iterpool);
63 SVN_ERR(svn_repos__normalize_prop(&value, NULL, key, value,
65 svn_hash_sets(*normal_props, key, svn_string_dup(value, result_pool));
67 svn_pool_destroy(iterpool);
72 /* A directory baton used by all directory-related callback functions
73 * in the dump editor. */
76 struct dump_edit_baton *eb;
78 /* Pool for per-directory allocations */
81 /* the path to this directory */
82 const char *repos_relpath; /* a relpath */
84 /* Copyfrom info for the node, if any. */
85 const char *copyfrom_path; /* a relpath */
86 svn_revnum_t copyfrom_rev;
88 /* Headers accumulated so far for this directory */
89 svn_repos__dumpfile_headers_t *headers;
91 /* Properties which were modified during change_dir_prop. */
94 /* Properties which were deleted during change_dir_prop. */
95 apr_hash_t *deleted_props;
97 /* Hash of paths that need to be deleted, though some -might- be
98 replaced. Maps const char * paths to this dir_baton. Note that
99 they're full paths, because that's what the editor driver gives
100 us, although they're all really within this directory. */
101 apr_hash_t *deleted_entries;
103 /* Flag to trigger dumping props. */
104 svn_boolean_t dump_props;
107 /* A file baton used by all file-related callback functions in the dump
111 struct dump_edit_baton *eb;
113 /* Pool for per-file allocations */
116 /* the path to this file */
117 const char *repos_relpath; /* a relpath */
119 /* Properties which were modified during change_file_prop. */
122 /* Properties which were deleted during change_file_prop. */
123 apr_hash_t *deleted_props;
125 /* The checksum of the file the delta is being applied to */
126 const char *base_checksum;
128 /* Copy state and source information (if any). */
129 svn_boolean_t is_copy;
130 const char *copyfrom_path;
131 svn_revnum_t copyfrom_rev;
133 /* The action associate with this node. */
134 enum svn_node_action action;
136 /* Flags to trigger dumping props and text. */
137 svn_boolean_t dump_text;
138 svn_boolean_t dump_props;
141 /* The baton used by the dump editor. */
142 struct dump_edit_baton {
143 /* The output stream we write the dumpfile to */
144 svn_stream_t *stream;
146 /* The repository relpath of the anchor of the editor when driven
147 via the RA update mechanism; NULL otherwise. (When the editor is
148 driven via the RA "replay" mechanism instead, the editor is
149 always anchored at the repository, we don't need to prepend an
150 anchor path to the dumped node paths, and open_root() doesn't
151 need to manufacture directory additions.) */
152 const char *update_anchor_relpath;
154 /* Pool for per-revision allocations */
157 /* Temporary file used for textdelta application along with its
158 absolute path; these two variables should be allocated in the
159 per-edit-session pool */
160 const char *delta_abspath;
161 apr_file_t *delta_file;
163 /* The baton of the directory node whose block of
164 dump stream data has not been fully completed; NULL if there's no
166 struct dir_baton *pending_db;
169 /* Make a directory baton to represent the directory at PATH (relative
170 * to the EDIT_BATON).
172 * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
173 * directory should be compared for changes. If the copyfrom
174 * information is valid, the directory will be compared against its
177 * PB is the directory baton of this directory's parent, or NULL if
178 * this is the top-level directory of the edit.
180 * Perform all allocations in POOL. */
181 static struct svn_error_t *
182 make_dir_baton(struct dir_baton **dbp,
184 const char *copyfrom_path,
185 svn_revnum_t copyfrom_rev,
187 struct dir_baton *pb,
190 struct dump_edit_baton *eb = edit_baton;
191 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
192 const char *repos_relpath;
194 /* Construct the full path of this node. */
196 SVN_ERR(svn_relpath_canonicalize_safe(&repos_relpath, NULL, path,
201 /* Strip leading slash from copyfrom_path so that the path is
202 canonical and svn_relpath_join can be used */
204 copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
208 new_db->repos_relpath = repos_relpath;
209 new_db->copyfrom_path = copyfrom_path
210 ? svn_relpath_canonicalize(copyfrom_path, pool)
212 new_db->copyfrom_rev = copyfrom_rev;
213 new_db->headers = NULL;
214 new_db->props = apr_hash_make(pool);
215 new_db->deleted_props = apr_hash_make(pool);
216 new_db->deleted_entries = apr_hash_make(pool);
222 /* Make a file baton to represent the directory at PATH (relative to
223 * PB->eb). PB is the directory baton of this directory's parent, or
224 * NULL if this is the top-level directory of the edit. Perform all
225 * allocations in POOL. */
226 static struct file_baton *
227 make_file_baton(const char *path,
228 struct dir_baton *pb,
231 struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
235 new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
236 new_fb->props = apr_hash_make(pool);
237 new_fb->deleted_props = apr_hash_make(pool);
238 new_fb->is_copy = FALSE;
239 new_fb->copyfrom_path = NULL;
240 new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
241 new_fb->action = svn_node_action_change;
246 /* Append to HEADERS the required headers, and set *CONTENT to the property
247 * content section, to represent the property delta of PROPS/DELETED_PROPS.
250 get_props_content(svn_repos__dumpfile_headers_t *headers,
251 svn_stringbuf_t **content,
253 apr_hash_t *deleted_props,
254 apr_pool_t *result_pool,
255 apr_pool_t *scratch_pool)
257 svn_stream_t *content_stream;
258 apr_hash_t *normal_props;
260 *content = svn_stringbuf_create_empty(result_pool);
262 content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
264 SVN_ERR(normalize_props(&normal_props, props, scratch_pool));
265 SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
266 content_stream, "PROPS-END",
268 SVN_ERR(svn_stream_close(content_stream));
270 /* Prop-delta: true */
271 svn_repos__dumpfile_header_push(
272 headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
277 /* A special case of dump_node(), for a delete record.
279 * The only thing special about this version is it only writes one blank
280 * line, not two, after the headers. Why? Historical precedent for the
281 * case where a delete record is used as part of a (delete + add-with-history)
282 * in implementing a replacement.
285 dump_node_delete(svn_stream_t *stream,
286 const char *node_relpath,
289 svn_repos__dumpfile_headers_t *headers
290 = svn_repos__dumpfile_headers_create(pool);
292 assert(svn_relpath_is_canonical(node_relpath));
295 svn_repos__dumpfile_header_push(
296 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
298 /* Node-action: delete */
299 svn_repos__dumpfile_header_push(
300 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
302 SVN_ERR(svn_repos__dump_node_record(stream, headers,
303 NULL, FALSE, 0, /* props & text */
304 FALSE /*content_length_always*/, pool));
308 /* Set *HEADERS_P to contain some headers for the node at PATH of type KIND.
310 * ACTION describes what is happening to the node (see enum
313 * If the node was itself copied, IS_COPY is TRUE and the
314 * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
315 * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
316 * node is part of a copied subtree.
318 * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a
319 * complete deletion record to the dump stream.
321 * If ACTION is svn_node_action_delete, then the node record will be
322 * complete. (The caller may want to write two blank lines after the
326 dump_node(svn_repos__dumpfile_headers_t **headers_p,
327 struct dump_edit_baton *eb,
328 const char *repos_relpath,
329 struct dir_baton *db,
330 struct file_baton *fb,
331 enum svn_node_action action,
332 svn_boolean_t is_copy,
333 const char *copyfrom_path,
334 svn_revnum_t copyfrom_rev,
337 const char *node_relpath = repos_relpath;
338 svn_repos__dumpfile_headers_t *headers
339 = svn_repos__dumpfile_headers_create(pool);
341 assert(svn_relpath_is_canonical(repos_relpath));
342 assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
343 assert(! (db && fb));
345 /* Add the edit root relpath prefix if necessary. */
346 if (eb->update_anchor_relpath)
347 node_relpath = svn_relpath_join(eb->update_anchor_relpath,
351 svn_repos__dumpfile_header_push(
352 headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
354 /* Node-kind: "file" | "dir" */
356 svn_repos__dumpfile_header_push(
357 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
359 svn_repos__dumpfile_header_push(
360 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
363 /* Write the appropriate Node-action header */
366 case svn_node_action_change:
367 /* We are here after a change_file_prop or change_dir_prop. They
368 set up whatever dump_props they needed to- nothing to
369 do here but print node action information.
371 Node-action: change. */
372 svn_repos__dumpfile_header_push(
373 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
376 case svn_node_action_delete:
377 /* Node-action: delete */
378 svn_repos__dumpfile_header_push(
379 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
382 case svn_node_action_replace:
385 /* Node-action: replace */
386 svn_repos__dumpfile_header_push(
387 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
389 /* Wait for a change_*_prop to be called before dumping
392 fb->dump_props = TRUE;
394 db->dump_props = TRUE;
399 /* More complex case: is_copy is true, and copyfrom_path/
400 copyfrom_rev are present: delete the original, and then re-add
402 /* ### Why not write a 'replace' record? Don't know. */
404 /* ### Unusually, we end this 'delete' node record with only a single
405 blank line after the header block -- no extra blank line. */
406 SVN_ERR(dump_node_delete(eb->stream, repos_relpath, pool));
408 /* The remaining action is a non-replacing add-with-history */
409 /* action = svn_node_action_add; */
411 /* FALL THROUGH to 'add' */
413 case svn_node_action_add:
414 /* Node-action: add */
415 svn_repos__dumpfile_header_push(
416 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
420 /* Node-copyfrom-rev / Node-copyfrom-path */
421 svn_repos__dumpfile_header_pushf(
422 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV, "%ld", copyfrom_rev);
423 svn_repos__dumpfile_header_push(
424 headers, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH, copyfrom_path);
428 /* fb->dump_props (for files) is handled in close_file()
429 which is called immediately.
431 However, directories are not closed until all the work
432 inside them has been done; db->dump_props (for directories)
433 is handled (via dump_pending()) in all the functions that
434 can possibly be called after add_directory():
443 change_dir_prop() is a special case. */
445 fb->dump_props = TRUE;
447 db->dump_props = TRUE;
453 /* Return the headers so far. We don't necessarily have all the headers
454 yet -- there may be property-related and content length headers to
455 come, if this was not a 'delete' record. */
456 *headers_p = headers;
461 dump_mkdir(struct dump_edit_baton *eb,
462 const char *repos_relpath,
465 svn_stringbuf_t *prop_content;
466 svn_repos__dumpfile_headers_t *headers
467 = svn_repos__dumpfile_headers_create(pool);
470 svn_repos__dumpfile_header_push(
471 headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath);
474 svn_repos__dumpfile_header_push(
475 headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
477 /* Node-action: add */
478 svn_repos__dumpfile_header_push(
479 headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
481 /* Dump the (empty) property block. */
482 SVN_ERR(get_props_content(headers, &prop_content,
483 apr_hash_make(pool), apr_hash_make(pool),
485 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content,
486 FALSE, 0, FALSE /*content_length_always*/,
489 /* Newlines to tie it all off. */
490 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
495 /* Dump pending headers and properties for the directory EB->pending_db (if
496 * not null), to allow starting the dump of a child node */
498 dump_pending_dir(struct dump_edit_baton *eb,
499 apr_pool_t *scratch_pool)
501 struct dir_baton *db = eb->pending_db;
502 svn_stringbuf_t *prop_content = NULL;
507 /* Some pending properties to dump? */
510 SVN_ERR(get_props_content(db->headers, &prop_content,
511 db->props, db->deleted_props,
512 scratch_pool, scratch_pool));
514 SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content,
515 FALSE, 0, FALSE /*content_length_always*/,
518 /* No text is going to be dumped. Write a couple of newlines and
519 wait for the next node/ revision. */
520 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
524 /* Cleanup so that data is never dumped twice. */
525 apr_hash_clear(db->props);
526 apr_hash_clear(db->deleted_props);
527 db->dump_props = FALSE;
530 /* Anything that was pending is pending no longer. */
531 eb->pending_db = NULL;
538 /*** Editor Function Implementations ***/
541 open_root(void *edit_baton,
542 svn_revnum_t base_revision,
546 struct dump_edit_baton *eb = edit_baton;
547 struct dir_baton *new_db = NULL;
549 /* Clear the per-revision pool after each revision */
550 svn_pool_clear(eb->pool);
552 if (eb->update_anchor_relpath)
555 const char *parent_path = eb->update_anchor_relpath;
556 apr_array_header_t *dirs_to_add =
557 apr_array_make(pool, 4, sizeof(const char *));
558 apr_pool_t *iterpool = svn_pool_create(pool);
560 while (! svn_path_is_empty(parent_path))
562 APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
563 parent_path = svn_relpath_dirname(parent_path, pool);
566 for (i = dirs_to_add->nelts; i; --i)
568 const char *dir_to_add =
569 APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
571 svn_pool_clear(iterpool);
573 /* For parents of the source directory, we just manufacture
574 the adds ourselves. */
577 SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
581 /* ... but for the source directory itself, we'll defer
582 to letting the typical plumbing handle this task. */
583 SVN_ERR(make_dir_baton(&new_db, NULL, NULL, SVN_INVALID_REVNUM,
584 edit_baton, NULL, pool));
585 SVN_ERR(dump_node(&new_db->headers,
586 eb, new_db->repos_relpath, new_db,
587 NULL, svn_node_action_add, FALSE,
588 NULL, SVN_INVALID_REVNUM, pool));
590 /* Remember that we've started but not yet finished
591 handling this directory. */
592 eb->pending_db = new_db;
595 svn_pool_destroy(iterpool);
600 SVN_ERR(make_dir_baton(&new_db, NULL, NULL, SVN_INVALID_REVNUM,
601 edit_baton, NULL, pool));
604 *root_baton = new_db;
609 delete_entry(const char *path,
610 svn_revnum_t revision,
614 struct dir_baton *pb = parent_baton;
616 SVN_ERR(dump_pending_dir(pb->eb, pool));
618 /* We don't dump this deletion immediate. Rather, we add this path
619 to the deleted_entries of the parent directory baton. That way,
620 we can tell (later) an addition from a replacement. All the real
621 deletions get handled in close_directory(). */
622 svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->pool, path), pb);
628 add_directory(const char *path,
630 const char *copyfrom_path,
631 svn_revnum_t copyfrom_rev,
635 struct dir_baton *pb = parent_baton;
637 struct dir_baton *new_db;
638 svn_boolean_t is_copy;
640 SVN_ERR(dump_pending_dir(pb->eb, pool));
642 SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, pb->eb,
645 /* This might be a replacement -- is the path already deleted? */
646 was_deleted = svn_hash_gets(pb->deleted_entries, path);
648 /* Detect an add-with-history */
649 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
652 SVN_ERR(dump_node(&new_db->headers,
653 pb->eb, new_db->repos_relpath, new_db, NULL,
654 was_deleted ? svn_node_action_replace : svn_node_action_add,
656 is_copy ? new_db->copyfrom_path : NULL,
657 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
661 /* Delete the path, it's now been dumped */
662 svn_hash_sets(pb->deleted_entries, path, NULL);
664 /* Remember that we've started, but not yet finished handling this
666 pb->eb->pending_db = new_db;
668 *child_baton = new_db;
673 open_directory(const char *path,
675 svn_revnum_t base_revision,
679 struct dir_baton *pb = parent_baton;
680 struct dir_baton *new_db;
681 const char *copyfrom_path = NULL;
682 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
684 SVN_ERR(dump_pending_dir(pb->eb, pool));
686 /* If the parent directory has explicit comparison path and rev,
687 record the same for this one. */
688 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
690 copyfrom_path = svn_relpath_join(pb->copyfrom_path,
691 svn_relpath_basename(path, NULL),
693 copyfrom_rev = pb->copyfrom_rev;
696 SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev,
697 pb->eb, pb, pb->pool));
699 *child_baton = new_db;
704 close_directory(void *dir_baton,
707 struct dir_baton *db = dir_baton;
708 apr_hash_index_t *hi;
709 svn_boolean_t this_pending;
711 /* Remember if this directory is the one currently pending. */
712 this_pending = (db->eb->pending_db == db);
714 SVN_ERR(dump_pending_dir(db->eb, pool));
716 /* If this directory was pending, then dump_pending() should have
717 taken care of all the props and such. Of course, the only way
718 that would be the case is if this directory was added/replaced.
720 Otherwise, if stuff for this directory has already been written
721 out (at some point in the past, prior to our handling other
722 nodes), we might need to generate a second "change" record just
723 to carry the information we've since learned about the
725 if ((! this_pending) && (db->dump_props))
727 SVN_ERR(dump_node(&db->headers,
728 db->eb, db->repos_relpath, db, NULL,
729 svn_node_action_change, FALSE,
730 NULL, SVN_INVALID_REVNUM, pool));
731 db->eb->pending_db = db;
732 SVN_ERR(dump_pending_dir(db->eb, pool));
735 /* Dump the deleted directory entries */
736 for (hi = apr_hash_first(pool, db->deleted_entries); hi;
737 hi = apr_hash_next(hi))
739 const char *path = apr_hash_this_key(hi);
741 SVN_ERR(dump_node_delete(db->eb->stream, path, pool));
742 /* This deletion record is complete -- write an extra newline */
743 SVN_ERR(svn_stream_puts(db->eb->stream, "\n"));
746 /* ### should be unnecessary */
747 apr_hash_clear(db->deleted_entries);
753 add_file(const char *path,
755 const char *copyfrom_path,
756 svn_revnum_t copyfrom_rev,
760 struct dir_baton *pb = parent_baton;
761 struct file_baton *fb;
764 SVN_ERR(dump_pending_dir(pb->eb, pool));
766 /* Make the file baton. */
767 fb = make_file_baton(path, pb, pool);
769 /* This might be a replacement -- is the path already deleted? */
770 was_deleted = svn_hash_gets(pb->deleted_entries, path);
772 /* Detect add-with-history. */
773 if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
775 fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
776 fb->copyfrom_rev = copyfrom_rev;
779 fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add;
781 /* Delete the path, it's now been dumped. */
783 svn_hash_sets(pb->deleted_entries, path, NULL);
790 open_file(const char *path,
792 svn_revnum_t ancestor_revision,
796 struct dir_baton *pb = parent_baton;
797 struct file_baton *fb;
799 SVN_ERR(dump_pending_dir(pb->eb, pool));
801 /* Make the file baton. */
802 fb = make_file_baton(path, pb, pool);
804 /* If the parent directory has explicit copyfrom path and rev,
805 record the same for this one. */
806 if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
808 fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
809 svn_relpath_basename(path, NULL),
811 fb->copyfrom_rev = pb->copyfrom_rev;
819 change_dir_prop(void *parent_baton,
821 const svn_string_t *value,
824 struct dir_baton *db = parent_baton;
825 svn_boolean_t this_pending;
827 /* This directory is not pending, but something else is, so handle
828 the "something else". */
829 this_pending = (db->eb->pending_db == db);
831 SVN_ERR(dump_pending_dir(db->eb, pool));
833 if (svn_property_kind2(name) != svn_prop_regular_kind)
837 svn_hash_sets(db->props,
838 apr_pstrdup(db->pool, name),
839 svn_string_dup(value, db->pool));
841 svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
843 /* Make sure we eventually output the props */
844 db->dump_props = TRUE;
850 change_file_prop(void *file_baton,
852 const svn_string_t *value,
855 struct file_baton *fb = file_baton;
857 if (svn_property_kind2(name) != svn_prop_regular_kind)
861 svn_hash_sets(fb->props,
862 apr_pstrdup(fb->pool, name),
863 svn_string_dup(value, fb->pool));
865 svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
867 /* Dump the property headers and wait; close_file might need
868 to write text headers too depending on whether
869 apply_textdelta is called */
870 fb->dump_props = TRUE;
876 apply_textdelta(void *file_baton, const char *base_checksum,
878 svn_txdelta_window_handler_t *handler,
879 void **handler_baton)
881 struct file_baton *fb = file_baton;
882 struct dump_edit_baton *eb = fb->eb;
883 svn_stream_t *delta_filestream;
885 /* Use a temporary file to measure the Text-content-length */
886 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
888 /* Prepare to write the delta to the delta_filestream */
889 svn_txdelta_to_svndiff3(handler, handler_baton,
891 SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
893 /* Record that there's text to be dumped, and its base checksum. */
894 fb->dump_text = TRUE;
895 fb->base_checksum = apr_pstrdup(fb->pool, base_checksum);
901 close_file(void *file_baton,
902 const char *text_checksum,
905 struct file_baton *fb = file_baton;
906 struct dump_edit_baton *eb = fb->eb;
907 svn_filesize_t text_content_length = 0;
908 svn_stringbuf_t *propstring = NULL;
909 svn_repos__dumpfile_headers_t *headers;
911 SVN_ERR(dump_pending_dir(eb, pool));
913 /* Start dumping this node, by collecting some basic headers for it. */
914 SVN_ERR(dump_node(&headers, eb, fb->repos_relpath, NULL, fb,
915 fb->action, fb->is_copy, fb->copyfrom_path,
916 fb->copyfrom_rev, pool));
918 /* Some pending properties to dump? We'll dump just the headers for
919 now, then dump the actual propchange content only after dumping
920 the text headers too (if present). */
923 SVN_ERR(get_props_content(headers, &propstring,
924 fb->props, fb->deleted_props,
928 /* Dump the text headers */
931 /* Text-delta: true */
932 svn_repos__dumpfile_header_push(
933 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
935 SVN_ERR(svn_io_file_size_get(&text_content_length, eb->delta_file,
938 if (fb->base_checksum)
939 /* Text-delta-base-md5: */
940 svn_repos__dumpfile_header_push(
941 headers, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5, fb->base_checksum);
943 /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
944 svn_repos__dumpfile_header_push(
945 headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum);
948 /* Dump the headers and props now */
949 SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, propstring,
950 fb->dump_text, text_content_length,
951 FALSE /*content_length_always*/,
957 fb->dump_props = FALSE;
958 apr_hash_clear(fb->props);
959 apr_hash_clear(fb->deleted_props);
965 /* Seek to the beginning of the delta file, map it to a stream,
966 and copy the stream to eb->stream. Then close the stream and
967 truncate the file so we can reuse it for the next textdelta
968 application. Note that the file isn't created, opened or
970 svn_stream_t *delta_filestream;
971 apr_off_t offset = 0;
973 SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
974 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
975 SVN_ERR(svn_stream_copy3(delta_filestream,
976 svn_stream_disown(eb->stream, pool),
980 SVN_ERR(svn_stream_close(delta_filestream));
981 SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
984 /* Write a couple of blank lines for matching output with `svnadmin
986 SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
992 close_edit(void *edit_baton, apr_pool_t *pool)
998 svn_repos__get_dump_editor(const svn_delta_editor_t **editor,
1000 svn_stream_t *stream,
1001 const char *update_anchor_relpath,
1004 struct dump_edit_baton *eb;
1005 svn_delta_editor_t *de;
1007 eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
1008 eb->stream = stream;
1009 eb->update_anchor_relpath = update_anchor_relpath;
1010 eb->pending_db = NULL;
1012 /* Create a special per-revision pool */
1013 eb->pool = svn_pool_create(pool);
1015 /* Open a unique temporary file for all textdelta applications in
1016 this edit session. The file is automatically closed and cleaned
1017 up when the edit session is done. */
1018 SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
1019 NULL, svn_io_file_del_on_close, pool, pool));
1021 de = svn_delta_default_editor(pool);
1022 de->open_root = open_root;
1023 de->delete_entry = delete_entry;
1024 de->add_directory = add_directory;
1025 de->open_directory = open_directory;
1026 de->close_directory = close_directory;
1027 de->change_dir_prop = change_dir_prop;
1028 de->change_file_prop = change_file_prop;
1029 de->apply_textdelta = apply_textdelta;
1030 de->add_file = add_file;
1031 de->open_file = open_file;
1032 de->close_file = close_file;
1033 de->close_edit = close_edit;
1035 /* Set the edit_baton and editor. */
1039 return SVN_NO_ERROR;