]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/svnrdump/load_editor.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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,
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. */
176 static svn_revnum_t
177 get_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
186 static svn_error_t *
187 commit_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'. */
225 static svn_error_t *
226 lock_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
237 static svn_error_t *
238 fetch_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
274 static svn_error_t *
275 fetch_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
316 static svn_error_t *
317 fetch_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
334 static svn_delta_shim_callbacks_t *
335 get_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  */
358 static svn_error_t *
359 get_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
387 static svn_error_t *
388 new_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
435 static svn_error_t *
436 magic_header_record(int version,
437             void *parse_baton,
438             apr_pool_t *pool)
439 {
440   return SVN_NO_ERROR;
441 }
442
443 static svn_error_t *
444 uuid_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  */
459 static void
460 push_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
492 static svn_error_t *
493 new_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
714 static svn_error_t *
715 set_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
752 static svn_error_t *
753 set_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
796 static svn_error_t *
797 delete_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  */
822 static svn_error_t *
823 remove_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
888 static svn_error_t *
889 set_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
905 static svn_error_t *
906 apply_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
920 static svn_error_t *
921 close_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
961 static svn_error_t *
962 close_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
1045 svn_error_t *
1046 svn_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 }