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  * A vtable that is driven by get_dumpstream_loader().
49  */
50 typedef struct loader_fns_t
51 {
52   /* Callback on starting a revision, to obtain an editor for the revision.
53    *
54    * This callback returns in *EDITOR and *EDIT_BATON an editor through which
55    * its caller will drive the changes for revision REVISION.
56    *
57    * This callback should call the editor's SET_TARGET_REVISION method if
58    * required.
59    *
60    * REV_PROPS holds the revision properties.
61    *
62    * POOL (and REV_PROPS) will persist until after the REVFINISH callback
63    * returns.
64    *
65    * Modelled on svn_ra_replay_revstart_callback_t.
66    */
67   svn_error_t *(*revstart)(
68     svn_revnum_t revision,
69     void *baton,
70     const svn_delta_editor_t **editor,
71     void **edit_baton,
72     apr_hash_t *rev_props,
73     apr_pool_t *pool);
74 
75   /* Fetch the node props for ORIG_PATH @ ORIG_REV, a node of kind KIND.
76    *
77    * This callback is required if a non-deltas-format dump is parsed;
78    * otherwise it will not be called and may be null. If required and not
79    * provided, the loader will return SVN_ERR_UNSUPPORTED_FEATURE.
80    *
81    * Only regular props are needed. Any special kinds of property such as
82    * entry-props and DAV/WC-props will be ignored.
83    */
84   svn_error_t *(*fetch_props)(
85     apr_hash_t **props,
86     void *baton,
87     const char *orig_path,
88     svn_revnum_t orig_rev,
89     svn_node_kind_t kind,
90     apr_pool_t *result_pool,
91     apr_pool_t *scratch_pool);
92 
93 } loader_fns_t;
94 
95 /**
96  * General baton used by the parser functions.
97  */
98 struct parse_baton
99 {
100   /* RA session(s) for committing to the target repository. */
101   svn_ra_session_t *session;
102   svn_ra_session_t *aux_session;
103 
104   /* To bleep, or not to bleep?  (What kind of question is that?) */
105   svn_boolean_t quiet;
106 
107   /* Root URL of the target repository. */
108   const char *root_url;
109 
110   /* The "parent directory" of the target repository in which to load.
111      (This is essentially the difference between ROOT_URL and
112      SESSION's url, and roughly equivalent to the 'svnadmin load
113      --parent-dir' option.) */
114   const char *parent_dir;
115 
116   /* A mapping of svn_revnum_t * dump stream revisions to their
117      corresponding svn_revnum_t * target repository revisions. */
118   /* ### See https://issues.apache.org/jira/browse/SVN-3903
119      ### for discussion about improving the memory costs of this mapping. */
120   apr_hash_t *rev_map;
121 
122   /* The most recent (youngest) revision from the dump stream mapped in
123      REV_MAP, or SVN_INVALID_REVNUM if no revisions have been mapped. */
124   svn_revnum_t last_rev_mapped;
125 
126   /* The oldest revision loaded from the dump stream, or
127      SVN_INVALID_REVNUM if none have been loaded. */
128   svn_revnum_t oldest_dumpstream_rev;
129 
130   /* An hash containing specific revision properties to skip while
131      loading. */
132   apr_hash_t *skip_revprops;
133 
134   const loader_fns_t *callbacks;
135   void *cb_baton;
136 };
137 
138 /**
139  * Use to wrap the dir_context_t in commit.c so we can keep track of
140  * relpath and parent for open_directory and close_directory.
141  */
142 struct directory_baton
143 {
144   void *baton;
145   const char *relpath;
146 
147   /* The copy-from source of this directory, no matter whether it is
148      copied explicitly (the root node of a copy) or implicitly (being an
149      existing child of a copied directory). For a node that is newly
150      added (without history), even inside a copied parent, these are
151      NULL and SVN_INVALID_REVNUM. */
152   const char *copyfrom_path;
153   svn_revnum_t copyfrom_rev;
154 
155   struct directory_baton *parent;
156 };
157 
158 /**
159  * Baton used to represent a node; to be used by the parser
160  * functions. Contains a link to the revision baton.
161  */
162 struct node_baton
163 {
164   const char *path;
165   svn_node_kind_t kind;
166   enum svn_node_action action;
167 
168   /* Is this directory explicitly added? If not, then it already existed
169      or is a child of a copy. */
170   svn_boolean_t is_added;
171 
172   svn_revnum_t copyfrom_rev;
173   const char *copyfrom_path;
174   const char *copyfrom_url;
175 
176   void *file_baton;
177   const char *base_checksum;
178 
179   /* (const char *name) -> (svn_prop_t *) */
180   apr_hash_t *prop_changes;
181 
182   struct revision_baton *rb;
183 };
184 
185 /**
186  * Baton used to represet a revision; used by the parser
187  * functions. Contains a link to the parser baton.
188  */
189 struct revision_baton
190 {
191   svn_revnum_t rev;
192   apr_hash_t *revprop_table;
193   svn_revnum_t head_rev_before_commit;
194   apr_int32_t rev_offset;
195 
196   /* Commit editor and baton used to transfer loaded revisions to
197      the target repository. */
198   const svn_delta_editor_t *commit_editor;
199   void *commit_edit_baton;
200 
201   const svn_string_t *datestamp;
202   const svn_string_t *author;
203 
204   struct parse_baton *pb;
205   struct directory_baton *db;
206   apr_pool_t *pool;
207 };
208 
209 
210 
211 /* Record the mapping of FROM_REV to TO_REV in REV_MAP, ensuring that
212    anything added to the hash is allocated in the hash's pool. */
213 static void
set_revision_mapping(apr_hash_t * rev_map,svn_revnum_t from_rev,svn_revnum_t to_rev)214 set_revision_mapping(apr_hash_t *rev_map,
215                      svn_revnum_t from_rev,
216                      svn_revnum_t to_rev)
217 {
218   svn_revnum_t *mapped_revs = apr_palloc(apr_hash_pool_get(rev_map),
219                                          sizeof(svn_revnum_t) * 2);
220   mapped_revs[0] = from_rev;
221   mapped_revs[1] = to_rev;
222   apr_hash_set(rev_map, mapped_revs, sizeof(*mapped_revs), mapped_revs + 1);
223 }
224 
225 /* Return the revision to which FROM_REV maps in REV_MAP, or
226    SVN_INVALID_REVNUM if no such mapping exists. */
227 static svn_revnum_t
get_revision_mapping(apr_hash_t * rev_map,svn_revnum_t from_rev)228 get_revision_mapping(apr_hash_t *rev_map,
229                      svn_revnum_t from_rev)
230 {
231   svn_revnum_t *to_rev = apr_hash_get(rev_map, &from_rev,
232                                       sizeof(from_rev));
233   return to_rev ? *to_rev : SVN_INVALID_REVNUM;
234 }
235 
236 
237 static svn_error_t *
magic_header_record(int version,void * parse_baton,apr_pool_t * pool)238 magic_header_record(int version,
239             void *parse_baton,
240             apr_pool_t *pool)
241 {
242   return SVN_NO_ERROR;
243 }
244 
245 static svn_error_t *
uuid_record(const char * uuid,void * parse_baton,apr_pool_t * pool)246 uuid_record(const char *uuid,
247             void *parse_baton,
248             apr_pool_t *pool)
249 {
250   return SVN_NO_ERROR;
251 }
252 
253 /* Push information about another directory onto the linked list RB->db.
254  *
255  * CHILD_BATON is the baton returned by the commit editor. RELPATH is the
256  * repository-relative path of this directory. IS_ADDED is true iff this
257  * directory is being added (with or without history). If added with
258  * history then COPYFROM_PATH/COPYFROM_REV are the copyfrom source, else
259  * are NULL/SVN_INVALID_REVNUM.
260  */
261 static void
push_directory(struct revision_baton * rb,void * child_baton,const char * relpath,svn_boolean_t is_added,const char * copyfrom_path,svn_revnum_t copyfrom_rev)262 push_directory(struct revision_baton *rb,
263                void *child_baton,
264                const char *relpath,
265                svn_boolean_t is_added,
266                const char *copyfrom_path,
267                svn_revnum_t copyfrom_rev)
268 {
269   struct directory_baton *child_db = apr_pcalloc(rb->pool, sizeof (*child_db));
270 
271   SVN_ERR_ASSERT_NO_RETURN(
272     is_added || (copyfrom_path == NULL && copyfrom_rev == SVN_INVALID_REVNUM));
273 
274   /* If this node is an existing (not newly added) child of a copied node,
275      calculate where it was copied from. */
276   if (!is_added
277       && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
278     {
279       const char *name = svn_relpath_basename(relpath, NULL);
280 
281       copyfrom_path = svn_relpath_join(rb->db->copyfrom_path, name,
282                                        rb->pool);
283       copyfrom_rev = rb->db->copyfrom_rev;
284     }
285 
286   child_db->baton = child_baton;
287   child_db->relpath = relpath;
288   child_db->copyfrom_path = copyfrom_path;
289   child_db->copyfrom_rev = copyfrom_rev;
290   child_db->parent = rb->db;
291   rb->db = child_db;
292 }
293 
294 /* Called to obtain an editor for the revision described by RB.
295  */
296 static svn_error_t *
revision_start_edit(struct revision_baton * rb,apr_pool_t * scratch_pool)297 revision_start_edit(struct revision_baton *rb,
298                     apr_pool_t *scratch_pool)
299 {
300   struct parse_baton *pb = rb->pb;
301   void *child_baton;
302 
303   SVN_ERR(pb->callbacks->revstart(rb->rev,
304                                   pb->cb_baton,
305                                   &rb->commit_editor, &rb->commit_edit_baton,
306                                   rb->revprop_table,
307                                   rb->pool));
308   SVN_ERR(rb->commit_editor->open_root(rb->commit_edit_baton,
309                                        rb->head_rev_before_commit,
310                                        rb->pool, &child_baton));
311   /* child_baton corresponds to the root directory baton here */
312   push_directory(rb, child_baton, "", TRUE /*is_added*/,
313                  NULL, SVN_INVALID_REVNUM);
314   return SVN_NO_ERROR;
315 }
316 
317 static svn_error_t *
new_revision_record(void ** revision_baton,apr_hash_t * headers,void * parse_baton,apr_pool_t * pool)318 new_revision_record(void **revision_baton,
319                     apr_hash_t *headers,
320                     void *parse_baton,
321                     apr_pool_t *pool)
322 {
323   struct parse_baton *pb = parse_baton;
324   struct revision_baton *rb;
325   const char *rev_str;
326 
327   rb = apr_pcalloc(pool, sizeof(*rb));
328   rb->pool = svn_pool_create(pool);
329   rb->pb = pb;
330   rb->db = NULL;
331 
332   rev_str = svn_hash_gets(headers, SVN_REPOS_DUMPFILE_REVISION_NUMBER);
333   if (rev_str)
334     rb->rev = SVN_STR_TO_REV(rev_str);
335 
336   SVN_ERR(svn_ra_get_latest_revnum(pb->session, &rb->head_rev_before_commit,
337                                    pool));
338 
339   /* FIXME: This is a lame fallback loading multiple segments of dump in
340      several separate operations. It is highly susceptible to race conditions.
341      Calculate the revision 'offset' for finding copyfrom sources.
342      It might be positive or negative. */
343   rb->rev_offset = (apr_int32_t) ((rb->rev) - (rb->head_rev_before_commit + 1));
344 
345   /* Stash the oldest (non-zero) dumpstream revision seen. */
346   if ((rb->rev > 0) && (!SVN_IS_VALID_REVNUM(pb->oldest_dumpstream_rev)))
347     pb->oldest_dumpstream_rev = rb->rev;
348 
349   /* Set the commit_editor/ commit_edit_baton to NULL and wait for
350      them to be created in new_node_record */
351   rb->commit_editor = NULL;
352   rb->commit_edit_baton = NULL;
353   rb->revprop_table = apr_hash_make(rb->pool);
354 
355   *revision_baton = rb;
356   return SVN_NO_ERROR;
357 }
358 
359 static svn_error_t *
new_node_record(void ** node_baton,apr_hash_t * headers,void * revision_baton,apr_pool_t * pool)360 new_node_record(void **node_baton,
361                 apr_hash_t *headers,
362                 void *revision_baton,
363                 apr_pool_t *pool)
364 {
365   struct revision_baton *rb = revision_baton;
366   const struct svn_delta_editor_t *commit_editor = rb->commit_editor;
367   struct node_baton *nb;
368   apr_hash_index_t *hi;
369   void *child_baton;
370   const char *nb_dirname;
371 
372   nb = apr_pcalloc(rb->pool, sizeof(*nb));
373   nb->rb = rb;
374   nb->is_added = FALSE;
375   nb->copyfrom_path = NULL;
376   nb->copyfrom_url = NULL;
377   nb->copyfrom_rev = SVN_INVALID_REVNUM;
378   nb->prop_changes = apr_hash_make(rb->pool);
379 
380   /* If the creation of commit_editor is pending, create it now and
381      open_root on it; also create a top-level directory baton. */
382   if (!commit_editor)
383     {
384       /* The revprop_table should have been filled in with important
385          information like svn:log in set_revision_property. We can now
386          use it all this information to create our commit_editor. But
387          first, clear revprops that we aren't allowed to set with the
388          commit_editor. We'll set them separately using the RA API
389          after closing the editor (see close_revision). */
390 
391       svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_AUTHOR, NULL);
392       svn_hash_sets(rb->revprop_table, SVN_PROP_REVISION_DATE, NULL);
393 
394       SVN_ERR(revision_start_edit(rb, pool));
395       commit_editor = rb->commit_editor;
396     }
397 
398   for (hi = apr_hash_first(rb->pool, headers); hi; hi = apr_hash_next(hi))
399     {
400       const char *hname = apr_hash_this_key(hi);
401       const char *hval = apr_hash_this_val(hi);
402 
403       /* Parse the different kinds of headers we can encounter and
404          stuff them into the node_baton for writing later */
405       if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_PATH) == 0)
406         nb->path = apr_pstrdup(rb->pool, hval);
407       if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_KIND) == 0)
408         nb->kind = strcmp(hval, "file") == 0 ? svn_node_file : svn_node_dir;
409       if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_ACTION) == 0)
410         {
411           if (strcmp(hval, "add") == 0)
412             nb->action = svn_node_action_add;
413           if (strcmp(hval, "change") == 0)
414             nb->action = svn_node_action_change;
415           if (strcmp(hval, "delete") == 0)
416             nb->action = svn_node_action_delete;
417           if (strcmp(hval, "replace") == 0)
418             nb->action = svn_node_action_replace;
419         }
420       if (strcmp(hname, SVN_REPOS_DUMPFILE_TEXT_DELTA_BASE_MD5) == 0)
421         nb->base_checksum = apr_pstrdup(rb->pool, hval);
422       if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_REV) == 0)
423         nb->copyfrom_rev = SVN_STR_TO_REV(hval);
424       if (strcmp(hname, SVN_REPOS_DUMPFILE_NODE_COPYFROM_PATH) == 0)
425         nb->copyfrom_path = apr_pstrdup(rb->pool, hval);
426     }
427 
428   /* Before handling the new node, ensure depth-first editing order by
429      traversing the directory hierarchy from the old node's to the new
430      node's parent directory. */
431   nb_dirname = svn_relpath_dirname(nb->path, pool);
432   if (svn_path_compare_paths(nb_dirname,
433                              rb->db->relpath) != 0)
434     {
435       char *ancestor_path;
436       apr_size_t residual_close_count;
437       apr_array_header_t *residual_open_path;
438       int i;
439       apr_size_t n;
440 
441       ancestor_path =
442         svn_relpath_get_longest_ancestor(nb_dirname,
443                                          rb->db->relpath, pool);
444       residual_close_count =
445         svn_path_component_count(svn_relpath_skip_ancestor(ancestor_path,
446                                                            rb->db->relpath));
447       residual_open_path =
448         svn_path_decompose(svn_relpath_skip_ancestor(ancestor_path,
449                                                      nb_dirname), pool);
450 
451       /* First close all as many directories as there are after
452          skip_ancestor, and then open fresh directories */
453       for (n = 0; n < residual_close_count; n ++)
454         {
455           /* Don't worry about destroying the actual rb->db object,
456              since the pool we're using has the lifetime of one
457              revision anyway */
458           SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
459           rb->db = rb->db->parent;
460         }
461 
462       for (i = 0; i < residual_open_path->nelts; i ++)
463         {
464           char *relpath_compose =
465             svn_relpath_join(rb->db->relpath,
466                              APR_ARRAY_IDX(residual_open_path, i, const char *),
467                              rb->pool);
468           SVN_ERR(commit_editor->open_directory(relpath_compose,
469                                                 rb->db->baton,
470                                                 rb->head_rev_before_commit,
471                                                 rb->pool, &child_baton));
472           push_directory(rb, child_baton, relpath_compose, TRUE /*is_added*/,
473                          NULL, SVN_INVALID_REVNUM);
474         }
475     }
476 
477   /* Fix up the copyfrom information in light of mapped revisions and
478      non-root load targets, and convert copyfrom path into a full
479      URL. */
480   if (nb->copyfrom_path && SVN_IS_VALID_REVNUM(nb->copyfrom_rev))
481     {
482       svn_revnum_t copyfrom_rev;
483 
484       /* Try to find the copyfrom revision in the revision map;
485          failing that, fall back to the revision offset approach. */
486       copyfrom_rev = get_revision_mapping(rb->pb->rev_map, nb->copyfrom_rev);
487       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
488         copyfrom_rev = nb->copyfrom_rev - rb->rev_offset;
489 
490       if (! SVN_IS_VALID_REVNUM(copyfrom_rev))
491         return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
492                                  _("Relative source revision %ld is not"
493                                    " available in current repository"),
494                                  copyfrom_rev);
495 
496       nb->copyfrom_rev = copyfrom_rev;
497 
498       if (rb->pb->parent_dir)
499         nb->copyfrom_path = svn_relpath_join(rb->pb->parent_dir,
500                                              nb->copyfrom_path, rb->pool);
501       /* Convert to a URL, as the commit editor requires. */
502       nb->copyfrom_url = svn_path_url_add_component2(rb->pb->root_url,
503                                                       nb->copyfrom_path,
504                                                       rb->pool);
505     }
506 
507 
508   switch (nb->action)
509     {
510     case svn_node_action_delete:
511     case svn_node_action_replace:
512       SVN_ERR(commit_editor->delete_entry(nb->path,
513                                           rb->head_rev_before_commit,
514                                           rb->db->baton, rb->pool));
515       if (nb->action == svn_node_action_delete)
516         break;
517       else
518         /* FALL THROUGH */;
519     case svn_node_action_add:
520       nb->is_added = TRUE;
521       switch (nb->kind)
522         {
523         case svn_node_file:
524           SVN_ERR(commit_editor->add_file(nb->path, rb->db->baton,
525                                           nb->copyfrom_url,
526                                           nb->copyfrom_rev,
527                                           rb->pool, &(nb->file_baton)));
528           break;
529         case svn_node_dir:
530           SVN_ERR(commit_editor->add_directory(nb->path, rb->db->baton,
531                                                nb->copyfrom_url,
532                                                nb->copyfrom_rev,
533                                                rb->pool, &child_baton));
534           push_directory(rb, child_baton, nb->path, TRUE /*is_added*/,
535                          nb->copyfrom_path, nb->copyfrom_rev);
536           break;
537         default:
538           break;
539         }
540       break;
541     case svn_node_action_change:
542       switch (nb->kind)
543         {
544         case svn_node_file:
545           SVN_ERR(commit_editor->open_file(nb->path, rb->db->baton,
546                                            SVN_INVALID_REVNUM, rb->pool,
547                                            &(nb->file_baton)));
548           break;
549         default:
550           SVN_ERR(commit_editor->open_directory(nb->path, rb->db->baton,
551                                                 rb->head_rev_before_commit,
552                                                 rb->pool, &child_baton));
553           push_directory(rb, child_baton, nb->path, FALSE /*is_added*/,
554                          NULL, SVN_INVALID_REVNUM);
555           break;
556         }
557       break;
558     }
559 
560   *node_baton = nb;
561   return SVN_NO_ERROR;
562 }
563 
564 static svn_error_t *
set_revision_property(void * baton,const char * name,const svn_string_t * value)565 set_revision_property(void *baton,
566                       const char *name,
567                       const svn_string_t *value)
568 {
569   struct revision_baton *rb = baton;
570 
571   SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value,
572                                     rb->pool, rb->pool));
573   SVN_ERR(svn_repos__validate_prop(name, value, rb->pool));
574 
575   if (rb->rev > 0)
576     {
577       if (! svn_hash_gets(rb->pb->skip_revprops, name))
578         svn_hash_sets(rb->revprop_table,
579                       apr_pstrdup(rb->pool, name),
580                       svn_string_dup(value, rb->pool));
581     }
582   else if (rb->head_rev_before_commit == 0
583            && ! svn_hash_gets(rb->pb->skip_revprops, name))
584     {
585       /* Special case: set revision 0 properties directly (which is
586          safe because the commit_editor hasn't been created yet), but
587          only when loading into an 'empty' filesystem. */
588       SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, 0,
589                                       name, NULL, value, rb->pool));
590     }
591 
592   /* Remember any datestamp/ author that passes through (see comment
593      in close_revision). */
594   if (!strcmp(name, SVN_PROP_REVISION_DATE))
595     rb->datestamp = svn_string_dup(value, rb->pool);
596   if (!strcmp(name, SVN_PROP_REVISION_AUTHOR))
597     rb->author = svn_string_dup(value, rb->pool);
598 
599   return SVN_NO_ERROR;
600 }
601 
602 static svn_error_t *
set_node_property(void * baton,const char * name,const svn_string_t * value)603 set_node_property(void *baton,
604                   const char *name,
605                   const svn_string_t *value)
606 {
607   struct node_baton *nb = baton;
608   struct revision_baton *rb = nb->rb;
609   struct parse_baton *pb = rb->pb;
610   apr_pool_t *pool = nb->rb->pool;
611   svn_prop_t *prop;
612 
613   if (value && strcmp(name, SVN_PROP_MERGEINFO) == 0)
614     {
615       svn_string_t *new_value;
616       svn_error_t *err;
617 
618       err = svn_repos__adjust_mergeinfo_property(&new_value, value,
619                                                  pb->parent_dir,
620                                                  pb->rev_map,
621                                                  pb->oldest_dumpstream_rev,
622                                                  rb->rev_offset,
623                                                  NULL, NULL, /*notify*/
624                                                  pool, pool);
625       if (err)
626         {
627           return svn_error_quick_wrap(err,
628                                       _("Invalid svn:mergeinfo value"));
629         }
630 
631       value = new_value;
632     }
633 
634   SVN_ERR(svn_repos__normalize_prop(&value, NULL, name, value, pool, pool));
635 
636   SVN_ERR(svn_repos__validate_prop(name, value, pool));
637 
638   prop = apr_palloc(nb->rb->pool, sizeof (*prop));
639   prop->name = apr_pstrdup(pool, name);
640   prop->value = svn_string_dup(value, pool);
641   svn_hash_sets(nb->prop_changes, prop->name, prop);
642 
643   return SVN_NO_ERROR;
644 }
645 
646 static svn_error_t *
delete_node_property(void * baton,const char * name)647 delete_node_property(void *baton,
648                      const char *name)
649 {
650   struct node_baton *nb = baton;
651   apr_pool_t *pool = nb->rb->pool;
652   svn_prop_t *prop;
653 
654   SVN_ERR(svn_repos__validate_prop(name, NULL, pool));
655 
656   prop = apr_palloc(pool, sizeof (*prop));
657   prop->name = apr_pstrdup(pool, name);
658   prop->value = NULL;
659   svn_hash_sets(nb->prop_changes, prop->name, prop);
660 
661   return SVN_NO_ERROR;
662 }
663 
664 /* Delete all the properties of the node, if any.
665  *
666  * The commit editor doesn't have a method to delete a node's properties
667  * without knowing what they are, so we have to first find out what
668  * properties the node would have had. If it's copied (explicitly or
669  * implicitly), we look at the copy source. If it's only being changed,
670  * we look at the node's current path in the head revision.
671  */
672 static svn_error_t *
remove_node_props(void * baton)673 remove_node_props(void *baton)
674 {
675   struct node_baton *nb = baton;
676   struct revision_baton *rb = nb->rb;
677   apr_pool_t *pool = nb->rb->pool;
678   apr_hash_index_t *hi;
679   apr_hash_t *props;
680   const char *orig_path;
681   svn_revnum_t orig_rev;
682 
683   /* Find the path and revision that has the node's original properties */
684   if (ARE_VALID_COPY_ARGS(nb->copyfrom_path, nb->copyfrom_rev))
685     {
686       orig_path = nb->copyfrom_path;
687       orig_rev = nb->copyfrom_rev;
688     }
689   else if (!nb->is_added
690            && ARE_VALID_COPY_ARGS(rb->db->copyfrom_path, rb->db->copyfrom_rev))
691     {
692       /* If this is a dir, then it's described by rb->db;
693          if this is a file, then it's a child of the dir in rb->db. */
694       orig_path = (nb->kind == svn_node_dir)
695                     ? rb->db->copyfrom_path
696                     : svn_relpath_join(rb->db->copyfrom_path,
697                                        svn_relpath_basename(nb->path, NULL),
698                                        rb->pool);
699       orig_rev = rb->db->copyfrom_rev;
700     }
701   else
702     {
703       /* ### Should we query at a known, fixed, "head" revision number
704          instead of passing SVN_INVALID_REVNUM and getting a moving target? */
705       orig_path = nb->path;
706       orig_rev = SVN_INVALID_REVNUM;
707     }
708 
709   if ((nb->action == svn_node_action_add
710             || nb->action == svn_node_action_replace)
711       && ! ARE_VALID_COPY_ARGS(orig_path, orig_rev))
712     /* Add-without-history; no "old" properties to worry about. */
713     return SVN_NO_ERROR;
714 
715   if (! rb->pb->callbacks->fetch_props)
716     return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
717                             _("This dumpstream reader requires a delta "
718                               "format dumpstream"));
719   SVN_ERR(rb->pb->callbacks->fetch_props(&props,
720                                          rb->pb->cb_baton,
721                                          orig_path, orig_rev, nb->kind,
722                                          pool, pool));
723 
724   for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
725     {
726       const char *name = apr_hash_this_key(hi);
727       svn_prop_kind_t kind = svn_property_kind2(name);
728 
729       if (kind == svn_prop_regular_kind)
730         SVN_ERR(set_node_property(nb, name, NULL));
731     }
732 
733   return SVN_NO_ERROR;
734 }
735 
736 static svn_error_t *
set_fulltext(svn_stream_t ** stream,void * node_baton)737 set_fulltext(svn_stream_t **stream,
738              void *node_baton)
739 {
740   struct node_baton *nb = node_baton;
741   const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
742   svn_txdelta_window_handler_t handler;
743   void *handler_baton;
744   apr_pool_t *pool = nb->rb->pool;
745 
746   SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
747                                          pool, &handler, &handler_baton));
748   *stream = svn_txdelta_target_push(handler, handler_baton,
749                                     svn_stream_empty(pool), pool);
750   return SVN_NO_ERROR;
751 }
752 
753 static svn_error_t *
apply_textdelta(svn_txdelta_window_handler_t * handler,void ** handler_baton,void * node_baton)754 apply_textdelta(svn_txdelta_window_handler_t *handler,
755                 void **handler_baton,
756                 void *node_baton)
757 {
758   struct node_baton *nb = node_baton;
759   const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
760   apr_pool_t *pool = nb->rb->pool;
761 
762   SVN_ERR(commit_editor->apply_textdelta(nb->file_baton, nb->base_checksum,
763                                          pool, handler, handler_baton));
764 
765   return SVN_NO_ERROR;
766 }
767 
768 static svn_error_t *
close_node(void * baton)769 close_node(void *baton)
770 {
771   struct node_baton *nb = baton;
772   const struct svn_delta_editor_t *commit_editor = nb->rb->commit_editor;
773   apr_pool_t *pool = nb->rb->pool;
774   apr_hash_index_t *hi;
775 
776   for (hi = apr_hash_first(pool, nb->prop_changes);
777        hi; hi = apr_hash_next(hi))
778     {
779       const char *name = apr_hash_this_key(hi);
780       svn_prop_t *prop = apr_hash_this_val(hi);
781 
782       switch (nb->kind)
783         {
784         case svn_node_file:
785           SVN_ERR(commit_editor->change_file_prop(nb->file_baton,
786                                                   name, prop->value, pool));
787           break;
788         case svn_node_dir:
789           SVN_ERR(commit_editor->change_dir_prop(nb->rb->db->baton,
790                                                  name, prop->value, pool));
791           break;
792         default:
793           break;
794         }
795     }
796 
797   /* Pass a file node closure through to the editor *unless* we
798      deleted the file (which doesn't require us to open it). */
799   if ((nb->kind == svn_node_file) && (nb->file_baton))
800     {
801       SVN_ERR(commit_editor->close_file(nb->file_baton, NULL, nb->rb->pool));
802     }
803 
804   /* The svn_node_dir case is handled in close_revision */
805 
806   return SVN_NO_ERROR;
807 }
808 
809 static svn_error_t *
close_revision(void * baton)810 close_revision(void *baton)
811 {
812   struct revision_baton *rb = baton;
813   const svn_delta_editor_t *commit_editor = rb->commit_editor;
814   void *commit_edit_baton = rb->commit_edit_baton;
815   svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
816 
817   /* Fake revision 0 */
818   if (rb->rev == 0)
819     {
820       /* ### Don't print directly; generate a notification. */
821       if (! rb->pb->quiet)
822         SVN_ERR(svn_cmdline_printf(rb->pool, "* Loaded revision 0.\n"));
823     }
824   else if (commit_editor)
825     {
826       /* Close all pending open directories, and then close the edit
827          session itself */
828       while (rb->db && rb->db->parent)
829         {
830           SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
831           rb->db = rb->db->parent;
832         }
833       /* root dir's baton */
834       SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
835       SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
836     }
837   else
838     {
839       /* Legitimate revision with no node information */
840       SVN_ERR(revision_start_edit(rb, rb->pool));
841       commit_editor = rb->commit_editor;
842       commit_edit_baton = rb->commit_edit_baton;
843 
844       SVN_ERR(commit_editor->close_directory(rb->db->baton, rb->pool));
845       SVN_ERR(commit_editor->close_edit(commit_edit_baton, rb->pool));
846     }
847 
848   /* svn_fs_commit_txn() rewrites the datestamp and author properties;
849      we'll rewrite them again by hand after closing the commit_editor.
850      The only time we don't do this is for revision 0 when loaded into
851      a non-empty repository.  */
852   if (rb->rev > 0)
853     {
854       committed_rev = get_revision_mapping(rb->pb->rev_map, rb->rev);
855     }
856   else if (rb->head_rev_before_commit == 0)
857     {
858       committed_rev = 0;
859     }
860 
861   if (SVN_IS_VALID_REVNUM(committed_rev))
862     {
863       if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_DATE))
864         {
865           SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
866                                           SVN_PROP_REVISION_DATE,
867                                           NULL, rb->datestamp, rb->pool));
868         }
869       if (!svn_hash_gets(rb->pb->skip_revprops, SVN_PROP_REVISION_AUTHOR))
870         {
871           SVN_ERR(svn_ra_change_rev_prop2(rb->pb->session, committed_rev,
872                                           SVN_PROP_REVISION_AUTHOR,
873                                           NULL, rb->author, rb->pool));
874         }
875     }
876 
877   svn_pool_destroy(rb->pool);
878 
879   return SVN_NO_ERROR;
880 }
881 
882 /*----------------------------------------------------------------------*/
883 
884 /**
885  * Baton used for commit callback (and Ev2 shims).
886  */
887 struct commit_baton_t
888 {
889   svn_revnum_t rev;
890   struct parse_baton *pb;
891 };
892 
893 /*
894  * - Notification of the commit.
895  * - Update the revision number mapping to take account of the actual
896  *   committed revision number.
897  */
898 static svn_error_t *
commit_callback(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)899 commit_callback(const svn_commit_info_t *commit_info,
900                 void *baton,
901                 apr_pool_t *pool)
902 {
903   struct commit_baton_t *cb = baton;
904   struct parse_baton *pb = cb->pb;
905 
906   /* ### Don't print directly; generate a notification. */
907   if (! pb->quiet)
908     SVN_ERR(svn_cmdline_printf(pool, "* Loaded revision %ld.\n",
909                                commit_info->revision));
910 
911   /* Add the mapping of the dumpstream revision to the committed revision. */
912   set_revision_mapping(pb->rev_map, cb->rev, commit_info->revision);
913 
914   /* If the incoming dump stream has non-contiguous revisions (e.g. from
915      using svndumpfilter --drop-empty-revs without --renumber-revs) then
916      we must account for the missing gaps in PB->REV_MAP.  Otherwise we
917      might not be able to map all mergeinfo source revisions to the correct
918      revisions in the target repos. */
919   if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
920       && (cb->rev != pb->last_rev_mapped + 1))
921     {
922       svn_revnum_t i;
923 
924       for (i = pb->last_rev_mapped + 1; i < cb->rev; i++)
925         {
926           set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
927         }
928     }
929 
930   /* Update our "last revision mapped". */
931   pb->last_rev_mapped = cb->rev;
932 
933   return SVN_NO_ERROR;
934 }
935 
936 static svn_error_t *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)937 fetch_base_func(const char **filename,
938                 void *baton,
939                 const char *path,
940                 svn_revnum_t base_revision,
941                 apr_pool_t *result_pool,
942                 apr_pool_t *scratch_pool)
943 {
944   struct commit_baton_t *cb = baton;
945   svn_stream_t *fstream;
946   svn_error_t *err;
947 
948   if (! SVN_IS_VALID_REVNUM(base_revision))
949     base_revision = cb->rev - 1;
950 
951   SVN_ERR(svn_stream_open_unique(&fstream, filename, NULL,
952                                  svn_io_file_del_on_pool_cleanup,
953                                  result_pool, scratch_pool));
954 
955   err = svn_ra_get_file(cb->pb->aux_session, path, base_revision,
956                         fstream, NULL, NULL, scratch_pool);
957   if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
958     {
959       svn_error_clear(err);
960       SVN_ERR(svn_stream_close(fstream));
961 
962       *filename = NULL;
963       return SVN_NO_ERROR;
964     }
965   else if (err)
966     return svn_error_trace(err);
967 
968   SVN_ERR(svn_stream_close(fstream));
969 
970   return SVN_NO_ERROR;
971 }
972 
973 static svn_error_t *
fetch_props(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,svn_node_kind_t node_kind,apr_pool_t * result_pool,apr_pool_t * scratch_pool)974 fetch_props(apr_hash_t **props,
975             void *baton,
976             const char *path,
977             svn_revnum_t base_revision,
978             svn_node_kind_t node_kind,
979             apr_pool_t *result_pool,
980             apr_pool_t *scratch_pool)
981 {
982   struct parse_baton *pb = baton;
983 
984   if (node_kind == svn_node_file)
985     {
986       SVN_ERR(svn_ra_get_file(pb->aux_session, path, base_revision,
987                               NULL, NULL, props, result_pool));
988     }
989   else if (node_kind == svn_node_dir)
990     {
991       apr_array_header_t *tmp_props;
992 
993       SVN_ERR(svn_ra_get_dir2(pb->aux_session, NULL, NULL, props, path,
994                               base_revision, 0 /* Dirent fields */,
995                               result_pool));
996       tmp_props = svn_prop_hash_to_array(*props, result_pool);
997       SVN_ERR(svn_categorize_props(tmp_props, NULL, NULL, &tmp_props,
998                                    result_pool));
999       *props = svn_prop_array_to_hash(tmp_props, result_pool);
1000     }
1001   else
1002     {
1003       *props = apr_hash_make(result_pool);
1004     }
1005 
1006   return SVN_NO_ERROR;
1007 }
1008 
1009 static svn_error_t *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1010 fetch_props_func(apr_hash_t **props,
1011                  void *baton,
1012                  const char *path,
1013                  svn_revnum_t base_revision,
1014                  apr_pool_t *result_pool,
1015                  apr_pool_t *scratch_pool)
1016 {
1017   struct commit_baton_t *cb = baton;
1018   svn_node_kind_t node_kind;
1019 
1020   if (! SVN_IS_VALID_REVNUM(base_revision))
1021     base_revision = cb->rev - 1;
1022 
1023   SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision,
1024                             &node_kind, scratch_pool));
1025   SVN_ERR(fetch_props(props, cb->pb, path, base_revision, node_kind,
1026                       result_pool, scratch_pool));
1027 
1028   return SVN_NO_ERROR;
1029 }
1030 
1031 static svn_error_t *
fetch_kind_func(svn_node_kind_t * kind,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * scratch_pool)1032 fetch_kind_func(svn_node_kind_t *kind,
1033                 void *baton,
1034                 const char *path,
1035                 svn_revnum_t base_revision,
1036                 apr_pool_t *scratch_pool)
1037 {
1038   struct commit_baton_t *cb = baton;
1039 
1040   if (! SVN_IS_VALID_REVNUM(base_revision))
1041     base_revision = cb->rev - 1;
1042 
1043   SVN_ERR(svn_ra_check_path(cb->pb->aux_session, path, base_revision,
1044                             kind, scratch_pool));
1045 
1046   return SVN_NO_ERROR;
1047 }
1048 
1049 static svn_delta_shim_callbacks_t *
get_shim_callbacks(struct commit_baton_t * cb,apr_pool_t * pool)1050 get_shim_callbacks(struct commit_baton_t *cb,
1051                    apr_pool_t *pool)
1052 {
1053   svn_delta_shim_callbacks_t *callbacks =
1054                         svn_delta_shim_callbacks_default(pool);
1055 
1056   callbacks->fetch_props_func = fetch_props_func;
1057   callbacks->fetch_kind_func = fetch_kind_func;
1058   callbacks->fetch_base_func = fetch_base_func;
1059   callbacks->fetch_baton = cb;
1060 
1061   return callbacks;
1062 }
1063 
1064 /*  */
1065 static svn_error_t *
revstart(svn_revnum_t revision,void * baton,const svn_delta_editor_t ** editor_p,void ** edit_baton_p,apr_hash_t * rev_props,apr_pool_t * result_pool)1066 revstart(svn_revnum_t revision,
1067          void *baton,
1068          const svn_delta_editor_t **editor_p,
1069          void **edit_baton_p,
1070          apr_hash_t *rev_props,
1071          apr_pool_t *result_pool)
1072 {
1073   struct parse_baton *pb = baton;
1074   struct commit_baton_t *cb = apr_palloc(result_pool, sizeof(*cb));
1075 
1076   cb->rev = revision;
1077   cb->pb = pb;
1078   SVN_ERR(svn_ra__register_editor_shim_callbacks(pb->session,
1079                                                  get_shim_callbacks(cb, result_pool)));
1080   SVN_ERR(svn_ra_get_commit_editor3(pb->session,
1081                                     editor_p, edit_baton_p,
1082                                     rev_props,
1083                                     commit_callback, cb,
1084                                     NULL, FALSE, result_pool));
1085   return SVN_NO_ERROR;
1086 }
1087 
1088 /* Return an implementation of the dumpstream parser API that will drive
1089  * commits over the RA layer to the location described by SESSION.
1090  *
1091  * Use AUX_SESSION (which is opened to the same URL as SESSION)
1092  * for any secondary, out-of-band RA communications required. This is
1093  * needed when loading a non-deltas dump, and for Ev2.
1094  *
1095  * Print feedback to the console for each revision, unless QUIET is true.
1096  *
1097  * Ignore (don't set) any revision property whose name is a key in
1098  * SKIP_REVPROPS. The values in the hash are unimportant.
1099  */
1100 static svn_error_t *
get_dumpstream_loader(svn_repos_parse_fns3_t ** parser_p,void ** parse_baton_p,svn_ra_session_t * session,svn_ra_session_t * aux_session,svn_boolean_t quiet,apr_hash_t * skip_revprops,apr_pool_t * pool)1101 get_dumpstream_loader(svn_repos_parse_fns3_t **parser_p,
1102                       void **parse_baton_p,
1103                       svn_ra_session_t *session,
1104                       svn_ra_session_t *aux_session,
1105                       svn_boolean_t quiet,
1106                       apr_hash_t *skip_revprops,
1107                       apr_pool_t *pool)
1108 {
1109   svn_repos_parse_fns3_t *parser;
1110   struct parse_baton *parse_baton;
1111   const char *session_url, *root_url, *parent_dir;
1112   static const loader_fns_t callbacks = { revstart, fetch_props };
1113 
1114   SVN_ERR(svn_ra_get_repos_root2(session, &root_url, pool));
1115   SVN_ERR(svn_ra_get_session_url(session, &session_url, pool));
1116   SVN_ERR(svn_ra_get_path_relative_to_root(session, &parent_dir,
1117                                            session_url, pool));
1118 
1119   parser = apr_pcalloc(pool, sizeof(*parser));
1120   parser->magic_header_record = magic_header_record;
1121   parser->uuid_record = uuid_record;
1122   parser->new_revision_record = new_revision_record;
1123   parser->new_node_record = new_node_record;
1124   parser->set_revision_property = set_revision_property;
1125   parser->set_node_property = set_node_property;
1126   parser->delete_node_property = delete_node_property;
1127   parser->remove_node_props = remove_node_props;
1128   parser->set_fulltext = set_fulltext;
1129   parser->apply_textdelta = apply_textdelta;
1130   parser->close_node = close_node;
1131   parser->close_revision = close_revision;
1132 
1133   parse_baton = apr_pcalloc(pool, sizeof(*parse_baton));
1134   parse_baton->session = session;
1135   parse_baton->aux_session = aux_session;
1136   parse_baton->quiet = quiet;
1137   parse_baton->root_url = root_url;
1138   parse_baton->parent_dir = parent_dir;
1139   parse_baton->rev_map = apr_hash_make(pool);
1140   parse_baton->last_rev_mapped = SVN_INVALID_REVNUM;
1141   parse_baton->oldest_dumpstream_rev = SVN_INVALID_REVNUM;
1142   parse_baton->skip_revprops = skip_revprops;
1143   parse_baton->callbacks = &callbacks;
1144   parse_baton->cb_baton = parse_baton;
1145 
1146   *parser_p = parser;
1147   *parse_baton_p = parse_baton;
1148   return SVN_NO_ERROR;
1149 }
1150 
1151 /*----------------------------------------------------------------------*/
1152 
1153 /* Dump-stream parser wrapper */
1154 
1155 struct filter_parse_baton_t
1156 {
1157   const svn_repos_parse_fns3_t *wrapped_parser;
1158   struct parse_baton *wrapped_pb;
1159 };
1160 
1161 struct filter_revision_baton_t
1162 {
1163   struct filter_parse_baton_t *pb;
1164   void *wrapped_rb;
1165 };
1166 
1167 struct filter_node_baton_t
1168 {
1169   struct filter_revision_baton_t *rb;
1170   void *wrapped_nb;
1171 };
1172 
1173 static svn_error_t *
filter_magic_header_record(int version,void * parse_baton,apr_pool_t * pool)1174 filter_magic_header_record(int version,
1175                            void *parse_baton,
1176                            apr_pool_t *pool)
1177 {
1178   struct filter_parse_baton_t *pb = parse_baton;
1179 
1180   SVN_ERR(pb->wrapped_parser->magic_header_record(version, pb->wrapped_pb,
1181                                                   pool));
1182   return SVN_NO_ERROR;
1183 }
1184 
1185 static svn_error_t *
filter_uuid_record(const char * uuid,void * parse_baton,apr_pool_t * pool)1186 filter_uuid_record(const char *uuid,
1187                    void *parse_baton,
1188                    apr_pool_t *pool)
1189 {
1190   struct filter_parse_baton_t *pb = parse_baton;
1191 
1192   SVN_ERR(pb->wrapped_parser->uuid_record(uuid, pb->wrapped_pb, pool));
1193   return SVN_NO_ERROR;
1194 }
1195 
1196 static svn_error_t *
filter_new_revision_record(void ** revision_baton,apr_hash_t * headers,void * parse_baton,apr_pool_t * pool)1197 filter_new_revision_record(void **revision_baton,
1198                            apr_hash_t *headers,
1199                            void *parse_baton,
1200                            apr_pool_t *pool)
1201 {
1202   struct filter_parse_baton_t *pb = parse_baton;
1203   struct filter_revision_baton_t *rb = apr_pcalloc(pool, sizeof (*rb));
1204 
1205   rb->pb = pb;
1206   SVN_ERR(pb->wrapped_parser->new_revision_record(&rb->wrapped_rb, headers,
1207                                                   pb->wrapped_pb, pool));
1208   *revision_baton = rb;
1209   return SVN_NO_ERROR;
1210 }
1211 
1212 static svn_error_t *
filter_new_node_record(void ** node_baton,apr_hash_t * headers,void * revision_baton,apr_pool_t * pool)1213 filter_new_node_record(void **node_baton,
1214                        apr_hash_t *headers,
1215                        void *revision_baton,
1216                        apr_pool_t *pool)
1217 {
1218   struct filter_revision_baton_t *rb = revision_baton;
1219   struct filter_parse_baton_t *pb = rb->pb;
1220   struct filter_node_baton_t *nb = apr_pcalloc(pool, sizeof (*nb));
1221 
1222   nb->rb = rb;
1223   SVN_ERR(pb->wrapped_parser->new_node_record(&nb->wrapped_nb, headers,
1224                                               rb->wrapped_rb, pool));
1225   *node_baton = nb;
1226   return SVN_NO_ERROR;
1227 }
1228 
1229 static svn_error_t *
filter_set_revision_property(void * revision_baton,const char * name,const svn_string_t * value)1230 filter_set_revision_property(void *revision_baton,
1231                              const char *name,
1232                              const svn_string_t *value)
1233 {
1234   struct filter_revision_baton_t *rb = revision_baton;
1235   struct filter_parse_baton_t *pb = rb->pb;
1236 
1237   SVN_ERR(pb->wrapped_parser->set_revision_property(rb->wrapped_rb,
1238                                                     name, value));
1239   return SVN_NO_ERROR;
1240 }
1241 
1242 static svn_error_t *
filter_set_node_property(void * node_baton,const char * name,const svn_string_t * value)1243 filter_set_node_property(void *node_baton,
1244                          const char *name,
1245                          const svn_string_t *value)
1246 {
1247   struct filter_node_baton_t *nb = node_baton;
1248   struct filter_revision_baton_t *rb = nb->rb;
1249   struct filter_parse_baton_t *pb = rb->pb;
1250 
1251   SVN_ERR(pb->wrapped_parser->set_node_property(nb->wrapped_nb,
1252                                                 name, value));
1253   return SVN_NO_ERROR;
1254 }
1255 
1256 static svn_error_t *
filter_delete_node_property(void * node_baton,const char * name)1257 filter_delete_node_property(void *node_baton,
1258                             const char *name)
1259 {
1260   struct filter_node_baton_t *nb = node_baton;
1261   struct filter_revision_baton_t *rb = nb->rb;
1262   struct filter_parse_baton_t *pb = rb->pb;
1263 
1264   SVN_ERR(pb->wrapped_parser->delete_node_property(nb->wrapped_nb, name));
1265   return SVN_NO_ERROR;
1266 }
1267 
1268 static svn_error_t *
filter_remove_node_props(void * node_baton)1269 filter_remove_node_props(void *node_baton)
1270 {
1271   struct filter_node_baton_t *nb = node_baton;
1272   struct filter_revision_baton_t *rb = nb->rb;
1273   struct filter_parse_baton_t *pb = rb->pb;
1274 
1275   SVN_ERR(pb->wrapped_parser->remove_node_props(nb->wrapped_nb));
1276   return SVN_NO_ERROR;
1277 }
1278 
1279 static svn_error_t *
filter_set_fulltext(svn_stream_t ** stream,void * node_baton)1280 filter_set_fulltext(svn_stream_t **stream,
1281                     void *node_baton)
1282 {
1283   struct filter_node_baton_t *nb = node_baton;
1284   struct filter_revision_baton_t *rb = nb->rb;
1285   struct filter_parse_baton_t *pb = rb->pb;
1286 
1287   SVN_ERR(pb->wrapped_parser->set_fulltext(stream, nb->wrapped_nb));
1288   return SVN_NO_ERROR;
1289 }
1290 
1291 static svn_error_t *
filter_apply_textdelta(svn_txdelta_window_handler_t * handler,void ** handler_baton,void * node_baton)1292 filter_apply_textdelta(svn_txdelta_window_handler_t *handler,
1293                        void **handler_baton,
1294                        void *node_baton)
1295 {
1296   struct filter_node_baton_t *nb = node_baton;
1297   struct filter_revision_baton_t *rb = nb->rb;
1298   struct filter_parse_baton_t *pb = rb->pb;
1299 
1300   SVN_ERR(pb->wrapped_parser->apply_textdelta(handler, handler_baton,
1301                                               nb->wrapped_nb));
1302   return SVN_NO_ERROR;
1303 }
1304 
1305 static svn_error_t *
filter_close_node(void * node_baton)1306 filter_close_node(void *node_baton)
1307 {
1308   struct filter_node_baton_t *nb = node_baton;
1309   struct filter_revision_baton_t *rb = nb->rb;
1310   struct filter_parse_baton_t *pb = rb->pb;
1311 
1312   SVN_ERR(pb->wrapped_parser->close_node(nb->wrapped_nb));
1313   return SVN_NO_ERROR;
1314 }
1315 
1316 static svn_error_t *
filter_close_revision(void * revision_baton)1317 filter_close_revision(void *revision_baton)
1318 {
1319   struct filter_revision_baton_t *rb = revision_baton;
1320   struct filter_parse_baton_t *pb = rb->pb;
1321 
1322   SVN_ERR(pb->wrapped_parser->close_revision(rb->wrapped_rb));
1323   return SVN_NO_ERROR;
1324 }
1325 
1326 static svn_error_t *
get_dumpstream_filter(svn_repos_parse_fns3_t ** parser_p,void ** parse_baton_p,const svn_repos_parse_fns3_t * wrapped_parser,void * wrapped_baton,apr_pool_t * pool)1327 get_dumpstream_filter(svn_repos_parse_fns3_t **parser_p,
1328                       void **parse_baton_p,
1329                       const svn_repos_parse_fns3_t *wrapped_parser,
1330                       void *wrapped_baton,
1331                       apr_pool_t *pool)
1332 {
1333   svn_repos_parse_fns3_t *parser;
1334   struct filter_parse_baton_t *b;
1335 
1336   parser = apr_pcalloc(pool, sizeof(*parser));
1337   parser->magic_header_record = filter_magic_header_record;
1338   parser->uuid_record = filter_uuid_record;
1339   parser->new_revision_record = filter_new_revision_record;
1340   parser->new_node_record = filter_new_node_record;
1341   parser->set_revision_property = filter_set_revision_property;
1342   parser->set_node_property = filter_set_node_property;
1343   parser->delete_node_property = filter_delete_node_property;
1344   parser->remove_node_props = filter_remove_node_props;
1345   parser->set_fulltext = filter_set_fulltext;
1346   parser->apply_textdelta = filter_apply_textdelta;
1347   parser->close_node = filter_close_node;
1348   parser->close_revision = filter_close_revision;
1349 
1350   b = apr_pcalloc(pool, sizeof(*b));
1351   b->wrapped_parser = wrapped_parser;
1352   b->wrapped_pb = wrapped_baton;
1353 
1354   *parser_p = parser;
1355   *parse_baton_p = b;
1356   return SVN_NO_ERROR;
1357 }
1358 
1359 /*----------------------------------------------------------------------*/
1360 
1361 /* Implements `svn_ra__lock_retry_func_t'. */
1362 static svn_error_t *
lock_retry_func(void * baton,const svn_string_t * reposlocktoken,apr_pool_t * pool)1363 lock_retry_func(void *baton,
1364                 const svn_string_t *reposlocktoken,
1365                 apr_pool_t *pool)
1366 {
1367   return svn_cmdline_printf(pool,
1368                             _("Failed to get lock on destination "
1369                               "repos, currently held by '%s'\n"),
1370                             reposlocktoken->data);
1371 }
1372 
1373 /* Acquire a lock (of sorts) on the repository associated with the
1374  * given RA SESSION. This lock is just a revprop change attempt in a
1375  * time-delay loop. This function is duplicated by svnsync in
1376  * svnsync/svnsync.c
1377  *
1378  * ### TODO: Make this function more generic and
1379  * expose it through a header for use by other Subversion
1380  * applications to avoid duplication.
1381  */
1382 static svn_error_t *
get_lock(const svn_string_t ** lock_string_p,svn_ra_session_t * session,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1383 get_lock(const svn_string_t **lock_string_p,
1384          svn_ra_session_t *session,
1385          svn_cancel_func_t cancel_func,
1386          void *cancel_baton,
1387          apr_pool_t *pool)
1388 {
1389   svn_boolean_t be_atomic;
1390 
1391   SVN_ERR(svn_ra_has_capability(session, &be_atomic,
1392                                 SVN_RA_CAPABILITY_ATOMIC_REVPROPS,
1393                                 pool));
1394   if (! be_atomic)
1395     {
1396       /* Pre-1.7 servers can't lock without a race condition.  (Issue #3546) */
1397       svn_error_t *err =
1398         svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1399                          _("Target server does not support atomic revision "
1400                            "property edits; consider upgrading it to 1.7."));
1401       svn_handle_warning2(stderr, err, "svnrdump: ");
1402       svn_error_clear(err);
1403     }
1404 
1405   return svn_ra__get_operational_lock(lock_string_p, NULL, session,
1406                                       SVNRDUMP_PROP_LOCK, FALSE,
1407                                       10 /* retries */, lock_retry_func, NULL,
1408                                       cancel_func, cancel_baton, pool);
1409 }
1410 
1411 svn_error_t *
svn_rdump__load_dumpstream(svn_stream_t * stream,svn_ra_session_t * session,svn_ra_session_t * aux_session,svn_boolean_t quiet,apr_hash_t * skip_revprops,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * pool)1412 svn_rdump__load_dumpstream(svn_stream_t *stream,
1413                            svn_ra_session_t *session,
1414                            svn_ra_session_t *aux_session,
1415                            svn_boolean_t quiet,
1416                            apr_hash_t *skip_revprops,
1417                            svn_cancel_func_t cancel_func,
1418                            void *cancel_baton,
1419                            apr_pool_t *pool)
1420 {
1421   svn_repos_parse_fns3_t *parser;
1422   void *parse_baton;
1423   const svn_string_t *lock_string;
1424   svn_error_t *err;
1425 
1426   SVN_ERR(get_lock(&lock_string, session, cancel_func, cancel_baton, pool));
1427 
1428   SVN_ERR(get_dumpstream_loader(&parser, &parse_baton,
1429                                 session, aux_session,
1430                                 quiet, skip_revprops,
1431                                 pool));
1432 
1433   /* Interpose a filtering layer: currently doing nothing */
1434   SVN_ERR(get_dumpstream_filter(&parser, &parse_baton,
1435                                 parser, parse_baton,
1436                                 pool));
1437 
1438   err = svn_repos_parse_dumpstream3(stream, parser, parse_baton, FALSE,
1439                                     cancel_func, cancel_baton, pool);
1440 
1441   /* If all goes well, or if we're cancelled cleanly, don't leave a
1442      stray lock behind. */
1443   if ((! err) || (err && (err->apr_err == SVN_ERR_CANCELLED)))
1444     err = svn_error_compose_create(
1445               svn_ra__release_operational_lock(session, SVNRDUMP_PROP_LOCK,
1446                                                lock_string, pool),
1447               err);
1448   return err;
1449 }
1450