1/*
2 *  load_editor.c: The svn_delta_editor_t editor used by svnrdump to
3 *  load 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_cmdline.h"
26#include "svn_pools.h"
27#include "svn_delta.h"
28#include "svn_repos.h"
29#include "svn_props.h"
30#include "svn_path.h"
31#include "svn_ra.h"
32#include "svn_subst.h"
33#include "svn_io.h"
34#include "svn_private_config.h"
35#include "private/svn_repos_private.h"
36#include "private/svn_ra_private.h"
37#include "private/svn_mergeinfo_private.h"
38#include "private/svn_fspath.h"
39
40#include "svnrdump.h"
41
42#define SVNRDUMP_PROP_LOCK SVN_PROP_PREFIX "rdump-lock"
43
44#define ARE_VALID_COPY_ARGS(p,r) ((p) && SVN_IS_VALID_REVNUM(r))
45
46
47/**
48 * General baton used by the parser functions.
49 */
50struct parse_baton
51{
52  /* Commit editor and baton used to transfer loaded revisions to
53     the target repository. */
54  const svn_delta_editor_t *commit_editor;
55  void *commit_edit_baton;
56
57  /* RA session(s) for committing to the target repository. */
58  svn_ra_session_t *session;
59  svn_ra_session_t *aux_session;
60
61  /* To bleep, or not to bleep?  (What kind of question is that?) */
62  svn_boolean_t quiet;
63
64  /* Root URL of the target repository. */
65  const char *root_url;
66
67  /* The "parent directory" of the target repository in which to load.
68     (This is essentially the difference between ROOT_URL and
69     SESSION's url, and roughly equivalent to the 'svnadmin load
70     --parent-dir' option.) */
71  const char *parent_dir;
72
73  /* A mapping of svn_revnum_t * dump stream revisions to their
74     corresponding svn_revnum_t * target repository revisions. */
75  /* ### See http://subversion.tigris.org/issues/show_bug.cgi?id=3903
76     ### for discussion about improving the memory costs of this mapping. */
77  apr_hash_t *rev_map;
78
79  /* The most recent (youngest) revision from the dump stream mapped in
80     REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
81  svn_revnum_t last_rev_mapped;
82
83  /* The oldest revision loaded from the dump stream, or
84     SVN_INVALID_REVNUM if none have been loaded. */
85  svn_revnum_t oldest_dumpstream_rev;
86
87  /* An hash containing specific revision properties to skip while
88     loading. */
89  apr_hash_t *skip_revprops;
90};
91
92/**
93 * Use to wrap the dir_context_t in commit.c so we can keep track of
94 * relpath and parent for open_directory and close_directory.
95 */
96struct directory_baton
97{
98  void *baton;
99  const char *relpath;
100
101  /* The copy-from source of this directory, no matter whether it is
102     copied explicitly (the root node of a copy) or implicitly (being an
103     existing child of a copied directory). For a node that is newly
104     added (without history), even inside a copied parent, these are
105     NULL and SVN_INVALID_REVNUM. */
106  const char *copyfrom_path;
107  svn_revnum_t copyfrom_rev;
108
109  struct directory_baton *parent;
110};
111
112/**
113 * Baton used to represent a node; to be used by the parser
114 * functions. Contains a link to the revision baton.
115 */
116struct node_baton
117{
118  const char *path;
119  svn_node_kind_t kind;
120  enum svn_node_action action;
121
122  /* Is this directory explicitly added? If not, then it already existed
123     or is a child of a copy. */
124  svn_boolean_t is_added;
125
126  svn_revnum_t copyfrom_rev;
127  const char *copyfrom_path;
128  const char *copyfrom_url;
129
130  void *file_baton;
131  const char *base_checksum;
132
133  /* (const char *name) -> (svn_prop_t *) */
134  apr_hash_t *prop_changes;
135
136  struct revision_baton *rb;
137};
138
139/**
140 * Baton used to represet a revision; used by the parser
141 * functions. Contains a link to the parser baton.
142 */
143struct revision_baton
144{
145  svn_revnum_t rev;
146  apr_hash_t *revprop_table;
147  apr_int32_t rev_offset;
148
149  const svn_string_t *datestamp;
150  const svn_string_t *author;
151
152  struct parse_baton *pb;
153  struct directory_baton *db;
154  apr_pool_t *pool;
155};
156
157
158
159/* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
160   anything added to the hash is allocated in the hash's pool. */
161static void
162set_revision_mapping(apr_hash_t *rev_map,
163                     svn_revnum_t from_rev,
164                     svn_revnum_t to_rev)
165{
166  svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
167                                         sizeof(svn_revnum_t) * 2);
168  mapped_revs[0] = from_rev;
169  mapped_revs[1] = to_rev;
170  apr_hash_set(rev_map, mapped_revs,
171               sizeof(svn_revnum_t), mapped_revs + 1);
172}
173
174/* Return the revision to which FROM_REV maps in REV_MAP, or
175   SVN_INVALID_REVNUM if no such mapping exists. */
176static svn_revnum_t
177get_revision_mapping(apr_hash_t *rev_map,
178                     svn_revnum_t from_rev)
179{
180  svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
181                                      sizeof(from_rev));
182  return to_rev ? *to_rev : SVN_INVALID_REVNUM;
183}
184
185
186static svn_error_t *
187commit_callback(const svn_commit_info_t *commit_info,
188                void *baton,
189                apr_pool_t *pool)
190{
191  struct revision_baton *rb = baton;
192  struct parse_baton *pb = rb->pb;
193
194  /* ### Don't print directly; generate a notification. */
195  if (! pb->quiet)
196    SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
197                               commit_info->revision));
198
199  /* Add the mapping of the dumpstream revision to the committed revision. */
200  set_revision_mapping(pb->rev_map, rb->rev, commit_info->revision);
201
202  /* If the incoming dump stream has non-contiguous revisions (e.g. from
203     using svndumpfilter --drop-empty-revs without --renumber-revs) then
204     we must account for the missing gaps in PB->REV_MAP.  Otherwise we
205     might not be able to map all mergeinfo source revisions to the correct
206     revisions in the target repos. */
207  if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
208      && (rb->rev != pb->last_rev_mapped + 1))
209    {
210      svn_revnum_t i;
211
212      for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
213        {
214          set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
215        }
216    }
217
218  /* Update our "last revision mapped". */
219  pb->last_rev_mapped = rb->rev;
220
221  return SVN_NO_ERROR;
222}
223
224/* Implements `svn_ra__lock_retry_func_t'. */
225static svn_error_t *
226lock_retry_func(void *baton,
227                const svn_string_t *reposlocktoken,
228                apr_pool_t *pool)
229{
230  return svn_cmdline_printf(pool,
231                            _("Failed to get lock on destination "
232                              "repos, currently held by '%s'\n"),
233                            reposlocktoken->data);
234}
235
236
237static svn_error_t *
238fetch_base_func(const char **filename,
239                void *baton,
240                const char *path,
241                svn_revnum_t base_revision,
242                apr_pool_t *result_pool,
243                apr_pool_t *scratch_pool)
244{
245  struct revision_baton *rb = baton;
246  svn_stream_t *fstream;
247  svn_error_t *err;
248
249  if (! SVN_IS_VALID_REVNUM(base_revision))
250    base_revision = rb->rev - 1;
251
252  SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
253                                 svn_io_file_del_on_pool_cleanup,
254                                 result_pool, scratch_pool));
255
256  err = svn_ra_get_file(rb->pb->aux_session, path, base_revision,
257                        fstream, NULL, NULL, scratch_pool);
258  if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
259    {
260      svn_error_clear(err);
261      SVN_ERR(svn_stream_close(fstream));
262
263      *filename = NULL;
264      return SVN_NO_ERROR;
265    }
266  else if (err)
267    return svn_error_trace(err);
268
269  SVN_ERR(svn_stream_close(fstream));
270
271  return SVN_NO_ERROR;
272}
273
274static svn_error_t *
275fetch_props_func(apr_hash_t **props,
276                 void *baton,
277                 const char *path,
278                 svn_revnum_t base_revision,
279                 apr_pool_t *result_pool,
280                 apr_pool_t *scratch_pool)
281{
282  struct revision_baton *rb = baton;
283  svn_node_kind_t node_kind;
284
285  if (! SVN_IS_VALID_REVNUM(base_revision))
286    base_revision = rb->rev - 1;
287
288  SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
289                            &node_kind, scratch_pool));
290
291  if (node_kind == svn_node_file)
292    {
293      SVN_ERR(svn_ra_get_file(rb->pb->aux_session, path, base_revision,
294                              NULL, NULL, props, result_pool));
295    }
296  else if (node_kind == svn_node_dir)
297    {
298      apr_array_header_t *tmp_props;
299
300      SVN_ERR(svn_ra_get_dir2(rb->pb->aux_session, NULL, NULL, props, path,
301                              base_revision, 0 /* Dirent fields */,
302                              result_pool));
303      tmp_props = svn_prop_hash_to_array(*props, result_pool);
304      SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
305                                   result_pool));
306      *props = svn_prop_array_to_hash(tmp_props, result_pool);
307    }
308  else
309    {
310      *props = apr_hash_make(result_pool);
311    }
312
313  return SVN_NO_ERROR;
314}
315
316static svn_error_t *
317fetch_kind_func(svn_node_kind_t *kind,
318                void *baton,
319                const char *path,
320                svn_revnum_t base_revision,
321                apr_pool_t *scratch_pool)
322{
323  struct revision_baton *rb = baton;
324
325  if (! SVN_IS_VALID_REVNUM(base_revision))
326    base_revision = rb->rev - 1;
327
328  SVN_ERR(svn_ra_check_path(rb->pb->aux_session, path, base_revision,
329                            kind, scratch_pool));
330
331  return SVN_NO_ERROR;
332}
333
334static svn_delta_shim_callbacks_t *
335get_shim_callbacks(struct revision_baton *rb,
336                   apr_pool_t *pool)
337{
338  svn_delta_shim_callbacks_t *callbacks =
339                        svn_delta_shim_callbacks_default(pool);
340
341  callbacks->fetch_props_func = fetch_props_func;
342  callbacks->fetch_kind_func = fetch_kind_func;
343  callbacks->fetch_base_func = fetch_base_func;
344  callbacks->fetch_baton = rb;
345
346  return callbacks;
347}
348
349/* Acquire a lock (of sorts) on the repository associated with the
350 * given RA SESSION. This lock is just a revprop change attempt in a
351 * time-delay loop. This function is duplicated by svnsync in
352 * svnsync/svnsync.c
353 *
354 * ### TODO: Make this function more generic and
355 * expose it through a header for use by other Subversion
356 * applications to avoid duplication.
357 */
358static svn_error_t *
359get_lock(const svn_string_t **lock_string_p,
360         svn_ra_session_t *session,
361         svn_cancel_func_t cancel_func,
362         void *cancel_baton,
363         apr_pool_t *pool)
364{
365  svn_boolean_t be_atomic;
366
367  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
368                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
369                                pool));
370  if (! be_atomic)
371    {
372      /* Pre-1.7 servers can't lock without a race condition.  (Issue #3546) */
373      svn_error_t *err =
374        svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
375                         _("Target server does not support atomic revision "
376                           "property edits; consider upgrading it to 1.7."));
377      svn_handle_warning2(stderr, err, "svnrdump: ");
378      svn_error_clear(err);
379    }
380
381  return svn_ra__get_operational_lock(lock_string_p, NULL, session,
382                                      SVNRDUMP_PROP_LOCK, FALSE,
383                                      10 /* retries */, lock_retry_func, NULL,
384                                      cancel_func, cancel_baton, pool);
385}
386
387static svn_error_t *
388new_revision_record(void **revision_baton,
389                    apr_hash_t *headers,
390                    void *parse_baton,
391                    apr_pool_t *pool)
392{
393  struct revision_baton *rb;
394  struct parse_baton *pb;
395  apr_hash_index_t *hi;
396  svn_revnum_t head_rev;
397
398  rb = apr_pcalloc(pool, sizeof(*rb));
399  pb = parse_baton;
400  rb->pool = svn_pool_create(pool);
401  rb->pb = pb;
402  rb->db = NULL;
403
404  for (hi = apr_hash_first(pool, headers); hi; hi = apr_hash_next(hi))
405    {
406      const char *hname = apr_hash_this_key(hi);
407      const char *hval = apr_hash_this_val(hi);
408
409      if (strcmp(hname, SVN_REPOS_DUMPFILE_REVISION_NUMBER) == 0)
410        rb->rev = atoi(hval);
411    }
412
413  SVN_ERR(svn_ra_get_latest_revnum(pb->session, &head_rev, pool));
414
415  /* FIXME: This is a lame fallback loading multiple segments of dump in
416     several separate operations. It is highly susceptible to race conditions.
417     Calculate the revision 'offset' for finding copyfrom sources.
418     It might be positive or negative. */
419  rb->rev_offset = (apr_int32_t) ((rb->rev) - (head_rev + 1));
420
421  /* Stash the oldest (non-zero) dumpstream revision seen. */
422  if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
423    pb->oldest_dumpstream_rev = rb->rev;
424
425  /* Set the commit_editor/ commit_edit_baton to NULL and wait for
426     them to be created in new_node_record */
427  rb->pb->commit_editor = NULL;
428  rb->pb->commit_edit_baton = NULL;
429  rb->revprop_table = apr_hash_make(rb->pool);
430
431  *revision_baton = rb;
432  return SVN_NO_ERROR;
433}
434
435static svn_error_t *
436magic_header_record(int version,
437            void *parse_baton,
438            apr_pool_t *pool)
439{
440  return SVN_NO_ERROR;
441}
442
443static svn_error_t *
444uuid_record(const char *uuid,
445            void *parse_baton,
446            apr_pool_t *pool)
447{
448  return SVN_NO_ERROR;
449}
450
451/* Push information about another directory onto the linked list RB->db.
452 *
453 * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
454 * repository-relative path of this directory. IS_ADDED is true iff this
455 * directory is being added (with or without history). If added with
456 * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
457 * are NULL/SVN_INVALID_REVNUM.
458 */
459static void
460push_directory(struct revision_baton *rb,
461               void *child_baton,
462               const char *relpath,
463               svn_boolean_t is_added,
464               const char *copyfrom_path,
465               svn_revnum_t copyfrom_rev)
466{
467  struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
468
469  SVN_ERR_ASSERT_NO_RETURN(
470    is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
471
472  /* If this node is an existing (not newly added) child of a copied node,
473     calculate where it was copied from. */
474  if (!is_added
475      && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
476    {
477      const char *name = svn_relpath_basename(relpath, NULL);
478
479      copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
480                                       rb->pool);
481      copyfrom_rev = rb->db->copyfrom_rev;
482    }
483
484  child_db->baton = child_baton;
485  child_db->relpath = relpath;
486  child_db->copyfrom_path = copyfrom_path;
487  child_db->copyfrom_rev = copyfrom_rev;
488  child_db->parent = rb->db;
489  rb->db = child_db;
490}
491
492static svn_error_t *
493new_node_record(void **node_baton,
494                apr_hash_t *headers,
495                void *revision_baton,
496                apr_pool_t *pool)
497{
498  struct revision_baton *rb = revision_baton;
499  const struct svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
500  void *commit_edit_baton = rb->pb->commit_edit_baton;
501  struct node_baton *nb;
502  apr_hash_index_t *hi;
503  void *child_baton;
504  const char *nb_dirname;
505
506  nb = apr_pcalloc(rb->pool, sizeof(*nb));
507  nb->rb = rb;
508  nb->is_added = FALSE;
509  nb->copyfrom_path = NULL;
510  nb->copyfrom_url = NULL;
511  nb->copyfrom_rev = SVN_INVALID_REVNUM;
512  nb->prop_changes = apr_hash_make(rb->pool);
513
514  /* If the creation of commit_editor is pending, create it now and
515     open_root on it; also create a top-level directory baton. */
516
517  if (!commit_editor)
518    {
519      /* The revprop_table should have been filled in with important
520         information like svn:log in set_revision_property. We can now
521         use it all this information to create our commit_editor. But
522         first, clear revprops that we aren't allowed to set with the
523         commit_editor. We'll set them separately using the RA API
524         after closing the editor (see close_revision). */
525
526      svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
527      svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
528
529      SVN_ERR(svn_ra__register_editor_shim_callbacks(rb->pb->session,
530                                    get_shim_callbacks(rb, rb->pool)));
531      SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
532                                        &commit_edit_baton, rb->revprop_table,
533                                        commit_callback, revision_baton,
534                                        NULL, FALSE, rb->pool));
535
536      rb->pb->commit_editor = commit_editor;
537      rb->pb->commit_edit_baton = commit_edit_baton;
538
539      SVN_ERR(commit_editor->open_root(commit_edit_baton,
540                                       rb->rev - rb->rev_offset - 1,
541                                       rb->pool, &child_baton));
542
543      /* child_baton corresponds to the root directory baton here */
544      push_directory(rb, child_baton, "", TRUE /*is_added*/,
545                     NULL, SVN_INVALID_REVNUM);
546    }
547
548  for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
549    {
550      const char *hname = apr_hash_this_key(hi);
551      const char *hval = apr_hash_this_val(hi);
552
553      /* Parse the different kinds of headers we can encounter and
554         stuff them into the node_baton for writing later */
555      if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
556        nb->path = apr_pstrdup(rb->pool, hval);
557      if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
558        nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
559      if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
560        {
561          if (strcmp(hval, "add") == 0)
562            nb->action = svn_node_action_add;
563          if (strcmp(hval, "change") == 0)
564            nb->action = svn_node_action_change;
565          if (strcmp(hval, "delete") == 0)
566            nb->action = svn_node_action_delete;
567          if (strcmp(hval, "replace") == 0)
568            nb->action = svn_node_action_replace;
569        }
570      if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
571        nb->base_checksum = apr_pstrdup(rb->pool, hval);
572      if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
573        nb->copyfrom_rev = atoi(hval);
574      if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
575        nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
576    }
577
578  /* Before handling the new node, ensure depth-first editing order by
579     traversing the directory hierarchy from the old node's to the new
580     node's parent directory. */
581  nb_dirname = svn_relpath_dirname(nb->path, pool);
582  if (svn_path_compare_paths(nb_dirname,
583                             rb->db->relpath) != 0)
584    {
585      char *ancestor_path;
586      apr_size_t residual_close_count;
587      apr_array_header_t *residual_open_path;
588      int i;
589      apr_size_t n;
590
591      ancestor_path =
592        svn_relpath_get_longest_ancestor(nb_dirname,
593                                         rb->db->relpath, pool);
594      residual_close_count =
595        svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
596                                                           rb->db->relpath));
597      residual_open_path =
598        svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
599                                                     nb_dirname), pool);
600
601      /* First close all as many directories as there are after
602         skip_ancestor, and then open fresh directories */
603      for (n = 0; n < residual_close_count; n ++)
604        {
605          /* Don't worry about destroying the actual rb->db object,
606             since the pool we're using has the lifetime of one
607             revision anyway */
608          SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
609          rb->db = rb->db->parent;
610        }
611
612      for (i = 0; i < residual_open_path->nelts; i ++)
613        {
614          char *relpath_compose =
615            svn_relpath_join(rb->db->relpath,
616                             APR_ARRAY_IDX(residual_open_path, i, const char *),
617                             rb->pool);
618          SVN_ERR(commit_editor->open_directory(relpath_compose,
619                                                rb->db->baton,
620                                                rb->rev - rb->rev_offset - 1,
621                                                rb->pool, &child_baton));
622          push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
623                         NULL, SVN_INVALID_REVNUM);
624        }
625    }
626
627  /* Fix up the copyfrom information in light of mapped revisions and
628     non-root load targets, and convert copyfrom path into a full
629     URL. */
630  if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
631    {
632      svn_revnum_t copyfrom_rev;
633
634      /* Try to find the copyfrom revision in the revision map;
635         failing that, fall back to the revision offset approach. */
636      copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
637      if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
638        copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
639
640      if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
641        return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
642                                 _("Relative source revision %ld is not"
643                                   " available in current repository"),
644                                 copyfrom_rev);
645
646      nb->copyfrom_rev = copyfrom_rev;
647
648      if (rb->pb->parent_dir)
649        nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
650                                             nb->copyfrom_path, rb->pool);
651      /* Convert to a URL, as the commit editor requires. */
652      nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
653                                                      nb->copyfrom_path,
654                                                      rb->pool);
655    }
656
657
658  switch (nb->action)
659    {
660    case svn_node_action_delete:
661    case svn_node_action_replace:
662      SVN_ERR(commit_editor->delete_entry(nb->path,
663                                          rb->rev - rb->rev_offset - 1,
664                                          rb->db->baton, rb->pool));
665      if (nb->action == svn_node_action_delete)
666        break;
667      else
668        /* FALL THROUGH */;
669    case svn_node_action_add:
670      nb->is_added = TRUE;
671      switch (nb->kind)
672        {
673        case svn_node_file:
674          SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
675                                          nb->copyfrom_url,
676                                          nb->copyfrom_rev,
677                                          rb->pool, &(nb->file_baton)));
678          break;
679        case svn_node_dir:
680          SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
681                                               nb->copyfrom_url,
682                                               nb->copyfrom_rev,
683                                               rb->pool, &child_baton));
684          push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
685                         nb->copyfrom_path, nb->copyfrom_rev);
686          break;
687        default:
688          break;
689        }
690      break;
691    case svn_node_action_change:
692      switch (nb->kind)
693        {
694        case svn_node_file:
695          SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
696                                           SVN_INVALID_REVNUM, rb->pool,
697                                           &(nb->file_baton)));
698          break;
699        default:
700          SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
701                                                rb->rev - rb->rev_offset - 1,
702                                                rb->pool, &child_baton));
703          push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
704                         NULL, SVN_INVALID_REVNUM);
705          break;
706        }
707      break;
708    }
709
710  *node_baton = nb;
711  return SVN_NO_ERROR;
712}
713
714static svn_error_t *
715set_revision_property(void *baton,
716                      const char *name,
717                      const svn_string_t *value)
718{
719  struct revision_baton *rb = baton;
720
721  SVN_ERR(svn_rdump__normalize_prop(name, &value, rb->pool));
722
723  SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
724
725  if (rb->rev > 0)
726    {
727      if (! svn_hash_gets(rb->pb->skip_revprops, name))
728        svn_hash_sets(rb->revprop_table,
729                      apr_pstrdup(rb->pool, name),
730                      svn_string_dup(value, rb->pool));
731    }
732  else if (rb->rev_offset == -1
733           && ! svn_hash_gets(rb->pb->skip_revprops, name))
734    {
735      /* Special case: set revision 0 properties directly (which is
736         safe because the commit_editor hasn't been created yet), but
737         only when loading into an 'empty' filesystem. */
738      SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
739                                      name, NULL, value, rb->pool));
740    }
741
742  /* Remember any datestamp/ author that passes through (see comment
743     in close_revision). */
744  if (!strcmp(name, SVN_PROP_REVISION_DATE))
745    rb->datestamp = svn_string_dup(value, rb->pool);
746  if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
747    rb->author = svn_string_dup(value, rb->pool);
748
749  return SVN_NO_ERROR;
750}
751
752static svn_error_t *
753set_node_property(void *baton,
754                  const char *name,
755                  const svn_string_t *value)
756{
757  struct node_baton *nb = baton;
758  struct revision_baton *rb = nb->rb;
759  struct parse_baton *pb = rb->pb;
760  apr_pool_t *pool = nb->rb->pool;
761  svn_prop_t *prop;
762
763  if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
764    {
765      svn_string_t *new_value;
766      svn_error_t *err;
767
768      err = svn_repos__adjust_mergeinfo_property(&new_value, value,
769                                                 pb->parent_dir,
770                                                 pb->rev_map,
771                                                 pb->oldest_dumpstream_rev,
772                                                 rb->rev_offset,
773                                                 NULL, NULL, /*notify*/
774                                                 pool, pool);
775      if (err)
776        {
777          return svn_error_quick_wrap(err,
778                                      _("Invalid svn:mergeinfo value"));
779        }
780
781      value = new_value;
782    }
783
784  SVN_ERR(svn_rdump__normalize_prop(name, &value, pool));
785
786  SVN_ERR(svn_repos__validate_prop(name, value, pool));
787
788  prop = apr_palloc(nb->rb->pool, sizeof (*prop));
789  prop->name = apr_pstrdup(pool, name);
790  prop->value = svn_string_dup(value, pool);
791  svn_hash_sets(nb->prop_changes, prop->name, prop);
792
793  return SVN_NO_ERROR;
794}
795
796static svn_error_t *
797delete_node_property(void *baton,
798                     const char *name)
799{
800  struct node_baton *nb = baton;
801  apr_pool_t *pool = nb->rb->pool;
802  svn_prop_t *prop;
803
804  SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
805
806  prop = apr_palloc(pool, sizeof (*prop));
807  prop->name = apr_pstrdup(pool, name);
808  prop->value = NULL;
809  svn_hash_sets(nb->prop_changes, prop->name, prop);
810
811  return SVN_NO_ERROR;
812}
813
814/* Delete all the properties of the node, if any.
815 *
816 * The commit editor doesn't have a method to delete a node's properties
817 * without knowing what they are, so we have to first find out what
818 * properties the node would have had. If it's copied (explicitly or
819 * implicitly), we look at the copy source. If it's only being changed,
820 * we look at the node's current path in the head revision.
821 */
822static svn_error_t *
823remove_node_props(void *baton)
824{
825  struct node_baton *nb = baton;
826  struct revision_baton *rb = nb->rb;
827  apr_pool_t *pool = nb->rb->pool;
828  apr_hash_index_t *hi;
829  apr_hash_t *props;
830  const char *orig_path;
831  svn_revnum_t orig_rev;
832
833  /* Find the path and revision that has the node's original properties */
834  if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
835    {
836      orig_path = nb->copyfrom_path;
837      orig_rev = nb->copyfrom_rev;
838    }
839  else if (!nb->is_added
840           && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
841    {
842      /* If this is a dir, then it's described by rb->db;
843         if this is a file, then it's a child of the dir in rb->db. */
844      orig_path = (nb->kind == svn_node_dir)
845                    ? rb->db->copyfrom_path
846                    : svn_relpath_join(rb->db->copyfrom_path,
847                                       svn_relpath_basename(nb->path, NULL),
848                                       rb->pool);
849      orig_rev = rb->db->copyfrom_rev;
850    }
851  else
852    {
853      /* ### Should we query at a known, fixed, "head" revision number
854         instead of passing SVN_INVALID_REVNUM and getting a moving target? */
855      orig_path = nb->path;
856      orig_rev = SVN_INVALID_REVNUM;
857    }
858
859  if ((nb->action == svn_node_action_add
860            || nb->action == svn_node_action_replace)
861      && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
862    /* Add-without-history; no "old" properties to worry about. */
863    return SVN_NO_ERROR;
864
865  if (nb->kind == svn_node_file)
866    {
867      SVN_ERR(svn_ra_get_file(nb->rb->pb->aux_session,
868                              orig_path, orig_rev, NULL, NULL, &props, pool));
869    }
870  else  /* nb->kind == svn_node_dir */
871    {
872      SVN_ERR(svn_ra_get_dir2(nb->rb->pb->aux_session, NULL, NULL, &props,
873                              orig_path, orig_rev, 0, pool));
874    }
875
876  for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
877    {
878      const char *name = apr_hash_this_key(hi);
879      svn_prop_kind_t kind = svn_property_kind2(name);
880
881      if (kind == svn_prop_regular_kind)
882        SVN_ERR(set_node_property(nb, name, NULL));
883    }
884
885  return SVN_NO_ERROR;
886}
887
888static svn_error_t *
889set_fulltext(svn_stream_t **stream,
890             void *node_baton)
891{
892  struct node_baton *nb = node_baton;
893  const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
894  svn_txdelta_window_handler_t handler;
895  void *handler_baton;
896  apr_pool_t *pool = nb->rb->pool;
897
898  SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
899                                         pool, &handler, &handler_baton));
900  *stream = svn_txdelta_target_push(handler, handler_baton,
901                                    svn_stream_empty(pool), pool);
902  return SVN_NO_ERROR;
903}
904
905static svn_error_t *
906apply_textdelta(svn_txdelta_window_handler_t *handler,
907                void **handler_baton,
908                void *node_baton)
909{
910  struct node_baton *nb = node_baton;
911  const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
912  apr_pool_t *pool = nb->rb->pool;
913
914  SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
915                                         pool, handler, handler_baton));
916
917  return SVN_NO_ERROR;
918}
919
920static svn_error_t *
921close_node(void *baton)
922{
923  struct node_baton *nb = baton;
924  const struct svn_delta_editor_t *commit_editor = nb->rb->pb->commit_editor;
925  apr_pool_t *pool = nb->rb->pool;
926  apr_hash_index_t *hi;
927
928  for (hi = apr_hash_first(pool, nb->prop_changes);
929       hi; hi = apr_hash_next(hi))
930    {
931      const char *name = apr_hash_this_key(hi);
932      svn_prop_t *prop = apr_hash_this_val(hi);
933
934      switch (nb->kind)
935        {
936        case svn_node_file:
937          SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
938                                                  name, prop->value, pool));
939          break;
940        case svn_node_dir:
941          SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
942                                                 name, prop->value, pool));
943          break;
944        default:
945          break;
946        }
947    }
948
949  /* Pass a file node closure through to the editor *unless* we
950     deleted the file (which doesn't require us to open it). */
951  if ((nb->kind == svn_node_file) && (nb->file_baton))
952    {
953      SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
954    }
955
956  /* The svn_node_dir case is handled in close_revision */
957
958  return SVN_NO_ERROR;
959}
960
961static svn_error_t *
962close_revision(void *baton)
963{
964  struct revision_baton *rb = baton;
965  const svn_delta_editor_t *commit_editor = rb->pb->commit_editor;
966  void *commit_edit_baton = rb->pb->commit_edit_baton;
967  svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
968
969  /* Fake revision 0 */
970  if (rb->rev == 0)
971    {
972      /* ### Don't print directly; generate a notification. */
973      if (! rb->pb->quiet)
974        SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
975    }
976  else if (commit_editor)
977    {
978      /* Close all pending open directories, and then close the edit
979         session itself */
980      while (rb->db && rb->db->parent)
981        {
982          SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
983          rb->db = rb->db->parent;
984        }
985      /* root dir's baton */
986      SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
987      SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
988    }
989  else
990    {
991      void *child_baton;
992
993      /* Legitimate revision with no node information */
994      SVN_ERR(svn_ra_get_commit_editor3(rb->pb->session, &commit_editor,
995                                        &commit_edit_baton, rb->revprop_table,
996                                        commit_callback, baton,
997                                        NULL, FALSE, rb->pool));
998
999      SVN_ERR(commit_editor->open_root(commit_edit_baton,
1000                                       rb->rev - rb->rev_offset - 1,
1001                                       rb->pool, &child_baton));
1002
1003      SVN_ERR(commit_editor->close_directory(child_baton, rb->pool));
1004      SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
1005    }
1006
1007  /* svn_fs_commit_txn() rewrites the datestamp and author properties;
1008     we'll rewrite them again by hand after closing the commit_editor.
1009     The only time we don't do this is for revision 0 when loaded into
1010     a non-empty repository.  */
1011  if (rb->rev > 0)
1012    {
1013      committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
1014    }
1015  else if (rb->rev_offset == -1)
1016    {
1017      committed_rev = 0;
1018    }
1019
1020  if (SVN_IS_VALID_REVNUM(committed_rev))
1021    {
1022      if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE))
1023        {
1024          SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_DATE,
1025                                           rb->datestamp, rb->pool));
1026          SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1027                                          SVN_PROP_REVISION_DATE,
1028                                          NULL, rb->datestamp, rb->pool));
1029        }
1030      if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR))
1031        {
1032          SVN_ERR(svn_repos__validate_prop(SVN_PROP_REVISION_AUTHOR,
1033                                           rb->author, rb->pool));
1034          SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
1035                                          SVN_PROP_REVISION_AUTHOR,
1036                                          NULL, rb->author, rb->pool));
1037        }
1038    }
1039
1040  svn_pool_destroy(rb->pool);
1041
1042  return SVN_NO_ERROR;
1043}
1044
1045svn_error_t *
1046svn_rdump__load_dumpstream(svn_stream_t *stream,
1047                           svn_ra_session_t *session,
1048                           svn_ra_session_t *aux_session,
1049                           svn_boolean_t quiet,
1050                           apr_hash_t *skip_revprops,
1051                           svn_cancel_func_t cancel_func,
1052                           void *cancel_baton,
1053                           apr_pool_t *pool)
1054{
1055  svn_repos_parse_fns3_t *parser;
1056  struct parse_baton *parse_baton;
1057  const svn_string_t *lock_string;
1058  svn_boolean_t be_atomic;
1059  svn_error_t *err;
1060  const char *session_url, *root_url, *parent_dir;
1061
1062  SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1063                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1064                                pool));
1065  SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1066  SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1067  SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1068  SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1069                                           session_url, pool));
1070
1071  parser = apr_pcalloc(pool, sizeof(*parser));
1072  parser->magic_header_record = magic_header_record;
1073  parser->uuid_record = uuid_record;
1074  parser->new_revision_record = new_revision_record;
1075  parser->new_node_record = new_node_record;
1076  parser->set_revision_property = set_revision_property;
1077  parser->set_node_property = set_node_property;
1078  parser->delete_node_property = delete_node_property;
1079  parser->remove_node_props = remove_node_props;
1080  parser->set_fulltext = set_fulltext;
1081  parser->apply_textdelta = apply_textdelta;
1082  parser->close_node = close_node;
1083  parser->close_revision = close_revision;
1084
1085  parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1086  parse_baton->session = session;
1087  parse_baton->aux_session = aux_session;
1088  parse_baton->quiet = quiet;
1089  parse_baton->root_url = root_url;
1090  parse_baton->parent_dir = parent_dir;
1091  parse_baton->rev_map = apr_hash_make(pool);
1092  parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1093  parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1094  parse_baton->skip_revprops = skip_revprops;
1095
1096  err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1097                                    cancel_func, cancel_baton, pool);
1098
1099  /* If all goes well, or if we're cancelled cleanly, don't leave a
1100     stray lock behind. */
1101  if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1102    err = svn_error_compose_create(
1103              svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,
1104                                               lock_string, pool),
1105              err);
1106  return err;
1107}
1108