Sync with upstream
[svnrdump.git] / dump_editor.c
blobc829549070e1653c8cb0b920e83de4fffbc78dd3
1 /*
2 * dump_editor.c: The svn_delta_editor_t editor used by svnrdump to
3 * dump revisions.
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
21 * under the License.
22 * ====================================================================
25 #include "svn_hash.h"
26 #include "svn_pools.h"
27 #include "svn_repos.h"
28 #include "svn_path.h"
29 #include "svn_props.h"
30 #include "svn_subst.h"
31 #include "svn_dirent_uri.h"
33 #include "svn17_compat.h"
34 #include "dump_editor.h"
36 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
38 #if 0
39 #define LDR_DBG(x) SVN_DBG(x)
40 #else
41 #define LDR_DBG(x) while(0)
42 #endif
44 /* The baton used by the dump editor. */
45 struct dump_edit_baton {
46 /* The output stream we write the dumpfile to */
47 svn_stream_t *stream;
49 /* Pool for per-revision allocations */
50 apr_pool_t *pool;
52 /* Properties which were modified during change_file_prop
53 * or change_dir_prop. */
54 apr_hash_t *props;
56 /* Properties which were deleted during change_file_prop
57 * or change_dir_prop. */
58 apr_hash_t *deleted_props;
60 /* Temporary buffer to write property hashes to in human-readable
61 * form. ### Is this really needed? */
62 svn_stringbuf_t *propstring;
64 /* Temporary file used for textdelta application along with its
65 absolute path; these two variables should be allocated in the
66 per-edit-session pool */
67 const char *delta_abspath;
68 apr_file_t *delta_file;
70 /* The checksum of the file the delta is being applied to */
71 const char *base_checksum;
73 /* Flags to trigger dumping props and text */
74 svn_boolean_t dump_text;
75 svn_boolean_t dump_props;
76 svn_boolean_t dump_newlines;
79 /* Normalize the line ending style of the values of properties in PROPS
80 * that "need translation" (according to svn_prop_needs_translation(),
81 * currently all svn:* props) so that they contain only LF (\n) line endings.
83 svn_error_t *
84 normalize_props(apr_hash_t *props,
85 apr_pool_t *pool)
87 apr_hash_index_t *hi;
88 const char *key, *cstring;
89 const svn_string_t *value;
91 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
93 key = svn__apr_hash_index_key(hi);
94 value = svn__apr_hash_index_val(hi);
96 if (svn_prop_needs_translation(key))
98 SVN_ERR(svn_subst_translate_cstring2(value->data, &cstring,
99 "\n", TRUE,
100 NULL, FALSE,
101 pool));
102 value = svn_string_create(cstring, pool);
103 apr_hash_set(props, key, APR_HASH_KEY_STRING, value);
106 return SVN_NO_ERROR;
109 /* Make a directory baton to represent the directory at path (relative
110 * to the edit_baton).
112 * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
113 * directory should be compared for changes. If the copyfrom
114 * information is valid, the directory will be compared against its
115 * copy source.
117 * PARENT_DIR_BATON is the directory baton of this directory's parent,
118 * or NULL if this is the top-level directory of the edit. ADDED
119 * indicates if this directory is newly added in this revision.
120 * Perform all allocations in POOL. */
121 static struct dir_baton *
122 make_dir_baton(const char *path,
123 const char *copyfrom_path,
124 svn_revnum_t copyfrom_rev,
125 void *edit_baton,
126 void *parent_dir_baton,
127 svn_boolean_t added,
128 apr_pool_t *pool)
130 struct dump_edit_baton *eb = edit_baton;
131 struct dir_baton *pb = parent_dir_baton;
132 struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
133 const char *abspath;
135 /* Construct the full path of this node. */
136 if (pb)
137 abspath = svn_uri_join("/", path, pool);
138 else
139 abspath = "/";
141 /* Strip leading slash from copyfrom_path so that the path is
142 canonical and svn_relpath_join can be used */
143 if (copyfrom_path)
144 copyfrom_path = ((*copyfrom_path == '/') ?
145 copyfrom_path + 1 : copyfrom_path);
147 new_db->eb = eb;
148 new_db->parent_dir_baton = pb;
149 new_db->abspath = abspath;
150 new_db->copyfrom_path = copyfrom_path;
151 new_db->copyfrom_rev = copyfrom_rev;
152 new_db->added = added;
153 new_db->written_out = FALSE;
154 new_db->deleted_entries = apr_hash_make(pool);
156 return new_db;
159 /* Extract and dump properties stored in edit baton EB, using POOL for
160 * any temporary allocations. If TRIGGER_VAR is not NULL, it is set to FALSE.
161 * Unless DUMP_DATA_TOO is set, only property headers are dumped.
163 static svn_error_t *
164 dump_props(struct dump_edit_baton *eb,
165 svn_boolean_t *trigger_var,
166 svn_boolean_t dump_data_too,
167 apr_pool_t *pool)
169 svn_stream_t *propstream;
171 if (trigger_var && !*trigger_var)
172 return SVN_NO_ERROR;
174 SVN_ERR(normalize_props(eb->props, eb->pool));
175 svn_stringbuf_setempty(eb->propstring);
176 propstream = svn_stream_from_stringbuf(eb->propstring, eb->pool);
177 SVN_ERR(svn_hash_write_incremental(eb->props, eb->deleted_props,
178 propstream, "PROPS-END", pool));
179 SVN_ERR(svn_stream_close(propstream));
181 /* Prop-delta: true */
182 SVN_ERR(svn_stream_printf(eb->stream, pool,
183 SVN_REPOS_DUMPFILE_PROP_DELTA
184 ": true\n"));
186 /* Prop-content-length: 193 */
187 SVN_ERR(svn_stream_printf(eb->stream, pool,
188 SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
189 ": %" APR_SIZE_T_FMT "\n", eb->propstring->len));
191 if (dump_data_too)
193 /* Content-length: 14 */
194 SVN_ERR(svn_stream_printf(eb->stream, pool,
195 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
196 ": %" APR_SIZE_T_FMT "\n\n",
197 eb->propstring->len));
199 /* The properties. */
200 SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data,
201 &(eb->propstring->len)));
203 /* No text is going to be dumped. Write a couple of newlines and
204 wait for the next node/ revision. */
205 SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
207 /* Cleanup so that data is never dumped twice. */
208 apr_hash_clear(eb->props);
209 apr_hash_clear(eb->deleted_props);
210 if (trigger_var)
211 *trigger_var = FALSE;
214 return SVN_NO_ERROR;
217 static svn_error_t *
218 dump_newlines(struct dump_edit_baton *eb,
219 svn_boolean_t *trigger_var,
220 apr_pool_t *pool)
222 if (trigger_var && *trigger_var)
224 SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
225 *trigger_var = FALSE;
227 return SVN_NO_ERROR;
231 * Write out a node record for PATH of type KIND under EB->FS_ROOT.
232 * ACTION describes what is happening to the node (see enum
233 * svn_node_action). Write record to writable EB->STREAM, using
234 * EB->BUFFER to write in chunks.
236 * If the node was itself copied, IS_COPY is TRUE and the
237 * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
238 * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
239 * node is part of a copied subtree.
241 static svn_error_t *
242 dump_node(struct dump_edit_baton *eb,
243 const char *path, /* an absolute path. */
244 svn_node_kind_t kind,
245 enum svn_node_action action,
246 svn_boolean_t is_copy,
247 const char *copyfrom_path,
248 svn_revnum_t copyfrom_rev,
249 apr_pool_t *pool)
251 /* Remove leading slashes from path and copyfrom_path */
252 if (path)
253 path = ((*path == '/') ? path + 1 : path);
255 if (copyfrom_path)
256 copyfrom_path = ((*copyfrom_path == '/') ?
257 copyfrom_path + 1 : copyfrom_path);
259 /* Node-path: commons/STATUS */
260 SVN_ERR(svn_stream_printf(eb->stream, pool,
261 SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n", path));
263 /* Node-kind: file */
264 if (kind == svn_node_file)
265 SVN_ERR(svn_stream_printf(eb->stream, pool,
266 SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
267 else if (kind == svn_node_dir)
268 SVN_ERR(svn_stream_printf(eb->stream, pool,
269 SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
272 /* Write the appropriate Node-action header */
273 switch (action)
275 case svn_node_action_change:
276 /* We are here after a change_file_prop or change_dir_prop. They
277 set up whatever dump_props they needed to- nothing to
278 do here but print node action information */
279 SVN_ERR(svn_stream_printf(eb->stream, pool,
280 SVN_REPOS_DUMPFILE_NODE_ACTION
281 ": change\n"));
282 break;
284 case svn_node_action_replace:
285 if (!is_copy)
287 /* Node-action: replace */
288 SVN_ERR(svn_stream_printf(eb->stream, pool,
289 SVN_REPOS_DUMPFILE_NODE_ACTION
290 ": replace\n"));
292 /* Wait for a change_*_prop to be called before dumping
293 anything */
294 eb->dump_props = TRUE;
295 break;
297 /* More complex case: is_copy is true, and copyfrom_path/
298 copyfrom_rev are present: delete the original, and then re-add
299 it */
301 SVN_ERR(svn_stream_printf(eb->stream, pool,
302 SVN_REPOS_DUMPFILE_NODE_ACTION
303 ": delete\n\n"));
305 /* Recurse: Print an additional add-with-history record. */
306 SVN_ERR(dump_node(eb, path, kind, svn_node_action_add,
307 is_copy, copyfrom_path, copyfrom_rev, pool));
309 /* We can leave this routine quietly now, don't need to dump any
310 content; that was already done in the second record. */
311 break;
313 case svn_node_action_delete:
314 SVN_ERR(svn_stream_printf(eb->stream, pool,
315 SVN_REPOS_DUMPFILE_NODE_ACTION
316 ": delete\n"));
318 /* We can leave this routine quietly now. Nothing more to do-
319 print a couple of newlines because we're not dumping props or
320 text. */
321 SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
322 break;
324 case svn_node_action_add:
325 SVN_ERR(svn_stream_printf(eb->stream, pool,
326 SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
328 if (!is_copy)
330 /* eb->dump_props for files is handled in close_file
331 which is called immediately. However, directories are not
332 closed until all the work inside them has been done;
333 eb->dump_props for directories is handled in all the
334 functions that can possibly be called after add_directory:
335 add_directory, open_directory, delete_entry, close_directory,
336 add_file, open_file. change_dir_prop is a special case. */
338 /* Wait for a change_*_prop to be called before dumping
339 anything */
340 eb->dump_props = TRUE;
341 break;
344 SVN_ERR(svn_stream_printf(eb->stream, pool,
345 SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
346 ": %ld\n"
347 SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
348 ": %s\n",
349 copyfrom_rev, copyfrom_path));
351 /* Ugly hack: If a directory was copied from a previous
352 revision, nothing like close_file will be called to write two
353 blank lines. If change_dir_prop is called, props are dumped
354 (along with the necessary PROPS-END\n\n and we're good. So
355 set a dump_newlines here to print the newlines unless
356 change_dir_prop is called next otherwise the `svnadmin load`
357 parser will fail. */
358 if (kind == svn_node_dir)
359 eb->dump_newlines = TRUE;
361 break;
363 return SVN_NO_ERROR;
366 static svn_error_t *
367 open_root(void *edit_baton,
368 svn_revnum_t base_revision,
369 apr_pool_t *pool,
370 void **root_baton)
372 struct dump_edit_baton *eb = edit_baton;
374 /* Clear the per-revision pool after each revision */
375 svn_pool_clear(eb->pool);
377 eb->props = apr_hash_make(eb->pool);
378 eb->deleted_props = apr_hash_make(eb->pool);
379 eb->propstring = svn_stringbuf_create("", eb->pool);
381 *root_baton = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
382 edit_baton, NULL, FALSE, eb->pool);
383 LDR_DBG(("open_root %p\n", *root_baton));
385 return SVN_NO_ERROR;
388 static svn_error_t *
389 delete_entry(const char *path,
390 svn_revnum_t revision,
391 void *parent_baton,
392 apr_pool_t *pool)
394 struct dir_baton *pb = parent_baton;
396 LDR_DBG(("delete_entry %s\n", path));
398 /* Some pending properties to dump? */
399 SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool));
401 /* Some pending newlines to dump? */
402 SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool));
404 /* Add this path to the deleted_entries of the parent directory
405 baton. */
406 apr_hash_set(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path),
407 APR_HASH_KEY_STRING, pb);
409 return SVN_NO_ERROR;
412 static svn_error_t *
413 add_directory(const char *path,
414 void *parent_baton,
415 const char *copyfrom_path,
416 svn_revnum_t copyfrom_rev,
417 apr_pool_t *pool,
418 void **child_baton)
420 struct dir_baton *pb = parent_baton;
421 void *val;
422 struct dir_baton *new_db;
423 svn_boolean_t is_copy;
425 LDR_DBG(("add_directory %s\n", path));
427 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
428 pb, TRUE, pb->eb->pool);
430 /* Some pending properties to dump? */
431 SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool));
433 /* Some pending newlines to dump? */
434 SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool));
436 /* This might be a replacement -- is the path already deleted? */
437 val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
439 /* Detect an add-with-history */
440 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
442 /* Dump the node */
443 SVN_ERR(dump_node(pb->eb, path,
444 svn_node_dir,
445 val ? svn_node_action_replace : svn_node_action_add,
446 is_copy,
447 is_copy ? copyfrom_path : NULL,
448 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
449 pool));
451 if (val)
452 /* Delete the path, it's now been dumped */
453 apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
455 new_db->written_out = TRUE;
457 *child_baton = new_db;
458 return SVN_NO_ERROR;
461 static svn_error_t *
462 open_directory(const char *path,
463 void *parent_baton,
464 svn_revnum_t base_revision,
465 apr_pool_t *pool,
466 void **child_baton)
468 struct dir_baton *pb = parent_baton;
469 struct dir_baton *new_db;
470 const char *copyfrom_path = NULL;
471 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
473 LDR_DBG(("open_directory %s\n", path));
475 /* Some pending properties to dump? */
476 SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool));
478 /* Some pending newlines to dump? */
479 SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool));
481 /* If the parent directory has explicit comparison path and rev,
482 record the same for this one. */
483 if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
485 copyfrom_path = svn_uri_join(pb->copyfrom_path,
486 svn_relpath_basename(path, NULL),
487 pb->eb->pool);
488 copyfrom_rev = pb->copyfrom_rev;
491 new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
492 FALSE, pb->eb->pool);
493 *child_baton = new_db;
494 return SVN_NO_ERROR;
497 static svn_error_t *
498 close_directory(void *dir_baton,
499 apr_pool_t *pool)
501 struct dir_baton *db = dir_baton;
502 struct dump_edit_baton *eb = db->eb;
503 apr_hash_index_t *hi;
505 LDR_DBG(("close_directory %p\n", dir_baton));
507 /* Some pending properties to dump? */
508 SVN_ERR(dump_props(eb, &(eb->dump_props), TRUE, pool));
510 /* Some pending newlines to dump? */
511 SVN_ERR(dump_newlines(eb, &(eb->dump_newlines), pool));
513 /* Dump the deleted directory entries */
514 for (hi = apr_hash_first(pool, db->deleted_entries); hi;
515 hi = apr_hash_next(hi))
517 const void *key;
518 const char *path;
519 apr_hash_this(hi, &key, NULL, NULL);
520 path = key;
522 SVN_ERR(dump_node(db->eb, path, svn_node_unknown, svn_node_action_delete,
523 FALSE, NULL, SVN_INVALID_REVNUM, pool));
526 apr_hash_clear(db->deleted_entries);
527 return SVN_NO_ERROR;
530 static svn_error_t *
531 add_file(const char *path,
532 void *parent_baton,
533 const char *copyfrom_path,
534 svn_revnum_t copyfrom_rev,
535 apr_pool_t *pool,
536 void **file_baton)
538 struct dir_baton *pb = parent_baton;
539 void *val;
540 svn_boolean_t is_copy;
542 LDR_DBG(("add_file %s\n", path));
544 /* Some pending properties to dump? */
545 SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool));
547 /* Some pending newlines to dump? */
548 SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool));
550 /* This might be a replacement -- is the path already deleted? */
551 val = apr_hash_get(pb->deleted_entries, path, APR_HASH_KEY_STRING);
553 /* Detect add-with-history. */
554 is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
556 /* Dump the node. */
557 SVN_ERR(dump_node(pb->eb, path,
558 svn_node_file,
559 val ? svn_node_action_replace : svn_node_action_add,
560 is_copy,
561 is_copy ? copyfrom_path : NULL,
562 is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
563 pool));
565 if (val)
566 /* delete the path, it's now been dumped. */
567 apr_hash_set(pb->deleted_entries, path, APR_HASH_KEY_STRING, NULL);
569 /* Build a nice file baton to pass to change_file_prop and
570 apply_textdelta */
571 *file_baton = pb->eb;
573 return SVN_NO_ERROR;
576 static svn_error_t *
577 open_file(const char *path,
578 void *parent_baton,
579 svn_revnum_t ancestor_revision,
580 apr_pool_t *pool,
581 void **file_baton)
583 struct dir_baton *pb = parent_baton;
584 const char *copyfrom_path = NULL;
585 svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
587 LDR_DBG(("open_file %s\n", path));
589 /* Some pending properties to dump? */
590 SVN_ERR(dump_props(pb->eb, &(pb->eb->dump_props), TRUE, pool));
592 /* Some pending newlines to dump? */
593 SVN_ERR(dump_newlines(pb->eb, &(pb->eb->dump_newlines), pool));
595 /* If the parent directory has explicit copyfrom path and rev,
596 record the same for this one. */
597 if (pb && ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
599 copyfrom_path = svn_relpath_join(pb->copyfrom_path,
600 svn_relpath_basename(path, NULL),
601 pb->eb->pool);
602 copyfrom_rev = pb->copyfrom_rev;
605 SVN_ERR(dump_node(pb->eb, path, svn_node_file, svn_node_action_change,
606 FALSE, copyfrom_path, copyfrom_rev, pool));
608 /* Build a nice file baton to pass to change_file_prop and
609 apply_textdelta */
610 *file_baton = pb->eb;
612 return SVN_NO_ERROR;
615 static svn_error_t *
616 change_dir_prop(void *parent_baton,
617 const char *name,
618 const svn_string_t *value,
619 apr_pool_t *pool)
621 struct dir_baton *db = parent_baton;
623 LDR_DBG(("change_dir_prop %p\n", parent_baton));
625 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
626 return SVN_NO_ERROR;
628 if (value)
629 apr_hash_set(db->eb->props, apr_pstrdup(db->eb->pool, name),
630 APR_HASH_KEY_STRING, svn_string_dup(value, db->eb->pool));
631 else
632 apr_hash_set(db->eb->deleted_props, apr_pstrdup(db->eb->pool, name),
633 APR_HASH_KEY_STRING, "");
635 if (! db->written_out)
637 /* If db->written_out is set, it means that the node information
638 corresponding to this directory has already been written: don't
639 do anything; dump_props will take care of dumping the
640 props. If it not, dump the node itself before dumping the
641 props. */
643 SVN_ERR(dump_node(db->eb, db->abspath, svn_node_dir,
644 svn_node_action_change, FALSE, db->copyfrom_path,
645 db->copyfrom_rev, pool));
646 db->written_out = TRUE;
649 /* Dump props whether or not the directory has been written
650 out. Then disable printing a couple of extra newlines */
651 SVN_ERR(dump_props(db->eb, NULL, TRUE, pool));
652 db->eb->dump_newlines = FALSE;
654 return SVN_NO_ERROR;
657 static svn_error_t *
658 change_file_prop(void *file_baton,
659 const char *name,
660 const svn_string_t *value,
661 apr_pool_t *pool)
663 struct dump_edit_baton *eb = file_baton;
665 LDR_DBG(("change_file_prop %p\n", file_baton));
667 if (svn_property_kind(NULL, name) != svn_prop_regular_kind)
668 return SVN_NO_ERROR;
670 if (value)
671 apr_hash_set(eb->props, apr_pstrdup(eb->pool, name),
672 APR_HASH_KEY_STRING, svn_string_dup(value, eb->pool));
673 else
674 apr_hash_set(eb->deleted_props, apr_pstrdup(eb->pool, name),
675 APR_HASH_KEY_STRING, "");
677 /* Dump the property headers and wait; close_file might need
678 to write text headers too depending on whether
679 apply_textdelta is called */
680 eb->dump_props = TRUE;
682 return SVN_NO_ERROR;
685 static svn_error_t *
686 window_handler(svn_txdelta_window_t *window, void *baton)
688 struct handler_baton *hb = baton;
689 static svn_error_t *err;
691 err = hb->apply_handler(window, hb->apply_baton);
692 if (window != NULL && !err)
693 return SVN_NO_ERROR;
695 if (err)
696 SVN_ERR(err);
698 return SVN_NO_ERROR;
701 static svn_error_t *
702 apply_textdelta(void *file_baton, const char *base_checksum,
703 apr_pool_t *pool,
704 svn_txdelta_window_handler_t *handler,
705 void **handler_baton)
707 struct dump_edit_baton *eb = file_baton;
709 /* Custom handler_baton allocated in a separate pool */
710 struct handler_baton *hb;
711 svn_stream_t *delta_filestream;
713 hb = apr_pcalloc(eb->pool, sizeof(*hb));
715 LDR_DBG(("apply_textdelta %p\n", file_baton));
717 /* Use a temporary file to measure the text-content-length */
718 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
720 /* Prepare to write the delta to the delta_filestream */
721 svn_txdelta_to_svndiff2(&(hb->apply_handler), &(hb->apply_baton),
722 delta_filestream, 0, pool);
724 eb->dump_text = TRUE;
725 eb->base_checksum = apr_pstrdup(eb->pool, base_checksum);
726 svn_stream_close(delta_filestream);
728 /* The actual writing takes place when this function has
729 finished. Set handler and handler_baton now so for
730 window_handler() */
731 *handler = window_handler;
732 *handler_baton = hb;
734 return SVN_NO_ERROR;
737 static svn_error_t *
738 close_file(void *file_baton,
739 const char *text_checksum,
740 apr_pool_t *pool)
742 struct dump_edit_baton *eb = file_baton;
743 svn_stream_t *delta_filestream;
744 apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
745 apr_off_t offset;
746 apr_status_t err;
748 LDR_DBG(("close_file %p\n", file_baton));
750 /* Some pending properties to dump? Dump just the headers- dump the
751 props only after dumping the text headers too (if present) */
752 SVN_ERR(dump_props(eb, &(eb->dump_props), FALSE, pool));
754 /* Dump the text headers */
755 if (eb->dump_text)
757 /* Text-delta: true */
758 SVN_ERR(svn_stream_printf(eb->stream, pool,
759 SVN_REPOS_DUMPFILE_TEXT_DELTA
760 ": true\n"));
762 err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file);
763 if (err)
764 SVN_ERR(svn_error_wrap_apr(err, NULL));
766 if (eb->base_checksum)
767 /* Text-delta-base-md5: */
768 SVN_ERR(svn_stream_printf(eb->stream, pool,
769 SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
770 ": %s\n",
771 eb->base_checksum));
773 /* Text-content-length: 39 */
774 SVN_ERR(svn_stream_printf(eb->stream, pool,
775 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
776 ": %lu\n",
777 (unsigned long)info->size));
779 /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
780 SVN_ERR(svn_stream_printf(eb->stream, pool,
781 SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
782 ": %s\n",
783 text_checksum));
786 /* Content-length: 1549 */
787 /* If both text and props are absent, skip this header */
788 if (eb->dump_props)
789 SVN_ERR(svn_stream_printf(eb->stream, pool,
790 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
791 ": %ld\n\n",
792 (unsigned long)info->size + eb->propstring->len));
793 else if (eb->dump_text)
794 SVN_ERR(svn_stream_printf(eb->stream, pool,
795 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
796 ": %ld\n\n",
797 (unsigned long)info->size));
799 /* Dump the props now */
800 if (eb->dump_props)
802 SVN_ERR(svn_stream_write(eb->stream, eb->propstring->data,
803 &(eb->propstring->len)));
805 /* Cleanup */
806 eb->dump_props = FALSE;
807 apr_hash_clear(eb->props);
808 apr_hash_clear(eb->deleted_props);
811 /* Dump the text */
812 if (eb->dump_text)
814 /* Seek to the beginning of the delta file, map it to a stream,
815 and copy the stream to eb->stream. Then close the stream and
816 truncate the file so we can reuse it for the next textdelta
817 application. Note that the file isn't created, opened or
818 closed here */
819 offset = 0;
820 SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
821 delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
822 SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
824 /* Cleanup */
825 SVN_ERR(svn_stream_close(delta_filestream));
826 SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
827 eb->dump_text = FALSE;
830 /* Write a couple of blank lines for matching output with `svnadmin
831 dump` */
832 SVN_ERR(svn_stream_printf(eb->stream, pool, "\n\n"));
834 return SVN_NO_ERROR;
837 static svn_error_t *
838 close_edit(void *edit_baton, apr_pool_t *pool)
840 return SVN_NO_ERROR;
843 svn_error_t *
844 get_dump_editor(const svn_delta_editor_t **editor,
845 void **edit_baton,
846 svn_stream_t *stream,
847 svn_cancel_func_t cancel_func,
848 void *cancel_baton,
849 apr_pool_t *pool)
851 struct dump_edit_baton *eb;
852 svn_delta_editor_t *de;
854 eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
855 eb->stream = stream;
857 /* Create a special per-revision pool */
858 eb->pool = svn_pool_create(pool);
860 /* Open a unique temporary file for all textdelta applications in
861 this edit session. The file is automatically closed and cleaned
862 up when the edit session is done. */
863 SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
864 NULL, svn_io_file_del_on_close, pool, pool));
866 de = svn_delta_default_editor(pool);
867 de->open_root = open_root;
868 de->delete_entry = delete_entry;
869 de->add_directory = add_directory;
870 de->open_directory = open_directory;
871 de->close_directory = close_directory;
872 de->change_dir_prop = change_dir_prop;
873 de->change_file_prop = change_file_prop;
874 de->apply_textdelta = apply_textdelta;
875 de->add_file = add_file;
876 de->open_file = open_file;
877 de->close_file = close_file;
878 de->close_edit = close_edit;
880 /* Set the edit_baton and editor. */
881 *edit_baton = eb;
882 *editor = de;
884 /* Wrap this editor in a cancellation editor. */
885 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
886 de, eb, editor, edit_baton, pool);