dump_editor.c revision 299742
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.  */
46struct 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 */
81struct 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. */
114struct 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.  */
159static struct dir_baton *
160make_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.  */
201static struct file_baton *
202make_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 */
224static svn_error_t *
225get_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 */
259static svn_error_t *
260dump_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 */
300static svn_error_t *
301dump_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
435static svn_error_t *
436dump_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 */
472static svn_error_t *
473dump_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
513/*** Editor Function Implementations ***/
514
515static svn_error_t *
516open_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
583static svn_error_t *
584delete_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
602static svn_error_t *
603add_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
647static svn_error_t *
648open_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
678static svn_error_t *
679close_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
727static svn_error_t *
728add_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
764static svn_error_t *
765open_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
793static svn_error_t *
794change_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
824static svn_error_t *
825change_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
850static svn_error_t *
851apply_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
875static svn_error_t *
876close_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
967static svn_error_t *
968close_edit(void *edit_baton, apr_pool_t *pool)
969{
970  return SVN_NO_ERROR;
971}
972
973static svn_error_t *
974fetch_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
1013static svn_error_t *
1014fetch_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
1058static svn_error_t *
1059fetch_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
1079svn_error_t *
1080svn_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}
1143