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