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.  */
50struct 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 */
90struct 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().  */
124struct 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. */
131struct 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.  */
177static struct dir_baton *
178make_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.  */
222static struct file_baton *
223make_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. */
244static svn_error_t *
245get_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 */
287static svn_error_t *
288do_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
337static svn_error_t *
338do_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 */
361static svn_error_t *
362dump_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
502static svn_error_t *
503dump_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 */
545static svn_error_t *
546dump_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
583/*** Editor Function Implementations ***/
584
585static svn_error_t *
586open_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
656static svn_error_t *
657delete_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
677static svn_error_t *
678add_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
725static svn_error_t *
726open_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
758static svn_error_t *
759close_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
808static svn_error_t *
809add_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
847static svn_error_t *
848open_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
878static svn_error_t *
879change_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
913static svn_error_t *
914change_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
941static svn_error_t *
942window_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
957static svn_error_t *
958apply_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
994static svn_error_t *
995close_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
1105static svn_error_t *
1106close_edit(void *edit_baton, apr_pool_t *pool)
1107{
1108  return SVN_NO_ERROR;
1109}
1110
1111static svn_error_t *
1112fetch_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
1151static svn_error_t *
1152fetch_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
1196static svn_error_t *
1197fetch_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
1217svn_error_t *
1218svn_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}
1281