]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/svnrdump/dump_editor.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / svnrdump / dump_editor.c
1 /*
2  *  dump_editor.c: The svn_delta_editor_t editor used by svnrdump to
3  *  dump revisions.
4  *
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
13  *
14  *      http://www.apache.org/licenses/LICENSE-2.0
15  *
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  * ====================================================================
23  */
24
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"
32
33 #include "private/svn_subr_private.h"
34 #include "private/svn_dep_compat.h"
35 #include "private/svn_editor.h"
36
37 #include "svnrdump.h"
38 #include <assert.h>
39
40 #define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
41
42 #if 0
43 #define LDR_DBG(x) SVN_DBG(x)
44 #else
45 #define LDR_DBG(x) while(0)
46 #endif
47
48 /* A directory baton used by all directory-related callback functions
49  * in the dump editor.  */
50 struct dir_baton
51 {
52   struct dump_edit_baton *eb;
53   struct dir_baton *parent_dir_baton;
54
55   /* Pool for per-directory allocations */
56   apr_pool_t *pool;
57
58   /* is this directory a new addition to this revision? */
59   svn_boolean_t added;
60
61   /* has this directory been written to the output stream? */
62   svn_boolean_t written_out;
63
64   /* the path to this directory */
65   const char *repos_relpath; /* a relpath */
66
67   /* Copyfrom info for the node, if any. */
68   const char *copyfrom_path; /* a relpath */
69   svn_revnum_t copyfrom_rev;
70
71   /* Properties which were modified during change_dir_prop. */
72   apr_hash_t *props;
73
74   /* Properties which were deleted during change_dir_prop. */
75   apr_hash_t *deleted_props;
76
77   /* Hash of paths that need to be deleted, though some -might- be
78      replaced.  Maps const char * paths to this dir_baton. Note that
79      they're full paths, because that's what the editor driver gives
80      us, although they're all really within this directory. */
81   apr_hash_t *deleted_entries;
82
83   /* Flags to trigger dumping props and record termination newlines. */
84   svn_boolean_t dump_props;
85   svn_boolean_t dump_newlines;
86 };
87
88 /* A file baton used by all file-related callback functions in the dump
89  * editor */
90 struct file_baton
91 {
92   struct dump_edit_baton *eb;
93   struct dir_baton *parent_dir_baton;
94
95   /* Pool for per-file allocations */
96   apr_pool_t *pool;
97
98   /* the path to this file */
99   const char *repos_relpath; /* a relpath */
100
101   /* Properties which were modified during change_file_prop. */
102   apr_hash_t *props;
103
104   /* Properties which were deleted during change_file_prop. */
105   apr_hash_t *deleted_props;
106
107   /* The checksum of the file the delta is being applied to */
108   const char *base_checksum;
109
110   /* Copy state and source information (if any). */
111   svn_boolean_t is_copy;
112   const char *copyfrom_path;
113   svn_revnum_t copyfrom_rev;
114
115   /* The action associate with this node. */
116   enum svn_node_action action;
117
118   /* Flags to trigger dumping props and text. */
119   svn_boolean_t dump_text;
120   svn_boolean_t dump_props;
121 };
122
123 /* A handler baton to be used in window_handler().  */
124 struct handler_baton
125 {
126   svn_txdelta_window_handler_t apply_handler;
127   void *apply_baton;
128 };
129
130 /* The baton used by the dump editor. */
131 struct dump_edit_baton {
132   /* The output stream we write the dumpfile to */
133   svn_stream_t *stream;
134
135   /* A backdoor ra session to fetch additional information during the edit. */
136   svn_ra_session_t *ra_session;
137
138   /* The repository relpath of the anchor of the editor when driven
139      via the RA update mechanism; NULL otherwise. (When the editor is
140      driven via the RA "replay" mechanism instead, the editor is
141      always anchored at the repository, we don't need to prepend an
142      anchor path to the dumped node paths, and open_root() doesn't
143      need to manufacture directory additions.)  */
144   const char *update_anchor_relpath;
145
146   /* Pool for per-revision allocations */
147   apr_pool_t *pool;
148
149   /* Temporary file used for textdelta application along with its
150      absolute path; these two variables should be allocated in the
151      per-edit-session pool */
152   const char *delta_abspath;
153   apr_file_t *delta_file;
154
155   /* The revision we're currently dumping. */
156   svn_revnum_t current_revision;
157
158   /* The kind (file or directory) and baton of the item whose block of
159      dump stream data has not been fully completed; NULL if there's no
160      such item. */
161   svn_node_kind_t pending_kind;
162   void *pending_baton;
163 };
164
165 /* Make a directory baton to represent the directory at PATH (relative
166  * to the EDIT_BATON).
167  *
168  * COPYFROM_PATH/COPYFROM_REV are the path/revision against which this
169  * directory should be compared for changes. If the copyfrom
170  * information is valid, the directory will be compared against its
171  * copy source.
172  *
173  * PB is the directory baton of this directory's parent, or NULL if
174  * this is the top-level directory of the edit.  ADDED indicates if
175  * this directory is newly added in this revision.  Perform all
176  * allocations in POOL.  */
177 static struct dir_baton *
178 make_dir_baton(const char *path,
179                const char *copyfrom_path,
180                svn_revnum_t copyfrom_rev,
181                void *edit_baton,
182                struct dir_baton *pb,
183                svn_boolean_t added,
184                apr_pool_t *pool)
185 {
186   struct dump_edit_baton *eb = edit_baton;
187   struct dir_baton *new_db = apr_pcalloc(pool, sizeof(*new_db));
188   const char *repos_relpath;
189
190   /* Construct the full path of this node. */
191   if (pb)
192     repos_relpath = svn_relpath_canonicalize(path, pool);
193   else
194     repos_relpath = "";
195
196   /* Strip leading slash from copyfrom_path so that the path is
197      canonical and svn_relpath_join can be used */
198   if (copyfrom_path)
199     copyfrom_path = svn_relpath_canonicalize(copyfrom_path, pool);
200
201   new_db->eb = eb;
202   new_db->parent_dir_baton = pb;
203   new_db->pool = pool;
204   new_db->repos_relpath = repos_relpath;
205   new_db->copyfrom_path = copyfrom_path
206                             ? svn_relpath_canonicalize(copyfrom_path, pool)
207                             : NULL;
208   new_db->copyfrom_rev = copyfrom_rev;
209   new_db->added = added;
210   new_db->written_out = FALSE;
211   new_db->props = apr_hash_make(pool);
212   new_db->deleted_props = apr_hash_make(pool);
213   new_db->deleted_entries = apr_hash_make(pool);
214
215   return new_db;
216 }
217
218 /* Make a file baton to represent the directory at PATH (relative to
219  * PB->eb).  PB is the directory baton of this directory's parent, or
220  * NULL if this is the top-level directory of the edit.  Perform all
221  * allocations in POOL.  */
222 static struct file_baton *
223 make_file_baton(const char *path,
224                 struct dir_baton *pb,
225                 apr_pool_t *pool)
226 {
227   struct file_baton *new_fb = apr_pcalloc(pool, sizeof(*new_fb));
228
229   new_fb->eb = pb->eb;
230   new_fb->parent_dir_baton = pb;
231   new_fb->pool = pool;
232   new_fb->repos_relpath = svn_relpath_canonicalize(path, pool);
233   new_fb->props = apr_hash_make(pool);
234   new_fb->deleted_props = apr_hash_make(pool);
235   new_fb->is_copy = FALSE;
236   new_fb->copyfrom_path = NULL;
237   new_fb->copyfrom_rev = SVN_INVALID_REVNUM;
238   new_fb->action = svn_node_action_change;
239
240   return new_fb;
241 }
242
243 /* Return in *HEADER and *CONTENT the headers and content for PROPS. */
244 static svn_error_t *
245 get_props_content(svn_stringbuf_t **header,
246                   svn_stringbuf_t **content,
247                   apr_hash_t *props,
248                   apr_hash_t *deleted_props,
249                   apr_pool_t *result_pool,
250                   apr_pool_t *scratch_pool)
251 {
252   svn_stream_t *content_stream;
253   apr_hash_t *normal_props;
254   const char *buf;
255
256   *content = svn_stringbuf_create_empty(result_pool);
257   *header = svn_stringbuf_create_empty(result_pool);
258
259   content_stream = svn_stream_from_stringbuf(*content, scratch_pool);
260
261   SVN_ERR(svn_rdump__normalize_props(&normal_props, props, scratch_pool));
262   SVN_ERR(svn_hash_write_incremental(normal_props, deleted_props,
263                                      content_stream, "PROPS-END",
264                                      scratch_pool));
265   SVN_ERR(svn_stream_close(content_stream));
266
267   /* Prop-delta: true */
268   *header = svn_stringbuf_createf(result_pool, SVN_REPOS_DUMPFILE_PROP_DELTA
269                                   ": true\n");
270
271   /* Prop-content-length: 193 */
272   buf = apr_psprintf(scratch_pool, SVN_REPOS_DUMPFILE_PROP_CONTENT_LENGTH
273                      ": %" APR_SIZE_T_FMT "\n", (*content)->len);
274   svn_stringbuf_appendcstr(*header, buf);
275
276   return SVN_NO_ERROR;
277 }
278
279 /* Extract and dump properties stored in PROPS and property deletions
280  * stored in DELETED_PROPS. If TRIGGER_VAR is not NULL, it is set to
281  * FALSE.
282  *
283  * If PROPSTRING is non-NULL, set *PROPSTRING to a string containing
284  * the content block of the property changes; otherwise, dump that to
285  * the stream, too.
286  */
287 static svn_error_t *
288 do_dump_props(svn_stringbuf_t **propstring,
289               svn_stream_t *stream,
290               apr_hash_t *props,
291               apr_hash_t *deleted_props,
292               svn_boolean_t *trigger_var,
293               apr_pool_t *result_pool,
294               apr_pool_t *scratch_pool)
295 {
296   svn_stringbuf_t *header;
297   svn_stringbuf_t *content;
298   apr_size_t len;
299
300   if (trigger_var && !*trigger_var)
301     return SVN_NO_ERROR;
302
303   SVN_ERR(get_props_content(&header, &content, props, deleted_props,
304                             result_pool, scratch_pool));
305   len = header->len;
306   SVN_ERR(svn_stream_write(stream, header->data, &len));
307
308   if (propstring)
309     {
310       *propstring = content;
311     }
312   else
313     {
314       /* Content-length: 14 */
315       SVN_ERR(svn_stream_printf(stream, scratch_pool,
316                                 SVN_REPOS_DUMPFILE_CONTENT_LENGTH
317                                 ": %" APR_SIZE_T_FMT "\n\n",
318                                 content->len));
319
320       len = content->len;
321       SVN_ERR(svn_stream_write(stream, content->data, &len));
322
323       /* No text is going to be dumped. Write a couple of newlines and
324          wait for the next node/ revision. */
325       SVN_ERR(svn_stream_puts(stream, "\n\n"));
326
327       /* Cleanup so that data is never dumped twice. */
328       apr_hash_clear(props);
329       apr_hash_clear(deleted_props);
330       if (trigger_var)
331         *trigger_var = FALSE;
332     }
333
334   return SVN_NO_ERROR;
335 }
336
337 static svn_error_t *
338 do_dump_newlines(struct dump_edit_baton *eb,
339                  svn_boolean_t *trigger_var,
340                  apr_pool_t *pool)
341 {
342   if (trigger_var && *trigger_var)
343     {
344       SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
345       *trigger_var = FALSE;
346     }
347   return SVN_NO_ERROR;
348 }
349
350 /*
351  * Write out a node record for PATH of type KIND under EB->FS_ROOT.
352  * ACTION describes what is happening to the node (see enum
353  * svn_node_action). Write record to writable EB->STREAM, using
354  * EB->BUFFER to write in chunks.
355  *
356  * If the node was itself copied, IS_COPY is TRUE and the
357  * path/revision of the copy source are in COPYFROM_PATH/COPYFROM_REV.
358  * If IS_COPY is FALSE, yet COPYFROM_PATH/COPYFROM_REV are valid, this
359  * node is part of a copied subtree.
360  */
361 static svn_error_t *
362 dump_node(struct dump_edit_baton *eb,
363           const char *repos_relpath,
364           struct dir_baton *db,
365           struct file_baton *fb,
366           enum svn_node_action action,
367           svn_boolean_t is_copy,
368           const char *copyfrom_path,
369           svn_revnum_t copyfrom_rev,
370           apr_pool_t *pool)
371 {
372   const char *node_relpath = repos_relpath;
373
374   assert(svn_relpath_is_canonical(repos_relpath));
375   assert(!copyfrom_path || svn_relpath_is_canonical(copyfrom_path));
376   assert(! (db && fb));
377
378   /* Add the edit root relpath prefix if necessary. */
379   if (eb->update_anchor_relpath)
380     node_relpath = svn_relpath_join(eb->update_anchor_relpath,
381                                     node_relpath, pool);
382
383   /* Node-path: ... */
384   SVN_ERR(svn_stream_printf(eb->stream, pool,
385                             SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
386                             node_relpath));
387
388   /* Node-kind: "file" | "dir" */
389   if (fb)
390     SVN_ERR(svn_stream_printf(eb->stream, pool,
391                               SVN_REPOS_DUMPFILE_NODE_KIND ": file\n"));
392   else if (db)
393     SVN_ERR(svn_stream_printf(eb->stream, pool,
394                               SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
395
396
397   /* Write the appropriate Node-action header */
398   switch (action)
399     {
400     case svn_node_action_change:
401       /* We are here after a change_file_prop or change_dir_prop. They
402          set up whatever dump_props they needed to- nothing to
403          do here but print node action information.
404
405          Node-action: change.  */
406       SVN_ERR(svn_stream_puts(eb->stream,
407                               SVN_REPOS_DUMPFILE_NODE_ACTION ": change\n"));
408       break;
409
410     case svn_node_action_replace:
411       if (is_copy)
412         {
413           /* Delete the original, and then re-add the replacement as a
414              copy using recursive calls into this function. */
415           SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_delete,
416                             FALSE, NULL, SVN_INVALID_REVNUM, pool));
417           SVN_ERR(dump_node(eb, repos_relpath, db, fb, svn_node_action_add,
418                             is_copy, copyfrom_path, copyfrom_rev, pool));
419         }
420       else
421         {
422           /* Node-action: replace */
423           SVN_ERR(svn_stream_puts(eb->stream,
424                                   SVN_REPOS_DUMPFILE_NODE_ACTION
425                                   ": replace\n"));
426
427           /* Wait for a change_*_prop to be called before dumping
428              anything */
429           if (fb)
430             fb->dump_props = TRUE;
431           else if (db)
432             db->dump_props = TRUE;
433         }
434       break;
435
436     case svn_node_action_delete:
437       /* Node-action: delete */
438       SVN_ERR(svn_stream_puts(eb->stream,
439                               SVN_REPOS_DUMPFILE_NODE_ACTION ": delete\n"));
440
441       /* We can leave this routine quietly now. Nothing more to do-
442          print a couple of newlines because we're not dumping props or
443          text. */
444       SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
445
446       break;
447
448     case svn_node_action_add:
449       /* Node-action: add */
450       SVN_ERR(svn_stream_puts(eb->stream,
451                               SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
452
453       if (is_copy)
454         {
455           /* Node-copyfrom-rev / Node-copyfrom-path */
456           SVN_ERR(svn_stream_printf(eb->stream, pool,
457                                     SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV
458                                     ": %ld\n"
459                                     SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH
460                                     ": %s\n",
461                                     copyfrom_rev, copyfrom_path));
462
463           /* Ugly hack: If a directory was copied from a previous
464              revision, nothing like close_file() will be called to write two
465              blank lines. If change_dir_prop() is called, props are dumped
466              (along with the necessary PROPS-END\n\n and we're good. So
467              set DUMP_NEWLINES here to print the newlines unless
468              change_dir_prop() is called next otherwise the `svnadmin load`
469              parser will fail.  */
470           if (db)
471             db->dump_newlines = TRUE;
472         }
473       else
474         {
475           /* fb->dump_props (for files) is handled in close_file()
476              which is called immediately.
477
478              However, directories are not closed until all the work
479              inside them has been done; db->dump_props (for directories)
480              is handled (via dump_pending()) in all the functions that
481              can possibly be called after add_directory():
482
483                - add_directory()
484                - open_directory()
485                - delete_entry()
486                - close_directory()
487                - add_file()
488                - open_file()
489
490              change_dir_prop() is a special case. */
491           if (fb)
492             fb->dump_props = TRUE;
493           else if (db)
494             db->dump_props = TRUE;
495         }
496
497       break;
498     }
499   return SVN_NO_ERROR;
500 }
501
502 static svn_error_t *
503 dump_mkdir(struct dump_edit_baton *eb,
504            const char *repos_relpath,
505            apr_pool_t *pool)
506 {
507   svn_stringbuf_t *prop_header, *prop_content;
508   apr_size_t len;
509   const char *buf;
510
511   /* Node-path: ... */
512   SVN_ERR(svn_stream_printf(eb->stream, pool,
513                             SVN_REPOS_DUMPFILE_NODE_PATH ": %s\n",
514                             repos_relpath));
515
516   /* Node-kind: dir */
517   SVN_ERR(svn_stream_printf(eb->stream, pool,
518                             SVN_REPOS_DUMPFILE_NODE_KIND ": dir\n"));
519
520   /* Node-action: add */
521   SVN_ERR(svn_stream_puts(eb->stream,
522                           SVN_REPOS_DUMPFILE_NODE_ACTION ": add\n"));
523
524   /* Dump the (empty) property block. */
525   SVN_ERR(get_props_content(&prop_header, &prop_content,
526                             apr_hash_make(pool), apr_hash_make(pool),
527                             pool, pool));
528   len = prop_header->len;
529   SVN_ERR(svn_stream_write(eb->stream, prop_header->data, &len));
530   len = prop_content->len;
531   buf = apr_psprintf(pool, SVN_REPOS_DUMPFILE_CONTENT_LENGTH
532                      ": %" APR_SIZE_T_FMT "\n", len);
533   SVN_ERR(svn_stream_puts(eb->stream, buf));
534   SVN_ERR(svn_stream_puts(eb->stream, "\n"));
535   SVN_ERR(svn_stream_write(eb->stream, prop_content->data, &len));
536
537   /* Newlines to tie it all off. */
538   SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
539
540   return SVN_NO_ERROR;
541 }
542
543 /* Dump pending items from the specified node, to allow starting the dump
544    of a child node */
545 static svn_error_t *
546 dump_pending(struct dump_edit_baton *eb,
547              apr_pool_t *scratch_pool)
548 {
549   if (! eb->pending_baton)
550     return SVN_NO_ERROR;
551
552   if (eb->pending_kind == svn_node_dir)
553     {
554       struct dir_baton *db = eb->pending_baton;
555
556       /* Some pending properties to dump? */
557       SVN_ERR(do_dump_props(NULL, eb->stream, db->props, db->deleted_props,
558                             &(db->dump_props), db->pool, scratch_pool));
559
560       /* Some pending newlines to dump? */
561       SVN_ERR(do_dump_newlines(eb, &(db->dump_newlines), scratch_pool));
562     }
563   else if (eb->pending_kind == svn_node_file)
564     {
565       struct file_baton *fb = eb->pending_baton;
566
567       /* Some pending properties to dump? */
568       SVN_ERR(do_dump_props(NULL, eb->stream, fb->props, fb->deleted_props,
569                             &(fb->dump_props), fb->pool, scratch_pool));
570     }
571   else
572     abort();
573
574   /* Anything that was pending is pending no longer. */
575   eb->pending_baton = NULL;
576   eb->pending_kind = svn_node_none;
577
578   return SVN_NO_ERROR;
579 }
580
581
582 \f
583 /*** Editor Function Implementations ***/
584
585 static svn_error_t *
586 open_root(void *edit_baton,
587           svn_revnum_t base_revision,
588           apr_pool_t *pool,
589           void **root_baton)
590 {
591   struct dump_edit_baton *eb = edit_baton;
592   struct dir_baton *new_db = NULL;
593
594   /* Clear the per-revision pool after each revision */
595   svn_pool_clear(eb->pool);
596
597   LDR_DBG(("open_root %p\n", *root_baton));
598
599   if (eb->update_anchor_relpath)
600     {
601       int i;
602       const char *parent_path = eb->update_anchor_relpath;
603       apr_array_header_t *dirs_to_add =
604         apr_array_make(pool, 4, sizeof(const char *));
605       apr_pool_t *iterpool = svn_pool_create(pool);
606
607       while (! svn_path_is_empty(parent_path))
608         {
609           APR_ARRAY_PUSH(dirs_to_add, const char *) = parent_path;
610           parent_path = svn_relpath_dirname(parent_path, pool);
611         }
612
613       for (i = dirs_to_add->nelts; i; --i)
614         {
615           const char *dir_to_add =
616             APR_ARRAY_IDX(dirs_to_add, i - 1, const char *);
617
618           svn_pool_clear(iterpool);
619
620           /* For parents of the source directory, we just manufacture
621              the adds ourselves. */
622           if (i > 1)
623             {
624               SVN_ERR(dump_mkdir(eb, dir_to_add, iterpool));
625             }
626           else
627             {
628               /* ... but for the source directory itself, we'll defer
629                  to letting the typical plumbing handle this task. */
630               new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
631                                       edit_baton, NULL, TRUE, pool);
632               SVN_ERR(dump_node(eb, new_db->repos_relpath, new_db,
633                                 NULL, svn_node_action_add, FALSE,
634                                 NULL, SVN_INVALID_REVNUM, pool));
635
636               /* Remember that we've started but not yet finished
637                  handling this directory. */
638               new_db->written_out = TRUE;
639               eb->pending_baton = new_db;
640               eb->pending_kind = svn_node_dir;
641             }
642         }
643       svn_pool_destroy(iterpool);
644     }
645
646   if (! new_db)
647     {
648       new_db = make_dir_baton(NULL, NULL, SVN_INVALID_REVNUM,
649                               edit_baton, NULL, FALSE, pool);
650     }
651
652   *root_baton = new_db;
653   return SVN_NO_ERROR;
654 }
655
656 static svn_error_t *
657 delete_entry(const char *path,
658              svn_revnum_t revision,
659              void *parent_baton,
660              apr_pool_t *pool)
661 {
662   struct dir_baton *pb = parent_baton;
663
664   LDR_DBG(("delete_entry %s\n", path));
665
666   SVN_ERR(dump_pending(pb->eb, pool));
667
668   /* We don't dump this deletion immediate.  Rather, we add this path
669      to the deleted_entries of the parent directory baton.  That way,
670      we can tell (later) an addition from a replacement.  All the real
671      deletions get handled in close_directory().  */
672   svn_hash_sets(pb->deleted_entries, apr_pstrdup(pb->eb->pool, path), pb);
673
674   return SVN_NO_ERROR;
675 }
676
677 static svn_error_t *
678 add_directory(const char *path,
679               void *parent_baton,
680               const char *copyfrom_path,
681               svn_revnum_t copyfrom_rev,
682               apr_pool_t *pool,
683               void **child_baton)
684 {
685   struct dir_baton *pb = parent_baton;
686   void *val;
687   struct dir_baton *new_db;
688   svn_boolean_t is_copy;
689
690   LDR_DBG(("add_directory %s\n", path));
691
692   SVN_ERR(dump_pending(pb->eb, pool));
693
694   new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb,
695                           pb, TRUE, pb->eb->pool);
696
697   /* This might be a replacement -- is the path already deleted? */
698   val = svn_hash_gets(pb->deleted_entries, path);
699
700   /* Detect an add-with-history */
701   is_copy = ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev);
702
703   /* Dump the node */
704   SVN_ERR(dump_node(pb->eb, new_db->repos_relpath, new_db, NULL,
705                     val ? svn_node_action_replace : svn_node_action_add,
706                     is_copy,
707                     is_copy ? new_db->copyfrom_path : NULL,
708                     is_copy ? copyfrom_rev : SVN_INVALID_REVNUM,
709                     pool));
710
711   if (val)
712     /* Delete the path, it's now been dumped */
713     svn_hash_sets(pb->deleted_entries, path, NULL);
714
715   /* Remember that we've started, but not yet finished handling this
716      directory. */
717   new_db->written_out = TRUE;
718   pb->eb->pending_baton = new_db;
719   pb->eb->pending_kind = svn_node_dir;
720
721   *child_baton = new_db;
722   return SVN_NO_ERROR;
723 }
724
725 static svn_error_t *
726 open_directory(const char *path,
727                void *parent_baton,
728                svn_revnum_t base_revision,
729                apr_pool_t *pool,
730                void **child_baton)
731 {
732   struct dir_baton *pb = parent_baton;
733   struct dir_baton *new_db;
734   const char *copyfrom_path = NULL;
735   svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
736
737   LDR_DBG(("open_directory %s\n", path));
738
739   SVN_ERR(dump_pending(pb->eb, pool));
740
741   /* If the parent directory has explicit comparison path and rev,
742      record the same for this one. */
743   if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
744     {
745       copyfrom_path = svn_relpath_join(pb->copyfrom_path,
746                                        svn_relpath_basename(path, NULL),
747                                        pb->eb->pool);
748       copyfrom_rev = pb->copyfrom_rev;
749     }
750
751   new_db = make_dir_baton(path, copyfrom_path, copyfrom_rev, pb->eb, pb,
752                           FALSE, pb->eb->pool);
753
754   *child_baton = new_db;
755   return SVN_NO_ERROR;
756 }
757
758 static svn_error_t *
759 close_directory(void *dir_baton,
760                 apr_pool_t *pool)
761 {
762   struct dir_baton *db = dir_baton;
763   apr_hash_index_t *hi;
764   svn_boolean_t this_pending;
765
766   LDR_DBG(("close_directory %p\n", dir_baton));
767
768   /* Remember if this directory is the one currently pending. */
769   this_pending = (db->eb->pending_baton == db);
770
771   SVN_ERR(dump_pending(db->eb, pool));
772
773   /* If this directory was pending, then dump_pending() should have
774      taken care of all the props and such.  Of course, the only way
775      that would be the case is if this directory was added/replaced.
776
777      Otherwise, if stuff for this directory has already been written
778      out (at some point in the past, prior to our handling other
779      nodes), we might need to generate a second "change" record just
780      to carry the information we've since learned about the
781      directory. */
782   if ((! this_pending) && (db->dump_props))
783     {
784       SVN_ERR(dump_node(db->eb, db->repos_relpath, db, NULL,
785                         svn_node_action_change, FALSE,
786                         NULL, SVN_INVALID_REVNUM, pool));
787       db->eb->pending_baton = db;
788       db->eb->pending_kind = svn_node_dir;
789       SVN_ERR(dump_pending(db->eb, pool));
790     }
791
792   /* Dump the deleted directory entries */
793   for (hi = apr_hash_first(pool, db->deleted_entries); hi;
794        hi = apr_hash_next(hi))
795     {
796       const char *path = svn__apr_hash_index_key(hi);
797
798       SVN_ERR(dump_node(db->eb, path, NULL, NULL, svn_node_action_delete,
799                         FALSE, NULL, SVN_INVALID_REVNUM, pool));
800     }
801
802   /* ### should be unnecessary */
803   apr_hash_clear(db->deleted_entries);
804
805   return SVN_NO_ERROR;
806 }
807
808 static svn_error_t *
809 add_file(const char *path,
810          void *parent_baton,
811          const char *copyfrom_path,
812          svn_revnum_t copyfrom_rev,
813          apr_pool_t *pool,
814          void **file_baton)
815 {
816   struct dir_baton *pb = parent_baton;
817   struct file_baton *fb;
818   void *val;
819
820   LDR_DBG(("add_file %s\n", path));
821
822   SVN_ERR(dump_pending(pb->eb, pool));
823
824   /* Make the file baton. */
825   fb = make_file_baton(path, pb, pool);
826
827   /* This might be a replacement -- is the path already deleted? */
828   val = svn_hash_gets(pb->deleted_entries, path);
829
830   /* Detect add-with-history. */
831   if (ARE_VALID_COPY_ARGS(copyfrom_path, copyfrom_rev))
832     {
833       fb->copyfrom_path = svn_relpath_canonicalize(copyfrom_path, fb->pool);
834       fb->copyfrom_rev = copyfrom_rev;
835       fb->is_copy = TRUE;
836     }
837   fb->action = val ? svn_node_action_replace : svn_node_action_add;
838
839   /* Delete the path, it's now been dumped. */
840   if (val)
841     svn_hash_sets(pb->deleted_entries, path, NULL);
842
843   *file_baton = fb;
844   return SVN_NO_ERROR;
845 }
846
847 static svn_error_t *
848 open_file(const char *path,
849           void *parent_baton,
850           svn_revnum_t ancestor_revision,
851           apr_pool_t *pool,
852           void **file_baton)
853 {
854   struct dir_baton *pb = parent_baton;
855   struct file_baton *fb;
856
857   LDR_DBG(("open_file %s\n", path));
858
859   SVN_ERR(dump_pending(pb->eb, pool));
860
861   /* Make the file baton. */
862   fb = make_file_baton(path, pb, pool);
863
864   /* If the parent directory has explicit copyfrom path and rev,
865      record the same for this one. */
866   if (ARE_VALID_COPY_ARGS(pb->copyfrom_path, pb->copyfrom_rev))
867     {
868       fb->copyfrom_path = svn_relpath_join(pb->copyfrom_path,
869                                            svn_relpath_basename(path, NULL),
870                                            pb->eb->pool);
871       fb->copyfrom_rev = pb->copyfrom_rev;
872     }
873
874   *file_baton = fb;
875   return SVN_NO_ERROR;
876 }
877
878 static svn_error_t *
879 change_dir_prop(void *parent_baton,
880                 const char *name,
881                 const svn_string_t *value,
882                 apr_pool_t *pool)
883 {
884   struct dir_baton *db = parent_baton;
885   svn_boolean_t this_pending;
886
887   LDR_DBG(("change_dir_prop %p\n", parent_baton));
888
889   /* This directory is not pending, but something else is, so handle
890      the "something else".  */
891   this_pending = (db->eb->pending_baton == db);
892   if (! this_pending)
893     SVN_ERR(dump_pending(db->eb, pool));
894
895   if (svn_property_kind2(name) != svn_prop_regular_kind)
896     return SVN_NO_ERROR;
897
898   if (value)
899     svn_hash_sets(db->props,
900                   apr_pstrdup(db->pool, name),
901                   svn_string_dup(value, db->pool));
902   else
903     svn_hash_sets(db->deleted_props, apr_pstrdup(db->pool, name), "");
904
905   /* Make sure we eventually output the props, and disable printing
906      a couple of extra newlines */
907   db->dump_newlines = FALSE;
908   db->dump_props = TRUE;
909
910   return SVN_NO_ERROR;
911 }
912
913 static svn_error_t *
914 change_file_prop(void *file_baton,
915                  const char *name,
916                  const svn_string_t *value,
917                  apr_pool_t *pool)
918 {
919   struct file_baton *fb = file_baton;
920
921   LDR_DBG(("change_file_prop %p\n", file_baton));
922
923   if (svn_property_kind2(name) != svn_prop_regular_kind)
924     return SVN_NO_ERROR;
925
926   if (value)
927     svn_hash_sets(fb->props,
928                   apr_pstrdup(fb->pool, name),
929                   svn_string_dup(value, fb->pool));
930   else
931     svn_hash_sets(fb->deleted_props, apr_pstrdup(fb->pool, name), "");
932
933   /* Dump the property headers and wait; close_file might need
934      to write text headers too depending on whether
935      apply_textdelta is called */
936   fb->dump_props = TRUE;
937
938   return SVN_NO_ERROR;
939 }
940
941 static svn_error_t *
942 window_handler(svn_txdelta_window_t *window, void *baton)
943 {
944   struct handler_baton *hb = baton;
945   static svn_error_t *err;
946
947   err = hb->apply_handler(window, hb->apply_baton);
948   if (window != NULL && !err)
949     return SVN_NO_ERROR;
950
951   if (err)
952     SVN_ERR(err);
953
954   return SVN_NO_ERROR;
955 }
956
957 static svn_error_t *
958 apply_textdelta(void *file_baton, const char *base_checksum,
959                 apr_pool_t *pool,
960                 svn_txdelta_window_handler_t *handler,
961                 void **handler_baton)
962 {
963   struct file_baton *fb = file_baton;
964   struct dump_edit_baton *eb = fb->eb;
965   struct handler_baton *hb;
966   svn_stream_t *delta_filestream;
967
968   LDR_DBG(("apply_textdelta %p\n", file_baton));
969
970   /* This is custom handler_baton, allocated from a separate pool.  */
971   hb = apr_pcalloc(eb->pool, sizeof(*hb));
972
973   /* Use a temporary file to measure the Text-content-length */
974   delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
975
976   /* Prepare to write the delta to the delta_filestream */
977   svn_txdelta_to_svndiff3(&(hb->apply_handler), &(hb->apply_baton),
978                           delta_filestream, 0,
979                           SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
980
981   /* Record that there's text to be dumped, and its base checksum. */
982   fb->dump_text = TRUE;
983   fb->base_checksum = apr_pstrdup(eb->pool, base_checksum);
984
985   /* The actual writing takes place when this function has
986      finished. Set handler and handler_baton now so for
987      window_handler() */
988   *handler = window_handler;
989   *handler_baton = hb;
990
991   return SVN_NO_ERROR;
992 }
993
994 static svn_error_t *
995 close_file(void *file_baton,
996            const char *text_checksum,
997            apr_pool_t *pool)
998 {
999   struct file_baton *fb = file_baton;
1000   struct dump_edit_baton *eb = fb->eb;
1001   apr_finfo_t *info = apr_pcalloc(pool, sizeof(apr_finfo_t));
1002   svn_stringbuf_t *propstring;
1003
1004   LDR_DBG(("close_file %p\n", file_baton));
1005
1006   SVN_ERR(dump_pending(eb, pool));
1007
1008   /* Dump the node. */
1009   SVN_ERR(dump_node(eb, fb->repos_relpath, NULL, fb,
1010                     fb->action, fb->is_copy, fb->copyfrom_path,
1011                     fb->copyfrom_rev, pool));
1012
1013   /* Some pending properties to dump?  We'll dump just the headers for
1014      now, then dump the actual propchange content only after dumping
1015      the text headers too (if present). */
1016   SVN_ERR(do_dump_props(&propstring, eb->stream, fb->props, fb->deleted_props,
1017                         &(fb->dump_props), pool, pool));
1018
1019   /* Dump the text headers */
1020   if (fb->dump_text)
1021     {
1022       apr_status_t err;
1023
1024       /* Text-delta: true */
1025       SVN_ERR(svn_stream_puts(eb->stream,
1026                               SVN_REPOS_DUMPFILE_TEXT_DELTA
1027                               ": true\n"));
1028
1029       err = apr_file_info_get(info, APR_FINFO_SIZE, eb->delta_file);
1030       if (err)
1031         SVN_ERR(svn_error_wrap_apr(err, NULL));
1032
1033       if (fb->base_checksum)
1034         /* Text-delta-base-md5: */
1035         SVN_ERR(svn_stream_printf(eb->stream, pool,
1036                                   SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5
1037                                   ": %s\n",
1038                                   fb->base_checksum));
1039
1040       /* Text-content-length: 39 */
1041       SVN_ERR(svn_stream_printf(eb->stream, pool,
1042                                 SVN_REPOS_DUMPFILE_TEXT_CONTENT_LENGTH
1043                                 ": %lu\n",
1044                                 (unsigned long)info->size));
1045
1046       /* Text-content-md5: 82705804337e04dcd0e586bfa2389a7f */
1047       SVN_ERR(svn_stream_printf(eb->stream, pool,
1048                                 SVN_REPOS_DUMPFILE_TEXT_CONTENT_MD5
1049                                 ": %s\n",
1050                                 text_checksum));
1051     }
1052
1053   /* Content-length: 1549 */
1054   /* If both text and props are absent, skip this header */
1055   if (fb->dump_props)
1056     SVN_ERR(svn_stream_printf(eb->stream, pool,
1057                               SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1058                               ": %ld\n\n",
1059                               (unsigned long)info->size + propstring->len));
1060   else if (fb->dump_text)
1061     SVN_ERR(svn_stream_printf(eb->stream, pool,
1062                               SVN_REPOS_DUMPFILE_CONTENT_LENGTH
1063                               ": %ld\n\n",
1064                               (unsigned long)info->size));
1065
1066   /* Dump the props now */
1067   if (fb->dump_props)
1068     {
1069       SVN_ERR(svn_stream_write(eb->stream, propstring->data,
1070                                &(propstring->len)));
1071
1072       /* Cleanup */
1073       fb->dump_props = FALSE;
1074       apr_hash_clear(fb->props);
1075       apr_hash_clear(fb->deleted_props);
1076     }
1077
1078   /* Dump the text */
1079   if (fb->dump_text)
1080     {
1081       /* Seek to the beginning of the delta file, map it to a stream,
1082          and copy the stream to eb->stream. Then close the stream and
1083          truncate the file so we can reuse it for the next textdelta
1084          application. Note that the file isn't created, opened or
1085          closed here */
1086       svn_stream_t *delta_filestream;
1087       apr_off_t offset = 0;
1088
1089       SVN_ERR(svn_io_file_seek(eb->delta_file, APR_SET, &offset, pool));
1090       delta_filestream = svn_stream_from_aprfile2(eb->delta_file, TRUE, pool);
1091       SVN_ERR(svn_stream_copy3(delta_filestream, eb->stream, NULL, NULL, pool));
1092
1093       /* Cleanup */
1094       SVN_ERR(svn_stream_close(delta_filestream));
1095       SVN_ERR(svn_io_file_trunc(eb->delta_file, 0, pool));
1096     }
1097
1098   /* Write a couple of blank lines for matching output with `svnadmin
1099      dump` */
1100   SVN_ERR(svn_stream_puts(eb->stream, "\n\n"));
1101
1102   return SVN_NO_ERROR;
1103 }
1104
1105 static svn_error_t *
1106 close_edit(void *edit_baton, apr_pool_t *pool)
1107 {
1108   return SVN_NO_ERROR;
1109 }
1110
1111 static svn_error_t *
1112 fetch_base_func(const char **filename,
1113                 void *baton,
1114                 const char *path,
1115                 svn_revnum_t base_revision,
1116                 apr_pool_t *result_pool,
1117                 apr_pool_t *scratch_pool)
1118 {
1119   struct dump_edit_baton *eb = baton;
1120   svn_stream_t *fstream;
1121   svn_error_t *err;
1122
1123   if (path[0] == '/')
1124     path += 1;
1125
1126   if (! SVN_IS_VALID_REVNUM(base_revision))
1127     base_revision = eb->current_revision - 1;
1128
1129   SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
1130                                  svn_io_file_del_on_pool_cleanup,
1131                                  result_pool, scratch_pool));
1132
1133   err = svn_ra_get_file(eb->ra_session, path, base_revision,
1134                         fstream, NULL, NULL, scratch_pool);
1135   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
1136     {
1137       svn_error_clear(err);
1138       SVN_ERR(svn_stream_close(fstream));
1139
1140       *filename = NULL;
1141       return SVN_NO_ERROR;
1142     }
1143   else if (err)
1144     return svn_error_trace(err);
1145
1146   SVN_ERR(svn_stream_close(fstream));
1147
1148   return SVN_NO_ERROR;
1149 }
1150
1151 static svn_error_t *
1152 fetch_props_func(apr_hash_t **props,
1153                  void *baton,
1154                  const char *path,
1155                  svn_revnum_t base_revision,
1156                  apr_pool_t *result_pool,
1157                  apr_pool_t *scratch_pool)
1158 {
1159   struct dump_edit_baton *eb = baton;
1160   svn_node_kind_t node_kind;
1161
1162   if (path[0] == '/')
1163     path += 1;
1164
1165   if (! SVN_IS_VALID_REVNUM(base_revision))
1166     base_revision = eb->current_revision - 1;
1167
1168   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, &node_kind,
1169                             scratch_pool));
1170
1171   if (node_kind == svn_node_file)
1172     {
1173       SVN_ERR(svn_ra_get_file(eb->ra_session, path, base_revision,
1174                               NULL, NULL, props, result_pool));
1175     }
1176   else if (node_kind == svn_node_dir)
1177     {
1178       apr_array_header_t *tmp_props;
1179
1180       SVN_ERR(svn_ra_get_dir2(eb->ra_session, NULL, NULL, props, path,
1181                               base_revision, 0 /* Dirent fields */,
1182                               result_pool));
1183       tmp_props = svn_prop_hash_to_array(*props, result_pool);
1184       SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
1185                                    result_pool));
1186       *props = svn_prop_array_to_hash(tmp_props, result_pool);
1187     }
1188   else
1189     {
1190       *props = apr_hash_make(result_pool);
1191     }
1192
1193   return SVN_NO_ERROR;
1194 }
1195
1196 static svn_error_t *
1197 fetch_kind_func(svn_node_kind_t *kind,
1198                 void *baton,
1199                 const char *path,
1200                 svn_revnum_t base_revision,
1201                 apr_pool_t *scratch_pool)
1202 {
1203   struct dump_edit_baton *eb = baton;
1204
1205   if (path[0] == '/')
1206     path += 1;
1207
1208   if (! SVN_IS_VALID_REVNUM(base_revision))
1209     base_revision = eb->current_revision - 1;
1210
1211   SVN_ERR(svn_ra_check_path(eb->ra_session, path, base_revision, kind,
1212                             scratch_pool));
1213
1214   return SVN_NO_ERROR;
1215 }
1216
1217 svn_error_t *
1218 svn_rdump__get_dump_editor(const svn_delta_editor_t **editor,
1219                            void **edit_baton,
1220                            svn_revnum_t revision,
1221                            svn_stream_t *stream,
1222                            svn_ra_session_t *ra_session,
1223                            const char *update_anchor_relpath,
1224                            svn_cancel_func_t cancel_func,
1225                            void *cancel_baton,
1226                            apr_pool_t *pool)
1227 {
1228   struct dump_edit_baton *eb;
1229   svn_delta_editor_t *de;
1230   svn_delta_shim_callbacks_t *shim_callbacks =
1231                                         svn_delta_shim_callbacks_default(pool);
1232
1233   eb = apr_pcalloc(pool, sizeof(struct dump_edit_baton));
1234   eb->stream = stream;
1235   eb->ra_session = ra_session;
1236   eb->update_anchor_relpath = update_anchor_relpath;
1237   eb->current_revision = revision;
1238   eb->pending_kind = svn_node_none;
1239
1240   /* Create a special per-revision pool */
1241   eb->pool = svn_pool_create(pool);
1242
1243   /* Open a unique temporary file for all textdelta applications in
1244      this edit session. The file is automatically closed and cleaned
1245      up when the edit session is done. */
1246   SVN_ERR(svn_io_open_unique_file3(&(eb->delta_file), &(eb->delta_abspath),
1247                                    NULL, svn_io_file_del_on_close, pool, pool));
1248
1249   de = svn_delta_default_editor(pool);
1250   de->open_root = open_root;
1251   de->delete_entry = delete_entry;
1252   de->add_directory = add_directory;
1253   de->open_directory = open_directory;
1254   de->close_directory = close_directory;
1255   de->change_dir_prop = change_dir_prop;
1256   de->change_file_prop = change_file_prop;
1257   de->apply_textdelta = apply_textdelta;
1258   de->add_file = add_file;
1259   de->open_file = open_file;
1260   de->close_file = close_file;
1261   de->close_edit = close_edit;
1262
1263   /* Set the edit_baton and editor. */
1264   *edit_baton = eb;
1265   *editor = de;
1266
1267   /* Wrap this editor in a cancellation editor. */
1268   SVN_ERR(svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1269                                             de, eb, editor, edit_baton, pool));
1270
1271   shim_callbacks->fetch_base_func = fetch_base_func;
1272   shim_callbacks->fetch_props_func = fetch_props_func;
1273   shim_callbacks->fetch_kind_func = fetch_kind_func;
1274   shim_callbacks->fetch_baton = eb;
1275
1276   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
1277                                    NULL, NULL, shim_callbacks, pool, pool));
1278
1279   return SVN_NO_ERROR;
1280 }