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