]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_repos/dump_editor.c
Merge llvm, clang, compiler-rt, libc++, libunwind, lld, lldb and openmp
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_repos / dump_editor.c
1 /*
2  *  dump_editor.c: A svn_delta_editor_t editor used to dump revisions.
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 #include "svn_repos.h"
25 #include "svn_hash.h"
26 #include "svn_pools.h"
27 #include "svn_path.h"
28 #include "svn_props.h"
29 #include "svn_subst.h"
30 #include "svn_dirent_uri.h"
31
32 #include "private/svn_repos_private.h"
33
34 #include <assert.h>
35
36 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
37
38 \f
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.
42  *
43  * Put the normalized props into NORMAL_PROPS, allocated in RESULT_POOL.
44  */
45 static svn_error_t *
46 normalize_props(apr_hash_t **normal_props,
47                 apr_hash_t *props,
48                 apr_pool_t *result_pool)
49 {
50   apr_hash_index_t *hi;
51   apr_pool_t *iterpool;
52
53   *normal_props = apr_hash_make(result_pool);
54
55   iterpool = svn_pool_create(result_pool);
56   for (hi = apr_hash_first(result_pool, props); hi; hi = apr_hash_next(hi))
57     {
58       const char *key = apr_hash_this_key(hi);
59       const svn_string_t *value = apr_hash_this_val(hi);
60
61       svn_pool_clear(iterpool);
62
63       SVN_ERR(svn_repos__normalize_prop(&value, NULL, key, value,
64                                         iterpool, iterpool));
65       svn_hash_sets(*normal_props, key, svn_string_dup(value, result_pool));
66     }
67   svn_pool_destroy(iterpool);
68
69   return SVN_NO_ERROR;
70 }
71
72 /* A directory baton used by all directory-related callback functions
73  * in the dump editor.  */
74 struct dir_baton
75 {
76   struct dump_edit_baton *eb;
77
78   /* Pool for per-directory allocations */
79   apr_pool_t *pool;
80
81   /* the path to this directory */
82   const char *repos_relpath; /* a relpath */
83
84   /* Copyfrom info for the node, if any. */
85   const char *copyfrom_path; /* a relpath */
86   svn_revnum_t copyfrom_rev;
87
88   /* Headers accumulated so far for this directory */
89   svn_repos__dumpfile_headers_t *headers;
90
91   /* Properties which were modified during change_dir_prop. */
92   apr_hash_t *props;
93
94   /* Properties which were deleted during change_dir_prop. */
95   apr_hash_t *deleted_props;
96
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;
102
103   /* Flag to trigger dumping props. */
104   svn_boolean_t dump_props;
105 };
106
107 /* A file baton used by all file-related callback functions in the dump
108  * editor */
109 struct file_baton
110 {
111   struct dump_edit_baton *eb;
112
113   /* Pool for per-file allocations */
114   apr_pool_t *pool;
115
116   /* the path to this file */
117   const char *repos_relpath; /* a relpath */
118
119   /* Properties which were modified during change_file_prop. */
120   apr_hash_t *props;
121
122   /* Properties which were deleted during change_file_prop. */
123   apr_hash_t *deleted_props;
124
125   /* The checksum of the file the delta is being applied to */
126   const char *base_checksum;
127
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;
132
133   /* The action associate with this node. */
134   enum svn_node_action action;
135
136   /* Flags to trigger dumping props and text. */
137   svn_boolean_t dump_text;
138   svn_boolean_t dump_props;
139 };
140
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;
145
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;
153
154   /* Pool for per-revision allocations */
155   apr_pool_t *pool;
156
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;
162
163   /* The baton of the directory node whose block of
164      dump stream data has not been fully completed; NULL if there's no
165      such item. */
166   struct dir_baton *pending_db;
167 };
168
169 /* Make a directory baton to represent the directory at PATH (relative
170  * to the EDIT_BATON).
171  *
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
175  * copy source.
176  *
177  * PB is the directory baton of this directory's parent, or NULL if
178  * this is the top-level directory of the edit.
179  *
180  * Perform all allocations in POOL.  */
181 static struct svn_error_t *
182 make_dir_baton(struct dir_baton **dbp,
183                const char *path,
184                const char *copyfrom_path,
185                svn_revnum_t copyfrom_rev,
186                void *edit_baton,
187                struct dir_baton *pb,
188                apr_pool_t *pool)
189 {
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;
193
194   /* Construct the full path of this node. */
195   if (pb)
196     SVN_ERR(svn_relpath_canonicalize_safe(&repos_relpath, NULL, path,
197                                           pool, pool));
198   else
199     repos_relpath = "";
200
201   /* Strip leading slash from copyfrom_path so that the path is
202      canonical and svn_relpath_join can be used */
203   if (copyfrom_path)
204     copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
205
206   new_db->eb = eb;
207   new_db->pool = pool;
208   new_db->repos_relpath = repos_relpath;
209   new_db->copyfrom_path = copyfrom_path
210                             ? svn_relpath_canonicalize(copyfrom_path, pool)
211                             : NULL;
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);
217
218   *dbp = new_db;
219   return SVN_NO_ERROR;
220 }
221
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,
229                 apr_pool_t *pool)
230 {
231   struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
232
233   new_fb->eb = pb->eb;
234   new_fb->pool = pool;
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;
242
243   return new_fb;
244 }
245
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.
248  */
249 static svn_error_t *
250 get_props_content(svn_repos__dumpfile_headers_t *headers,
251                   svn_stringbuf_t **content,
252                   apr_hash_t *props,
253                   apr_hash_t *deleted_props,
254                   apr_pool_t *result_pool,
255                   apr_pool_t *scratch_pool)
256 {
257   svn_stream_t *content_stream;
258   apr_hash_t *normal_props;
259
260   *content = svn_stringbuf_create_empty(result_pool);
261
262   content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
263
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",
267                                      scratch_pool));
268   SVN_ERR(svn_stream_close(content_stream));
269
270   /* Prop-delta: true */
271   svn_repos__dumpfile_header_push(
272     headers, SVN_REPOS_DUMPFILE_PROP_DELTA, "true");
273
274   return SVN_NO_ERROR;
275 }
276
277 /* A special case of dump_node(), for a delete record.
278  *
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.
283  */
284 static svn_error_t *
285 dump_node_delete(svn_stream_t *stream,
286                  const char *node_relpath,
287                  apr_pool_t *pool)
288 {
289   svn_repos__dumpfile_headers_t *headers
290     = svn_repos__dumpfile_headers_create(pool);
291
292   assert(svn_relpath_is_canonical(node_relpath));
293
294   /* Node-path: ... */
295   svn_repos__dumpfile_header_push(
296     headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
297
298   /* Node-action: delete */
299   svn_repos__dumpfile_header_push(
300     headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
301
302   SVN_ERR(svn_repos__dump_node_record(stream, headers,
303                                       NULL, FALSE, 0,  /* props & text */
304                                       FALSE /*content_length_always*/, pool));
305   return SVN_NO_ERROR;
306 }
307
308 /* Set *HEADERS_P to contain some headers for the node at PATH of type KIND.
309  *
310  * ACTION describes what is happening to the node (see enum
311  * svn_node_action).
312  *
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.
317  *
318  * Iff ACTION is svn_node_action_replace and IS_COPY, then first write a
319  * complete deletion record to the dump stream.
320  *
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
323  * header block.)
324  */
325 static svn_error_t *
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,
335           apr_pool_t *pool)
336 {
337   const char *node_relpath = repos_relpath;
338   svn_repos__dumpfile_headers_t *headers
339     = svn_repos__dumpfile_headers_create(pool);
340
341   assert(svn_relpath_is_canonical(repos_relpath));
342   assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
343   assert(! (db && fb));
344
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,
348                                     node_relpath, pool);
349
350   /* Node-path: ... */
351   svn_repos__dumpfile_header_push(
352     headers, SVN_REPOS_DUMPFILE_NODE_PATH, node_relpath);
353
354   /* Node-kind: "file" | "dir" */
355   if (fb)
356     svn_repos__dumpfile_header_push(
357       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "file");
358   else if (db)
359     svn_repos__dumpfile_header_push(
360       headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
361
362
363   /* Write the appropriate Node-action header */
364   switch (action)
365     {
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.
370
371          Node-action: change.  */
372       svn_repos__dumpfile_header_push(
373         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "change");
374       break;
375
376     case svn_node_action_delete:
377       /* Node-action: delete */
378       svn_repos__dumpfile_header_push(
379         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "delete");
380       break;
381
382     case svn_node_action_replace:
383       if (! is_copy)
384         {
385           /* Node-action: replace */
386           svn_repos__dumpfile_header_push(
387             headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "replace");
388
389           /* Wait for a change_*_prop to be called before dumping
390              anything */
391           if (fb)
392             fb->dump_props = TRUE;
393           else if (db)
394             db->dump_props = TRUE;
395           break;
396         }
397       else
398         {
399           /* More complex case: is_copy is true, and copyfrom_path/
400              copyfrom_rev are present: delete the original, and then re-add
401              it */
402           /* ### Why not write a 'replace' record? Don't know. */
403
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));
407
408           /* The remaining action is a non-replacing add-with-history */
409           /* action = svn_node_action_add; */
410         }
411       /* FALL THROUGH to 'add' */
412
413     case svn_node_action_add:
414       /* Node-action: add */
415       svn_repos__dumpfile_header_push(
416         headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
417
418       if (is_copy)
419         {
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);
425         }
426       else
427         {
428           /* fb->dump_props (for files) is handled in close_file()
429              which is called immediately.
430
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():
435
436                - add_directory()
437                - open_directory()
438                - delete_entry()
439                - close_directory()
440                - add_file()
441                - open_file()
442
443              change_dir_prop() is a special case. */
444           if (fb)
445             fb->dump_props = TRUE;
446           else if (db)
447             db->dump_props = TRUE;
448         }
449
450       break;
451     }
452
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;
457   return SVN_NO_ERROR;
458 }
459
460 static svn_error_t *
461 dump_mkdir(struct dump_edit_baton *eb,
462            const char *repos_relpath,
463            apr_pool_t *pool)
464 {
465   svn_stringbuf_t *prop_content;
466   svn_repos__dumpfile_headers_t *headers
467     = svn_repos__dumpfile_headers_create(pool);
468
469   /* Node-path: ... */
470   svn_repos__dumpfile_header_push(
471     headers, SVN_REPOS_DUMPFILE_NODE_PATH, repos_relpath);
472
473   /* Node-kind: dir */
474   svn_repos__dumpfile_header_push(
475     headers, SVN_REPOS_DUMPFILE_NODE_KIND, "dir");
476
477   /* Node-action: add */
478   svn_repos__dumpfile_header_push(
479     headers, SVN_REPOS_DUMPFILE_NODE_ACTION, "add");
480
481   /* Dump the (empty) property block. */
482   SVN_ERR(get_props_content(headers, &prop_content,
483                             apr_hash_make(pool), apr_hash_make(pool),
484                             pool, pool));
485   SVN_ERR(svn_repos__dump_node_record(eb->stream, headers, prop_content,
486                                       FALSE, 0, FALSE /*content_length_always*/,
487                                       pool));
488
489   /* Newlines to tie it all off. */
490   SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
491
492   return SVN_NO_ERROR;
493 }
494
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 */
497 static svn_error_t *
498 dump_pending_dir(struct dump_edit_baton *eb,
499                  apr_pool_t *scratch_pool)
500 {
501   struct dir_baton *db = eb->pending_db;
502   svn_stringbuf_t *prop_content = NULL;
503
504   if (! db)
505     return SVN_NO_ERROR;
506
507   /* Some pending properties to dump? */
508   if (db->dump_props)
509     {
510       SVN_ERR(get_props_content(db->headers, &prop_content,
511                                 db->props, db->deleted_props,
512                                 scratch_pool, scratch_pool));
513     }
514   SVN_ERR(svn_repos__dump_node_record(eb->stream, db->headers, prop_content,
515                                       FALSE, 0, FALSE /*content_length_always*/,
516                                       scratch_pool));
517
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"));
521
522   if (db->dump_props)
523     {
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;
528     }
529
530   /* Anything that was pending is pending no longer. */
531   eb->pending_db = NULL;
532
533   return SVN_NO_ERROR;
534 }
535
536
537 \f
538 /*** Editor Function Implementations ***/
539
540 static svn_error_t *
541 open_root(void *edit_baton,
542           svn_revnum_t base_revision,
543           apr_pool_t *pool,
544           void **root_baton)
545 {
546   struct dump_edit_baton *eb = edit_baton;
547   struct dir_baton *new_db = NULL;
548
549   /* Clear the per-revision pool after each revision */
550   svn_pool_clear(eb->pool);
551
552   if (eb->update_anchor_relpath)
553     {
554       int i;
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);
559
560       while (! svn_path_is_empty(parent_path))
561         {
562           APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
563           parent_path = svn_relpath_dirname(parent_path, pool);
564         }
565
566       for (i = dirs_to_add->nelts; i; --i)
567         {
568           const char *dir_to_add =
569             APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
570
571           svn_pool_clear(iterpool);
572
573           /* For parents of the source directory, we just manufacture
574              the adds ourselves. */
575           if (i > 1)
576             {
577               SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
578             }
579           else
580             {
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));
589
590               /* Remember that we've started but not yet finished
591                  handling this directory. */
592               eb->pending_db = new_db;
593             }
594         }
595       svn_pool_destroy(iterpool);
596     }
597
598   if (! new_db)
599     {
600       SVN_ERR(make_dir_baton(&new_db, NULL, NULL, SVN_INVALID_REVNUM,
601                              edit_baton, NULL, pool));
602     }
603
604   *root_baton = new_db;
605   return SVN_NO_ERROR;
606 }
607
608 static svn_error_t *
609 delete_entry(const char *path,
610              svn_revnum_t revision,
611              void *parent_baton,
612              apr_pool_t *pool)
613 {
614   struct dir_baton *pb = parent_baton;
615
616   SVN_ERR(dump_pending_dir(pb->eb, pool));
617
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);
623
624   return SVN_NO_ERROR;
625 }
626
627 static svn_error_t *
628 add_directory(const char *path,
629               void *parent_baton,
630               const char *copyfrom_path,
631               svn_revnum_t copyfrom_rev,
632               apr_pool_t *pool,
633               void **child_baton)
634 {
635   struct dir_baton *pb = parent_baton;
636   void *was_deleted;
637   struct dir_baton *new_db;
638   svn_boolean_t is_copy;
639
640   SVN_ERR(dump_pending_dir(pb->eb, pool));
641
642   SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev, pb->eb,
643                          pb, pb->pool));
644
645   /* This might be a replacement -- is the path already deleted? */
646   was_deleted = svn_hash_gets(pb->deleted_entries, path);
647
648   /* Detect an add-with-history */
649   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
650
651   /* Dump the node */
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,
655                     is_copy,
656                     is_copy ? new_db->copyfrom_path : NULL,
657                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
658                     pool));
659
660   if (was_deleted)
661     /* Delete the path, it's now been dumped */
662     svn_hash_sets(pb->deleted_entries, path, NULL);
663
664   /* Remember that we've started, but not yet finished handling this
665      directory. */
666   pb->eb->pending_db = new_db;
667
668   *child_baton = new_db;
669   return SVN_NO_ERROR;
670 }
671
672 static svn_error_t *
673 open_directory(const char *path,
674                void *parent_baton,
675                svn_revnum_t base_revision,
676                apr_pool_t *pool,
677                void **child_baton)
678 {
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;
683
684   SVN_ERR(dump_pending_dir(pb->eb, pool));
685
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))
689     {
690       copyfrom_path = svn_relpath_join(pb->copyfrom_path,
691                                        svn_relpath_basename(path, NULL),
692                                        pb->pool);
693       copyfrom_rev = pb->copyfrom_rev;
694     }
695
696   SVN_ERR(make_dir_baton(&new_db, path, copyfrom_path, copyfrom_rev,
697                          pb->eb, pb, pb->pool));
698
699   *child_baton = new_db;
700   return SVN_NO_ERROR;
701 }
702
703 static svn_error_t *
704 close_directory(void *dir_baton,
705                 apr_pool_t *pool)
706 {
707   struct dir_baton *db = dir_baton;
708   apr_hash_index_t *hi;
709   svn_boolean_t this_pending;
710
711   /* Remember if this directory is the one currently pending. */
712   this_pending = (db->eb->pending_db == db);
713
714   SVN_ERR(dump_pending_dir(db->eb, pool));
715
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.
719
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
724      directory. */
725   if ((! this_pending) && (db->dump_props))
726     {
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));
733     }
734
735   /* Dump the deleted directory entries */
736   for (hi = apr_hash_first(pool, db->deleted_entries); hi;
737        hi = apr_hash_next(hi))
738     {
739       const char *path = apr_hash_this_key(hi);
740
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"));
744     }
745
746   /* ### should be unnecessary */
747   apr_hash_clear(db->deleted_entries);
748
749   return SVN_NO_ERROR;
750 }
751
752 static svn_error_t *
753 add_file(const char *path,
754          void *parent_baton,
755          const char *copyfrom_path,
756          svn_revnum_t copyfrom_rev,
757          apr_pool_t *pool,
758          void **file_baton)
759 {
760   struct dir_baton *pb = parent_baton;
761   struct file_baton *fb;
762   void *was_deleted;
763
764   SVN_ERR(dump_pending_dir(pb->eb, pool));
765
766   /* Make the file baton. */
767   fb = make_file_baton(path, pb, pool);
768
769   /* This might be a replacement -- is the path already deleted? */
770   was_deleted = svn_hash_gets(pb->deleted_entries, path);
771
772   /* Detect add-with-history. */
773   if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
774     {
775       fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
776       fb->copyfrom_rev = copyfrom_rev;
777       fb->is_copy = TRUE;
778     }
779   fb->action = was_deleted ? svn_node_action_replace : svn_node_action_add;
780
781   /* Delete the path, it's now been dumped. */
782   if (was_deleted)
783     svn_hash_sets(pb->deleted_entries, path, NULL);
784
785   *file_baton = fb;
786   return SVN_NO_ERROR;
787 }
788
789 static svn_error_t *
790 open_file(const char *path,
791           void *parent_baton,
792           svn_revnum_t ancestor_revision,
793           apr_pool_t *pool,
794           void **file_baton)
795 {
796   struct dir_baton *pb = parent_baton;
797   struct file_baton *fb;
798
799   SVN_ERR(dump_pending_dir(pb->eb, pool));
800
801   /* Make the file baton. */
802   fb = make_file_baton(path, pb, pool);
803
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))
807     {
808       fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
809                                            svn_relpath_basename(path, NULL),
810                                            pb->pool);
811       fb->copyfrom_rev = pb->copyfrom_rev;
812     }
813
814   *file_baton = fb;
815   return SVN_NO_ERROR;
816 }
817
818 static svn_error_t *
819 change_dir_prop(void *parent_baton,
820                 const char *name,
821                 const svn_string_t *value,
822                 apr_pool_t *pool)
823 {
824   struct dir_baton *db = parent_baton;
825   svn_boolean_t this_pending;
826
827   /* This directory is not pending, but something else is, so handle
828      the "something else".  */
829   this_pending = (db->eb->pending_db == db);
830   if (! this_pending)
831     SVN_ERR(dump_pending_dir(db->eb, pool));
832
833   if (svn_property_kind2(name) != svn_prop_regular_kind)
834     return SVN_NO_ERROR;
835
836   if (value)
837     svn_hash_sets(db->props,
838                   apr_pstrdup(db->pool, name),
839                   svn_string_dup(value, db->pool));
840   else
841     svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
842
843   /* Make sure we eventually output the props */
844   db->dump_props = TRUE;
845
846   return SVN_NO_ERROR;
847 }
848
849 static svn_error_t *
850 change_file_prop(void *file_baton,
851                  const char *name,
852                  const svn_string_t *value,
853                  apr_pool_t *pool)
854 {
855   struct file_baton *fb = file_baton;
856
857   if (svn_property_kind2(name) != svn_prop_regular_kind)
858     return SVN_NO_ERROR;
859
860   if (value)
861     svn_hash_sets(fb->props,
862                   apr_pstrdup(fb->pool, name),
863                   svn_string_dup(value, fb->pool));
864   else
865     svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
866
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;
871
872   return SVN_NO_ERROR;
873 }
874
875 static svn_error_t *
876 apply_textdelta(void *file_baton, const char *base_checksum,
877                 apr_pool_t *pool,
878                 svn_txdelta_window_handler_t *handler,
879                 void **handler_baton)
880 {
881   struct file_baton *fb = file_baton;
882   struct dump_edit_baton *eb = fb->eb;
883   svn_stream_t *delta_filestream;
884
885   /* Use a temporary file to measure the Text-content-length */
886   delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
887
888   /* Prepare to write the delta to the delta_filestream */
889   svn_txdelta_to_svndiff3(handler, handler_baton,
890                           delta_filestream, 0,
891                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
892
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);
896
897   return SVN_NO_ERROR;
898 }
899
900 static svn_error_t *
901 close_file(void *file_baton,
902            const char *text_checksum,
903            apr_pool_t *pool)
904 {
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;
910
911   SVN_ERR(dump_pending_dir(eb, pool));
912
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));
917
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). */
921   if (fb->dump_props)
922     {
923       SVN_ERR(get_props_content(headers, &propstring,
924                                 fb->props, fb->deleted_props,
925                                 pool, pool));
926     }
927
928   /* Dump the text headers */
929   if (fb->dump_text)
930     {
931       /* Text-delta: true */
932       svn_repos__dumpfile_header_push(
933         headers, SVN_REPOS_DUMPFILE_TEXT_DELTA, "true");
934
935       SVN_ERR(svn_io_file_size_get(&text_content_length, eb->delta_file,
936                                    pool));
937
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);
942
943       /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
944       svn_repos__dumpfile_header_push(
945         headers, SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5, text_checksum);
946     }
947
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*/,
952                                       pool));
953
954   if (fb->dump_props)
955     {
956       /* Cleanup */
957       fb->dump_props = FALSE;
958       apr_hash_clear(fb->props);
959       apr_hash_clear(fb->deleted_props);
960     }
961
962   /* Dump the text */
963   if (fb->dump_text)
964     {
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
969          closed here */
970       svn_stream_t *delta_filestream;
971       apr_off_t offset = 0;
972
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),
977                                NULL, NULL, pool));
978
979       /* Cleanup */
980       SVN_ERR(svn_stream_close(delta_filestream));
981       SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
982     }
983
984   /* Write a couple of blank lines for matching output with `svnadmin
985      dump` */
986   SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
987
988   return SVN_NO_ERROR;
989 }
990
991 static svn_error_t *
992 close_edit(void *edit_baton, apr_pool_t *pool)
993 {
994   return SVN_NO_ERROR;
995 }
996
997 svn_error_t *
998 svn_repos__get_dump_editor(const svn_delta_editor_t **editor,
999                            void **edit_baton,
1000                            svn_stream_t *stream,
1001                            const char *update_anchor_relpath,
1002                            apr_pool_t *pool)
1003 {
1004   struct dump_edit_baton *eb;
1005   svn_delta_editor_t *de;
1006
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;
1011
1012   /* Create a special per-revision pool */
1013   eb->pool = svn_pool_create(pool);
1014
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));
1020
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;
1034
1035   /* Set the edit_baton and editor. */
1036   *edit_baton = eb;
1037   *editor = de;
1038
1039   return SVN_NO_ERROR;
1040 }