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