1/*
2 * editor.c:  compatibility editors
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24#include <apr_pools.h>
25
26#include "svn_error.h"
27#include "svn_pools.h"
28#include "svn_ra.h"
29#include "svn_delta.h"
30#include "svn_dirent_uri.h"
31
32#include "private/svn_ra_private.h"
33#include "private/svn_delta_private.h"
34#include "private/svn_editor.h"
35
36#include "ra_loader.h"
37#include "svn_private_config.h"
38
39
40struct fp_baton {
41  svn_ra__provide_props_cb_t provide_props_cb;
42  void *cb_baton;
43};
44
45struct fb_baton {
46  svn_ra__provide_base_cb_t provide_base_cb;
47  void *cb_baton;
48};
49
50/* The shims currently want a callback that provides props for a given
51   REPOS_RELPATH at a given BASE_REVISION. However, the RA Ev2 interface
52   has a callback that provides properties for the REPOS_RELPATH from any
53   revision, which is returned along with the properties.
54
55   This is a little shim to map between the prototypes. The base revision
56   for the properties is discarded, and the requested revision (from the
57   shim code) is ignored.
58
59   The shim code needs to be updated to allow for an RA-style callback
60   to fetch properties.  */
61static svn_error_t *
62fetch_props(apr_hash_t **props,
63            void *baton,
64            const char *repos_relpath,
65            svn_revnum_t base_revision,
66            apr_pool_t *result_pool,
67            apr_pool_t *scratch_pool)
68{
69  struct fp_baton *fpb = baton;
70  svn_revnum_t unused_revision;
71
72  /* Ignored: BASE_REVISION.  */
73
74  return svn_error_trace(fpb->provide_props_cb(props, &unused_revision,
75                                               fpb->cb_baton,
76                                               repos_relpath,
77                                               result_pool, scratch_pool));
78}
79
80/* See note above regarding BASE_REVISION.
81   This also pulls down the entire contents of the file stream from the
82   RA layer and stores them in a local file, returning the path.
83*/
84static svn_error_t *
85fetch_base(const char **filename,
86           void *baton,
87           const char *repos_relpath,
88           svn_revnum_t base_revision,
89           apr_pool_t *result_pool,
90           apr_pool_t *scratch_pool)
91{
92  struct fb_baton *fbb = baton;
93  svn_revnum_t unused_revision;
94  svn_stream_t *contents;
95  svn_stream_t *file_stream;
96  const char *tmp_filename;
97
98  /* Ignored: BASE_REVISION.  */
99
100  SVN_ERR(fbb->provide_base_cb(&contents, &unused_revision, fbb->cb_baton,
101                               repos_relpath, result_pool, scratch_pool));
102
103  SVN_ERR(svn_stream_open_unique(&file_stream, &tmp_filename, NULL,
104                                 svn_io_file_del_on_pool_cleanup,
105                                 scratch_pool, scratch_pool));
106  SVN_ERR(svn_stream_copy3(contents, file_stream, NULL, NULL, scratch_pool));
107
108  *filename = apr_pstrdup(result_pool, tmp_filename);
109
110
111
112  return SVN_NO_ERROR;
113}
114
115
116
117svn_error_t *
118svn_ra__use_commit_shim(svn_editor_t **editor,
119                        svn_ra_session_t *session,
120                        apr_hash_t *revprop_table,
121                        svn_commit_callback2_t commit_callback,
122                        void *commit_baton,
123                        apr_hash_t *lock_tokens,
124                        svn_boolean_t keep_locks,
125                        svn_ra__provide_base_cb_t provide_base_cb,
126                        svn_ra__provide_props_cb_t provide_props_cb,
127                        svn_ra__get_copysrc_kind_cb_t get_copysrc_kind_cb,
128                        void *cb_baton,
129                        svn_cancel_func_t cancel_func,
130                        void *cancel_baton,
131                        apr_pool_t *result_pool,
132                        apr_pool_t *scratch_pool)
133{
134  const svn_delta_editor_t *deditor;
135  void *dedit_baton;
136  struct svn_delta__extra_baton *exb;
137  svn_delta__unlock_func_t unlock_func;
138  void *unlock_baton;
139  const char *repos_root;
140  const char *session_url;
141  const char *base_relpath;
142  svn_boolean_t *found_abs_paths;
143  struct fp_baton *fpb;
144
145  /* NOTE: PROVIDE_BASE_CB is currently unused by this shim. In the future,
146     we can pass it to the underlying Ev2/Ev1 shim to produce better
147     apply_txdelta drives (ie. against a base rather than <empty>).  */
148
149  /* Fetch the RA provider's Ev1 commit editor.  */
150  SVN_ERR(session->vtable->get_commit_editor(session, &deditor, &dedit_baton,
151                                             revprop_table,
152                                             commit_callback, commit_baton,
153                                             lock_tokens, keep_locks,
154                                             result_pool));
155
156  /* Get or calculate the appropriate repos root and base relpath. */
157  SVN_ERR(svn_ra_get_repos_root2(session, &repos_root, scratch_pool));
158  SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
159  base_relpath = svn_uri_skip_ancestor(repos_root, session_url, scratch_pool);
160
161  /* We will assume that when the underlying Ev1 editor is finally driven
162     by the shim, that we will not need to prepend "/" to the paths. Place
163     this on the heap because it is examined much later. Set to FALSE.  */
164  found_abs_paths = apr_pcalloc(result_pool, sizeof(*found_abs_paths));
165
166  /* The PROVIDE_PROPS_CB callback does not match what the shims want.
167     Let's jigger things around a little bit here.  */
168  fpb = apr_palloc(result_pool, sizeof(*fpb));
169  fpb->provide_props_cb = provide_props_cb;
170  fpb->cb_baton = cb_baton;
171
172  /* Create the Ev2 editor from the Ev1 editor provided by the RA layer.
173
174     Note: GET_COPYSRC_KIND_CB is compatible in type/semantics with the
175     shim's FETCH_KIND_FUNC parameter.  */
176  SVN_ERR(svn_delta__editor_from_delta(editor, &exb,
177                                       &unlock_func, &unlock_baton,
178                                       deditor, dedit_baton,
179                                       found_abs_paths,
180                                       repos_root, base_relpath,
181                                       cancel_func, cancel_baton,
182                                       get_copysrc_kind_cb, cb_baton,
183                                       fetch_props, fpb,
184                                       result_pool, scratch_pool));
185
186  /* Note: UNLOCK_FUNC and UNLOCK_BATON are unused during commit drives.
187     We can safely drop them on the floor.  */
188
189  /* Since we're (currently) just wrapping an existing Ev1 editor, we have
190     to call any start_edit handler it may provide (the shim uses this to
191     invoke Ev1's open_root callback).  We've got a couple of options to do
192     so: Implement a wrapper editor and call the start_edit callback upon
193     the first invocation of any of the underlying editor's functions; or,
194     just assume our consumer is going to eventually use the editor it is
195     asking for, and call the start edit callback now.  For simplicity's
196     sake, we do the latter.  */
197  if (exb->start_edit)
198    {
199      /* Most commit drives pass SVN_INVALID_REVNUM for the revision.
200         All calls to svn_delta_path_driver() pass SVN_INVALID_REVNUM,
201         so this is fine for any commits done via that function.
202
203         Notably, the PROPSET command passes a specific revision. Before
204         PROPSET can use the RA Ev2 interface, we may need to make this
205         revision a parameter.
206         ### what are the exact semantics? what is the meaning of the
207         ### revision passed to the Ev1->open_root() callback?  */
208      SVN_ERR(exb->start_edit(exb->baton, SVN_INVALID_REVNUM));
209    }
210
211  /* Note: EXB also contains a TARGET_REVISION function, but that is not
212     used during commit operations. We can safely ignore it. (ie. it is
213     in EXB for use by paired-shims)  */
214
215  return SVN_NO_ERROR;
216}
217
218
219struct wrapped_replay_baton_t {
220  svn_ra__replay_revstart_ev2_callback_t revstart_func;
221  svn_ra__replay_revfinish_ev2_callback_t revfinish_func;
222  void *replay_baton;
223
224  svn_ra_session_t *session;
225
226  svn_ra__provide_base_cb_t provide_base_cb;
227  svn_ra__provide_props_cb_t provide_props_cb;
228  void *cb_baton;
229
230  /* This will be populated by the revstart wrapper. */
231  svn_editor_t *editor;
232};
233
234static svn_error_t *
235revstart_func_wrapper(svn_revnum_t revision,
236                      void *replay_baton,
237                      const svn_delta_editor_t **deditor,
238                      void **dedit_baton,
239                      apr_hash_t *rev_props,
240                      apr_pool_t *result_pool)
241{
242  struct wrapped_replay_baton_t *wrb = replay_baton;
243  const char *repos_root;
244  const char *session_url;
245  const char *base_relpath;
246  svn_boolean_t *found_abs_paths;
247  struct fp_baton *fpb;
248  struct svn_delta__extra_baton *exb;
249
250  /* Get the Ev2 editor from the original revstart func. */
251  SVN_ERR(wrb->revstart_func(revision, wrb->replay_baton, &wrb->editor,
252                             rev_props, result_pool));
253
254  /* Get or calculate the appropriate repos root and base relpath. */
255  SVN_ERR(svn_ra_get_repos_root2(wrb->session, &repos_root, result_pool));
256  SVN_ERR(svn_ra_get_session_url(wrb->session, &session_url, result_pool));
257  base_relpath = svn_uri_skip_ancestor(repos_root, session_url, result_pool);
258
259  /* We will assume that when the underlying Ev1 editor is finally driven
260     by the shim, that we will not need to prepend "/" to the paths. Place
261     this on the heap because it is examined much later. Set to FALSE.  */
262  found_abs_paths = apr_pcalloc(result_pool, sizeof(*found_abs_paths));
263
264  /* The PROVIDE_PROPS_CB callback does not match what the shims want.
265     Let's jigger things around a little bit here.  */
266  fpb = apr_palloc(result_pool, sizeof(*fpb));
267  fpb->provide_props_cb = wrb->provide_props_cb;
268  fpb->cb_baton = wrb->cb_baton;
269
270  /* Create the extra baton. */
271  exb = apr_pcalloc(result_pool, sizeof(*exb));
272
273  /* Create the Ev1 editor from the Ev2 editor provided by the RA layer.
274
275     Note: GET_COPYSRC_KIND_CB is compatible in type/semantics with the
276     shim's FETCH_KIND_FUNC parameter.  */
277  SVN_ERR(svn_delta__delta_from_editor(deditor, dedit_baton, wrb->editor,
278                                       NULL, NULL,
279                                       found_abs_paths,
280                                       repos_root, base_relpath,
281                                       fetch_props, wrb->cb_baton,
282                                       fetch_base, wrb->cb_baton,
283                                       exb, result_pool));
284
285  return SVN_NO_ERROR;
286}
287
288static svn_error_t *
289revfinish_func_wrapper(svn_revnum_t revision,
290                       void *replay_baton,
291                       const svn_delta_editor_t *editor,
292                       void *edit_baton,
293                       apr_hash_t *rev_props,
294                       apr_pool_t *pool)
295{
296  struct wrapped_replay_baton_t *wrb = replay_baton;
297
298  SVN_ERR(wrb->revfinish_func(revision, replay_baton, wrb->editor, rev_props,
299                              pool));
300
301  return SVN_NO_ERROR;
302}
303
304svn_error_t *
305svn_ra__use_replay_range_shim(svn_ra_session_t *session,
306                              svn_revnum_t start_revision,
307                              svn_revnum_t end_revision,
308                              svn_revnum_t low_water_mark,
309                              svn_boolean_t send_deltas,
310                              svn_ra__replay_revstart_ev2_callback_t revstart_func,
311                              svn_ra__replay_revfinish_ev2_callback_t revfinish_func,
312                              void *replay_baton,
313                              svn_ra__provide_base_cb_t provide_base_cb,
314                              svn_ra__provide_props_cb_t provide_props_cb,
315                              void *cb_baton,
316                              apr_pool_t *scratch_pool)
317{
318  /* The basic strategy here is to wrap the callback start and finish
319     functions to appropriately return an Ev1 editor which is itself wrapped
320     from the Ev2 one the provided callbacks will give us. */
321
322  struct wrapped_replay_baton_t *wrb = apr_pcalloc(scratch_pool, sizeof(*wrb));
323
324  wrb->revstart_func = revstart_func;
325  wrb->revfinish_func = revfinish_func;
326  wrb->replay_baton = replay_baton;
327  wrb->session = session;
328
329  wrb->provide_base_cb = provide_base_cb;
330  wrb->provide_props_cb = provide_props_cb;
331  wrb->cb_baton = cb_baton;
332
333  return svn_error_trace(svn_ra_replay_range(session, start_revision,
334                                             end_revision, low_water_mark,
335                                             send_deltas,
336                                             revstart_func_wrapper,
337                                             revfinish_func_wrapper,
338                                             wrb, scratch_pool));
339}
340