1 /*
2 * ra_loader.c: logic for loading different RA library implementations
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 /* ==================================================================== */
25
26 /*** Includes. ***/
27 #define APR_WANT_STRFUNC
28 #include <apr_want.h>
29
30 #include <apr.h>
31 #include <apr_strings.h>
32 #include <apr_pools.h>
33 #include <apr_hash.h>
34 #include <apr_uri.h>
35
36 #include "svn_hash.h"
37 #include "svn_types.h"
38 #include "svn_error.h"
39 #include "svn_delta.h"
40 #include "svn_ra.h"
41 #include "svn_dirent_uri.h"
42 #include "svn_props.h"
43 #include "svn_iter.h"
44
45 #include "private/svn_branch_compat.h"
46 #include "private/svn_branch_repos.h"
47 #include "private/svn_ra_private.h"
48 #include "private/svn_delta_private.h"
49 #include "private/svn_string_private.h"
50 #include "svnmover.h"
51 #include "svn_private_config.h"
52
53
54 /* Read the branching info string VALUE belonging to revision REVISION.
55 */
56 static svn_error_t *
read_rev_prop(svn_string_t ** value,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,apr_pool_t * result_pool)57 read_rev_prop(svn_string_t **value,
58 svn_ra_session_t *ra_session,
59 const char *branch_info_dir,
60 svn_revnum_t revision,
61 apr_pool_t *result_pool)
62 {
63 apr_pool_t *scratch_pool = result_pool;
64
65 if (branch_info_dir)
66 {
67 const char *file_path;
68 svn_stream_t *stream;
69 svn_error_t *err;
70
71 file_path = svn_dirent_join(branch_info_dir,
72 apr_psprintf(scratch_pool, "branch-info-r%ld",
73 revision), scratch_pool);
74 err = svn_stream_open_readonly(&stream, file_path, scratch_pool, scratch_pool);
75 if (err)
76 {
77 svn_error_clear(err);
78 *value = NULL;
79 return SVN_NO_ERROR;
80 }
81 SVN_ERR(err);
82 SVN_ERR(svn_string_from_stream2(value, stream, 0, result_pool));
83 }
84 else
85 {
86 SVN_ERR(svn_ra_rev_prop(ra_session, revision, "svn-br-info", value,
87 result_pool));
88 }
89 return SVN_NO_ERROR;
90 }
91
92 /* Store the branching info string VALUE belonging to revision REVISION.
93 */
94 static svn_error_t *
write_rev_prop(svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,svn_string_t * value,apr_pool_t * scratch_pool)95 write_rev_prop(svn_ra_session_t *ra_session,
96 const char *branch_info_dir,
97 svn_revnum_t revision,
98 svn_string_t *value,
99 apr_pool_t *scratch_pool)
100 {
101 if (branch_info_dir)
102 {
103 const char *file_path;
104 svn_error_t *err;
105
106 file_path = svn_dirent_join(branch_info_dir,
107 apr_psprintf(scratch_pool, "branch-info-r%ld",
108 revision), scratch_pool);
109 err = svn_io_file_create(file_path, value->data, scratch_pool);
110 if (err)
111 {
112 svn_error_clear(err);
113 SVN_ERR(svn_io_dir_make(branch_info_dir, APR_FPROT_OS_DEFAULT,
114 scratch_pool));
115 err = svn_io_file_create(file_path, value->data, scratch_pool);
116 }
117 SVN_ERR(err);
118 }
119 else
120 {
121 SVN_ERR(svn_ra_change_rev_prop2(ra_session, revision, "svn-br-info",
122 NULL, value, scratch_pool));
123 }
124
125 return SVN_NO_ERROR;
126 }
127
128 /* Create a new revision-root object and read the move-tracking /
129 * branch-tracking metadata from the repository into it.
130 */
131 static svn_error_t *
branch_revision_fetch_info(svn_branch__txn_t ** txn_p,svn_branch__repos_t * repos,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)132 branch_revision_fetch_info(svn_branch__txn_t **txn_p,
133 svn_branch__repos_t *repos,
134 svn_ra_session_t *ra_session,
135 const char *branch_info_dir,
136 svn_revnum_t revision,
137 apr_pool_t *result_pool,
138 apr_pool_t *scratch_pool)
139 {
140 svn_string_t *value;
141 svn_stream_t *stream;
142 svn_branch__txn_t *txn;
143
144 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
145
146 /* Read initial state from repository */
147 SVN_ERR(read_rev_prop(&value, ra_session, branch_info_dir, revision,
148 scratch_pool));
149 if (! value && revision == 0)
150 {
151 value = svn_branch__get_default_r0_metadata(scratch_pool);
152 /*SVN_DBG(("fetch_per_revision_info(r%ld): LOADED DEFAULT INFO:\n%s",
153 revision, value->data));*/
154 SVN_ERR(write_rev_prop(ra_session, branch_info_dir, revision, value,
155 scratch_pool));
156 }
157 else if (! value)
158 {
159 return svn_error_createf(SVN_BRANCH__ERR, NULL,
160 _("Move-tracking metadata not found in r%ld "
161 "in this repository. Run svnmover on an "
162 "empty repository to initialize the "
163 "metadata"), revision);
164 }
165 stream = svn_stream_from_string(value, scratch_pool);
166
167 SVN_ERR(svn_branch__txn_parse(&txn, repos, stream,
168 result_pool, scratch_pool));
169
170 /* Self-test: writing out the info should produce exactly the same string. */
171 {
172 svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
173
174 stream = svn_stream_from_stringbuf(buf, scratch_pool);
175 SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
176 SVN_ERR(svn_stream_close(stream));
177
178 SVN_ERR_ASSERT(svn_string_compare(value,
179 svn_stringbuf__morph_into_string(buf)));
180 }
181
182 *txn_p = txn;
183 return SVN_NO_ERROR;
184 }
185
186 /* Fetch all element payloads in TXN.
187 */
188 static svn_error_t *
txn_fetch_payloads(svn_branch__txn_t * txn,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)189 txn_fetch_payloads(svn_branch__txn_t *txn,
190 svn_branch__compat_fetch_func_t fetch_func,
191 void *fetch_baton,
192 apr_pool_t *result_pool,
193 apr_pool_t *scratch_pool)
194 {
195 apr_array_header_t *branches = svn_branch__txn_get_branches(txn, scratch_pool);
196 int i;
197
198 /* Read payload of each element.
199 (In a real implementation, of course, we'd delay this until demanded.) */
200 for (i = 0; i < branches->nelts; i++)
201 {
202 svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
203 svn_element__tree_t *element_tree;
204 apr_hash_index_t *hi;
205
206 SVN_ERR(svn_branch__state_get_elements(branch, &element_tree,
207 scratch_pool));
208 for (hi = apr_hash_first(scratch_pool, element_tree->e_map);
209 hi; hi = apr_hash_next(hi))
210 {
211 int eid = svn_eid__hash_this_key(hi);
212 svn_element__content_t *element /*= apr_hash_this_val(hi)*/;
213
214 SVN_ERR(svn_branch__state_get_element(branch, &element,
215 eid, scratch_pool));
216 if (! element->payload->is_subbranch_root)
217 {
218 SVN_ERR(svn_branch__compat_fetch(&element->payload,
219 txn,
220 element->payload->branch_ref,
221 fetch_func, fetch_baton,
222 result_pool, scratch_pool));
223 }
224 }
225 }
226
227 return SVN_NO_ERROR;
228 }
229
230 /* Create a new repository object and read the move-tracking /
231 * branch-tracking metadata from the repository into it.
232 */
233 static svn_error_t *
branch_repos_fetch_info(svn_branch__repos_t ** repos_p,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)234 branch_repos_fetch_info(svn_branch__repos_t **repos_p,
235 svn_ra_session_t *ra_session,
236 const char *branch_info_dir,
237 svn_branch__compat_fetch_func_t fetch_func,
238 void *fetch_baton,
239 apr_pool_t *result_pool,
240 apr_pool_t *scratch_pool)
241 {
242 svn_branch__repos_t *repos
243 = svn_branch__repos_create(result_pool);
244 svn_revnum_t base_revision;
245 svn_revnum_t r;
246
247 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_revision, scratch_pool));
248
249 for (r = 0; r <= base_revision; r++)
250 {
251 svn_branch__txn_t *txn;
252
253 SVN_ERR(branch_revision_fetch_info(&txn,
254 repos, ra_session, branch_info_dir,
255 r,
256 result_pool, scratch_pool));
257 SVN_ERR(svn_branch__repos_add_revision(repos, txn));
258 SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
259 result_pool, scratch_pool));
260 }
261
262 *repos_p = repos;
263 return SVN_NO_ERROR;
264 }
265
266 /* Return a mutable state based on revision BASE_REVISION in REPOS.
267 */
268 static svn_error_t *
branch_get_mutable_state(svn_branch__txn_t ** txn_p,svn_branch__repos_t * repos,svn_ra_session_t * ra_session,const char * branch_info_dir,svn_revnum_t base_revision,svn_branch__compat_fetch_func_t fetch_func,void * fetch_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)269 branch_get_mutable_state(svn_branch__txn_t **txn_p,
270 svn_branch__repos_t *repos,
271 svn_ra_session_t *ra_session,
272 const char *branch_info_dir,
273 svn_revnum_t base_revision,
274 svn_branch__compat_fetch_func_t fetch_func,
275 void *fetch_baton,
276 apr_pool_t *result_pool,
277 apr_pool_t *scratch_pool)
278 {
279 svn_branch__txn_t *txn;
280 apr_array_header_t *branches;
281 int i;
282
283 SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(base_revision));
284
285 SVN_ERR(branch_revision_fetch_info(&txn,
286 repos, ra_session, branch_info_dir,
287 base_revision,
288 result_pool, scratch_pool));
289 SVN_ERR_ASSERT(txn->rev == base_revision);
290 SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
291 result_pool, scratch_pool));
292
293 /* Update all the 'predecessor' info to point to the BASE_REVISION instead
294 of to that revision's predecessor. */
295 txn->base_rev = base_revision;
296 txn->rev = SVN_INVALID_REVNUM;
297
298 branches = svn_branch__txn_get_branches(txn, scratch_pool);
299 for (i = 0; i < branches->nelts; i++)
300 {
301 svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
302 svn_branch__history_t *history
303 = svn_branch__history_create_empty(result_pool);
304
305 /* Set each branch's parent to the branch in the base rev */
306 svn_branch__rev_bid_t *parent
307 = svn_branch__rev_bid_create(base_revision,
308 svn_branch__get_id(b, scratch_pool),
309 result_pool);
310
311 svn_hash_sets(history->parents,
312 apr_pstrdup(result_pool, b->bid), parent);
313 SVN_ERR(svn_branch__state_set_history(b, history, scratch_pool));
314 }
315
316 *txn_p = txn;
317 return SVN_NO_ERROR;
318 }
319
320 /* Store the move-tracking / branch-tracking metadata from TXN into the
321 * repository. TXN->rev is the newly committed revision number.
322 */
323 static svn_error_t *
store_repos_info(svn_branch__txn_t * txn,svn_ra_session_t * ra_session,const char * branch_info_dir,apr_pool_t * scratch_pool)324 store_repos_info(svn_branch__txn_t *txn,
325 svn_ra_session_t *ra_session,
326 const char *branch_info_dir,
327 apr_pool_t *scratch_pool)
328 {
329 svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
330 svn_stream_t *stream = svn_stream_from_stringbuf(buf, scratch_pool);
331
332 SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
333
334 SVN_ERR(svn_stream_close(stream));
335 /*SVN_DBG(("store_repos_info: %s", buf->data));*/
336 SVN_ERR(write_rev_prop(ra_session, branch_info_dir, txn->rev,
337 svn_stringbuf__morph_into_string(buf), scratch_pool));
338
339 return SVN_NO_ERROR;
340 }
341
342 struct ccw_baton
343 {
344 svn_commit_callback2_t original_callback;
345 void *original_baton;
346
347 svn_ra_session_t *session;
348 const char *branch_info_dir;
349 svn_branch__txn_t *branching_txn;
350 };
351
352 /* Wrapper which stores the branching/move-tracking info.
353 */
354 static svn_error_t *
commit_callback_wrapper(const svn_commit_info_t * commit_info,void * baton,apr_pool_t * pool)355 commit_callback_wrapper(const svn_commit_info_t *commit_info,
356 void *baton,
357 apr_pool_t *pool)
358 {
359 struct ccw_baton *ccwb = baton;
360
361 /* if this commit used element-branching info, store the new info */
362 if (ccwb->branching_txn)
363 {
364 svn_branch__repos_t *repos = ccwb->branching_txn->repos;
365
366 ccwb->branching_txn->rev = commit_info->revision;
367 SVN_ERR(svn_branch__repos_add_revision(repos, ccwb->branching_txn));
368 SVN_ERR(store_repos_info(ccwb->branching_txn, ccwb->session,
369 ccwb->branch_info_dir, pool));
370 }
371
372 /* call the wrapped callback */
373 if (ccwb->original_callback)
374 {
375 SVN_ERR(ccwb->original_callback(commit_info, ccwb->original_baton, pool));
376 }
377
378 return SVN_NO_ERROR;
379 }
380
381
382 /* Some RA layers do not correctly fill in REPOS_ROOT in commit_info, or
383 they are third-party layers conforming to an older commit_info structure.
384 Interpose a utility function to ensure the field is valid. */
385 static void
remap_commit_callback(svn_commit_callback2_t * callback,void ** callback_baton,svn_ra_session_t * session,svn_branch__txn_t * branching_txn,const char * branch_info_dir,svn_commit_callback2_t original_callback,void * original_baton,apr_pool_t * result_pool)386 remap_commit_callback(svn_commit_callback2_t *callback,
387 void **callback_baton,
388 svn_ra_session_t *session,
389 svn_branch__txn_t *branching_txn,
390 const char *branch_info_dir,
391 svn_commit_callback2_t original_callback,
392 void *original_baton,
393 apr_pool_t *result_pool)
394 {
395 /* Allocate this in RESULT_POOL, since the callback will be called
396 long after this function has returned. */
397 struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
398
399 ccwb->session = session;
400 ccwb->branch_info_dir = apr_pstrdup(result_pool, branch_info_dir);
401 ccwb->branching_txn = branching_txn;
402 ccwb->original_callback = original_callback;
403 ccwb->original_baton = original_baton;
404
405 *callback = commit_callback_wrapper;
406 *callback_baton = ccwb;
407 }
408
409
410 /* Ev3 shims */
411 struct fb_baton {
412 /* A session parented at the repository root */
413 svn_ra_session_t *session;
414 const char *repos_root_url;
415 const char *session_path;
416 };
417
418 /* Fetch kind and/or props and/or text.
419 *
420 * Implements svn_branch__compat_fetch_func_t. */
421 static svn_error_t *
fetch(svn_node_kind_t * kind_p,apr_hash_t ** props_p,svn_stringbuf_t ** file_text,apr_hash_t ** children_names,void * baton,const char * repos_relpath,svn_revnum_t revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)422 fetch(svn_node_kind_t *kind_p,
423 apr_hash_t **props_p,
424 svn_stringbuf_t **file_text,
425 apr_hash_t **children_names,
426 void *baton,
427 const char *repos_relpath,
428 svn_revnum_t revision,
429 apr_pool_t *result_pool,
430 apr_pool_t *scratch_pool)
431 {
432 struct fb_baton *fbb = baton;
433 svn_node_kind_t kind;
434 apr_hash_index_t *hi;
435
436 if (props_p)
437 *props_p = NULL;
438 if (file_text)
439 *file_text = NULL;
440 if (children_names)
441 *children_names = NULL;
442
443 SVN_ERR(svn_ra_check_path(fbb->session, repos_relpath, revision,
444 &kind, scratch_pool));
445 if (kind_p)
446 *kind_p = kind;
447 if (kind == svn_node_file && (props_p || file_text))
448 {
449 svn_stream_t *file_stream = NULL;
450
451 if (file_text)
452 {
453 *file_text = svn_stringbuf_create_empty(result_pool);
454 file_stream = svn_stream_from_stringbuf(*file_text, scratch_pool);
455 }
456 SVN_ERR(svn_ra_get_file(fbb->session, repos_relpath, revision,
457 file_stream, NULL, props_p, result_pool));
458 if (file_text)
459 {
460 SVN_ERR(svn_stream_close(file_stream));
461 }
462 }
463 else if (kind == svn_node_dir && (props_p || children_names))
464 {
465 SVN_ERR(svn_ra_get_dir2(fbb->session,
466 children_names, NULL, props_p,
467 repos_relpath, revision,
468 0 /*minimal child info*/,
469 result_pool));
470 }
471
472 /* Remove non-regular props */
473 if (props_p && *props_p)
474 {
475 for (hi = apr_hash_first(scratch_pool, *props_p); hi; hi = apr_hash_next(hi))
476 {
477 const char *name = apr_hash_this_key(hi);
478
479 if (svn_property_kind2(name) != svn_prop_regular_kind)
480 svn_hash_sets(*props_p, name, NULL);
481
482 }
483 }
484
485 return SVN_NO_ERROR;
486 }
487
488 svn_error_t *
svn_ra_load_branching_state(svn_branch__txn_t ** branching_txn_p,svn_branch__compat_fetch_func_t * fetch_func,void ** fetch_baton,svn_ra_session_t * session,const char * branch_info_dir,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)489 svn_ra_load_branching_state(svn_branch__txn_t **branching_txn_p,
490 svn_branch__compat_fetch_func_t *fetch_func,
491 void **fetch_baton,
492 svn_ra_session_t *session,
493 const char *branch_info_dir,
494 svn_revnum_t base_revision,
495 apr_pool_t *result_pool,
496 apr_pool_t *scratch_pool)
497 {
498 svn_branch__repos_t *repos;
499 const char *repos_root_url, *session_url, *base_relpath;
500 struct fb_baton *fbb = apr_palloc(result_pool, sizeof (*fbb));
501
502 if (base_revision == SVN_INVALID_REVNUM)
503 {
504 SVN_ERR(svn_ra_get_latest_revnum(session, &base_revision, scratch_pool));
505 }
506
507 /* fetcher */
508 SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, result_pool));
509 SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
510 base_relpath = svn_uri_skip_ancestor(repos_root_url, session_url, result_pool);
511 SVN_ERR(svn_ra__dup_session(&fbb->session, session, repos_root_url, result_pool, scratch_pool));
512 fbb->session_path = base_relpath;
513 fbb->repos_root_url = repos_root_url;
514 *fetch_func = fetch;
515 *fetch_baton = fbb;
516
517 SVN_ERR(branch_repos_fetch_info(&repos,
518 session, branch_info_dir,
519 *fetch_func, *fetch_baton,
520 result_pool, scratch_pool));
521 SVN_ERR(branch_get_mutable_state(branching_txn_p,
522 repos, session, branch_info_dir,
523 base_revision,
524 *fetch_func, *fetch_baton,
525 result_pool, scratch_pool));
526
527 return SVN_NO_ERROR;
528 }
529
530 svn_error_t *
svn_ra_get_commit_txn(svn_ra_session_t * session,svn_branch__txn_t ** edit_txn_p,apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,apr_hash_t * lock_tokens,svn_boolean_t keep_locks,const char * branch_info_dir,apr_pool_t * pool)531 svn_ra_get_commit_txn(svn_ra_session_t *session,
532 svn_branch__txn_t **edit_txn_p,
533 apr_hash_t *revprop_table,
534 svn_commit_callback2_t commit_callback,
535 void *commit_baton,
536 apr_hash_t *lock_tokens,
537 svn_boolean_t keep_locks,
538 const char *branch_info_dir,
539 apr_pool_t *pool)
540 {
541 svn_branch__txn_t *branching_txn;
542 svn_branch__compat_fetch_func_t fetch_func;
543 void *fetch_baton;
544 const svn_delta_editor_t *deditor;
545 void *dedit_baton;
546 svn_branch__compat_shim_connector_t *shim_connector;
547
548 /* load branching info
549 * ### Currently we always start from a single base revision, never from
550 * a mixed-rev state */
551 SVN_ERR(svn_ra_load_branching_state(&branching_txn, &fetch_func, &fetch_baton,
552 session, branch_info_dir,
553 SVN_INVALID_REVNUM /*base_revision*/,
554 pool, pool));
555
556 /* arrange for branching info to be stored after commit */
557 remap_commit_callback(&commit_callback, &commit_baton,
558 session, branching_txn, branch_info_dir,
559 commit_callback, commit_baton, pool);
560
561 SVN_ERR(svn_ra_get_commit_editor3(session, &deditor, &dedit_baton,
562 revprop_table,
563 commit_callback, commit_baton,
564 lock_tokens, keep_locks, pool));
565
566 /* Convert to Ev3 */
567 {
568 const char *repos_root_url;
569
570 SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, pool));
571
572 /*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton,
573 deditor, dedit_baton, "", pool));*/
574 SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
575 edit_txn_p,
576 &shim_connector,
577 deditor, dedit_baton, branching_txn,
578 repos_root_url,
579 fetch_func, fetch_baton,
580 NULL, NULL /*cancel*/,
581 pool, pool));
582 }
583
584 return SVN_NO_ERROR;
585 }
586
587