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