blob: f7293b2c65536e9a82a021e9fcf14bc390b3c454 [file] [log] [blame]
/*
* ra_loader.c: logic for loading different RA library implementations
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
/* ==================================================================== */
/*** Includes. ***/
#define APR_WANT_STRFUNC
#include <apr_want.h>
#include <apr.h>
#include <apr_strings.h>
#include <apr_pools.h>
#include <apr_hash.h>
#include <apr_uri.h>
#include "svn_hash.h"
#include "svn_types.h"
#include "svn_error.h"
#include "svn_delta.h"
#include "svn_ra.h"
#include "svn_dirent_uri.h"
#include "svn_props.h"
#include "svn_iter.h"
#include "private/svn_branch_compat.h"
#include "private/svn_branch_repos.h"
#include "private/svn_ra_private.h"
#include "private/svn_delta_private.h"
#include "private/svn_string_private.h"
#include "svnmover.h"
#include "svn_private_config.h"
/* Read the branching info string VALUE belonging to revision REVISION.
*/
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)
{
apr_pool_t *scratch_pool = result_pool;
if (branch_info_dir)
{
const char *file_path;
svn_stream_t *stream;
svn_error_t *err;
file_path = svn_dirent_join(branch_info_dir,
apr_psprintf(scratch_pool, "branch-info-r%ld",
revision), scratch_pool);
err = svn_stream_open_readonly(&stream, file_path, scratch_pool, scratch_pool);
if (err)
{
svn_error_clear(err);
*value = NULL;
return SVN_NO_ERROR;
}
SVN_ERR(err);
SVN_ERR(svn_string_from_stream2(value, stream, 0, result_pool));
}
else
{
SVN_ERR(svn_ra_rev_prop(ra_session, revision, "svn-br-info", value,
result_pool));
}
return SVN_NO_ERROR;
}
/* Store the branching info string VALUE belonging to revision REVISION.
*/
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)
{
if (branch_info_dir)
{
const char *file_path;
svn_error_t *err;
file_path = svn_dirent_join(branch_info_dir,
apr_psprintf(scratch_pool, "branch-info-r%ld",
revision), scratch_pool);
err = svn_io_file_create(file_path, value->data, scratch_pool);
if (err)
{
svn_error_clear(err);
SVN_ERR(svn_io_dir_make(branch_info_dir, APR_FPROT_OS_DEFAULT,
scratch_pool));
err = svn_io_file_create(file_path, value->data, scratch_pool);
}
SVN_ERR(err);
}
else
{
SVN_ERR(svn_ra_change_rev_prop2(ra_session, revision, "svn-br-info",
NULL, value, scratch_pool));
}
return SVN_NO_ERROR;
}
/* Create a new revision-root object and read the move-tracking /
* branch-tracking metadata from the repository into it.
*/
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)
{
svn_string_t *value;
svn_stream_t *stream;
svn_branch__txn_t *txn;
SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
/* Read initial state from repository */
SVN_ERR(read_rev_prop(&value, ra_session, branch_info_dir, revision,
scratch_pool));
if (! value && revision == 0)
{
value = svn_branch__get_default_r0_metadata(scratch_pool);
/*SVN_DBG(("fetch_per_revision_info(r%ld): LOADED DEFAULT INFO:\n%s",
revision, value->data));*/
SVN_ERR(write_rev_prop(ra_session, branch_info_dir, revision, value,
scratch_pool));
}
else if (! value)
{
return svn_error_createf(SVN_BRANCH__ERR, NULL,
_("Move-tracking metadata not found in r%ld "
"in this repository. Run svnmover on an "
"empty repository to initialize the "
"metadata"), revision);
}
stream = svn_stream_from_string(value, scratch_pool);
SVN_ERR(svn_branch__txn_parse(&txn, repos, stream,
result_pool, scratch_pool));
/* Self-test: writing out the info should produce exactly the same string. */
{
svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
stream = svn_stream_from_stringbuf(buf, scratch_pool);
SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
SVN_ERR(svn_stream_close(stream));
SVN_ERR_ASSERT(svn_string_compare(value,
svn_stringbuf__morph_into_string(buf)));
}
*txn_p = txn;
return SVN_NO_ERROR;
}
/* Fetch all element payloads in TXN.
*/
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)
{
apr_array_header_t *branches = svn_branch__txn_get_branches(txn, scratch_pool);
int i;
/* Read payload of each element.
(In a real implementation, of course, we'd delay this until demanded.) */
for (i = 0; i < branches->nelts; i++)
{
svn_branch__state_t *branch = APR_ARRAY_IDX(branches, i, void *);
svn_element__tree_t *element_tree;
apr_hash_index_t *hi;
SVN_ERR(svn_branch__state_get_elements(branch, &element_tree,
scratch_pool));
for (hi = apr_hash_first(scratch_pool, element_tree->e_map);
hi; hi = apr_hash_next(hi))
{
int eid = svn_eid__hash_this_key(hi);
svn_element__content_t *element /*= apr_hash_this_val(hi)*/;
SVN_ERR(svn_branch__state_get_element(branch, &element,
eid, scratch_pool));
if (! element->payload->is_subbranch_root)
{
SVN_ERR(svn_branch__compat_fetch(&element->payload,
txn,
element->payload->branch_ref,
fetch_func, fetch_baton,
result_pool, scratch_pool));
}
}
}
return SVN_NO_ERROR;
}
/* Create a new repository object and read the move-tracking /
* branch-tracking metadata from the repository into it.
*/
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)
{
svn_branch__repos_t *repos
= svn_branch__repos_create(result_pool);
svn_revnum_t base_revision;
svn_revnum_t r;
SVN_ERR(svn_ra_get_latest_revnum(ra_session, &base_revision, scratch_pool));
for (r = 0; r <= base_revision; r++)
{
svn_branch__txn_t *txn;
SVN_ERR(branch_revision_fetch_info(&txn,
repos, ra_session, branch_info_dir,
r,
result_pool, scratch_pool));
SVN_ERR(svn_branch__repos_add_revision(repos, txn));
SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
result_pool, scratch_pool));
}
*repos_p = repos;
return SVN_NO_ERROR;
}
/* Return a mutable state based on revision BASE_REVISION in REPOS.
*/
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)
{
svn_branch__txn_t *txn;
apr_array_header_t *branches;
int i;
SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(base_revision));
SVN_ERR(branch_revision_fetch_info(&txn,
repos, ra_session, branch_info_dir,
base_revision,
result_pool, scratch_pool));
SVN_ERR_ASSERT(txn->rev == base_revision);
SVN_ERR(txn_fetch_payloads(txn, fetch_func, fetch_baton,
result_pool, scratch_pool));
/* Update all the 'predecessor' info to point to the BASE_REVISION instead
of to that revision's predecessor. */
txn->base_rev = base_revision;
txn->rev = SVN_INVALID_REVNUM;
branches = svn_branch__txn_get_branches(txn, scratch_pool);
for (i = 0; i < branches->nelts; i++)
{
svn_branch__state_t *b = APR_ARRAY_IDX(branches, i, void *);
svn_branch__history_t *history
= svn_branch__history_create_empty(result_pool);
/* Set each branch's parent to the branch in the base rev */
svn_branch__rev_bid_t *parent
= svn_branch__rev_bid_create(base_revision,
svn_branch__get_id(b, scratch_pool),
result_pool);
svn_hash_sets(history->parents,
apr_pstrdup(result_pool, b->bid), parent);
SVN_ERR(svn_branch__state_set_history(b, history, scratch_pool));
}
*txn_p = txn;
return SVN_NO_ERROR;
}
/* Store the move-tracking / branch-tracking metadata from TXN into the
* repository. TXN->rev is the newly committed revision number.
*/
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)
{
svn_stringbuf_t *buf = svn_stringbuf_create_empty(scratch_pool);
svn_stream_t *stream = svn_stream_from_stringbuf(buf, scratch_pool);
SVN_ERR(svn_branch__txn_serialize(txn, stream, scratch_pool));
SVN_ERR(svn_stream_close(stream));
/*SVN_DBG(("store_repos_info: %s", buf->data));*/
SVN_ERR(write_rev_prop(ra_session, branch_info_dir, txn->rev,
svn_stringbuf__morph_into_string(buf), scratch_pool));
return SVN_NO_ERROR;
}
struct ccw_baton
{
svn_commit_callback2_t original_callback;
void *original_baton;
svn_ra_session_t *session;
const char *branch_info_dir;
svn_branch__txn_t *branching_txn;
};
/* Wrapper which stores the branching/move-tracking info.
*/
static svn_error_t *
commit_callback_wrapper(const svn_commit_info_t *commit_info,
void *baton,
apr_pool_t *pool)
{
struct ccw_baton *ccwb = baton;
/* if this commit used element-branching info, store the new info */
if (ccwb->branching_txn)
{
svn_branch__repos_t *repos = ccwb->branching_txn->repos;
ccwb->branching_txn->rev = commit_info->revision;
SVN_ERR(svn_branch__repos_add_revision(repos, ccwb->branching_txn));
SVN_ERR(store_repos_info(ccwb->branching_txn, ccwb->session,
ccwb->branch_info_dir, pool));
}
/* call the wrapped callback */
if (ccwb->original_callback)
{
SVN_ERR(ccwb->original_callback(commit_info, ccwb->original_baton, pool));
}
return SVN_NO_ERROR;
}
/* Some RA layers do not correctly fill in REPOS_ROOT in commit_info, or
they are third-party layers conforming to an older commit_info structure.
Interpose a utility function to ensure the field is valid. */
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)
{
/* Allocate this in RESULT_POOL, since the callback will be called
long after this function has returned. */
struct ccw_baton *ccwb = apr_palloc(result_pool, sizeof(*ccwb));
ccwb->session = session;
ccwb->branch_info_dir = apr_pstrdup(result_pool, branch_info_dir);
ccwb->branching_txn = branching_txn;
ccwb->original_callback = original_callback;
ccwb->original_baton = original_baton;
*callback = commit_callback_wrapper;
*callback_baton = ccwb;
}
/* Ev3 shims */
struct fb_baton {
/* A session parented at the repository root */
svn_ra_session_t *session;
const char *repos_root_url;
const char *session_path;
};
/* Fetch kind and/or props and/or text.
*
* Implements svn_branch__compat_fetch_func_t. */
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)
{
struct fb_baton *fbb = baton;
svn_node_kind_t kind;
apr_hash_index_t *hi;
if (props_p)
*props_p = NULL;
if (file_text)
*file_text = NULL;
if (children_names)
*children_names = NULL;
SVN_ERR(svn_ra_check_path(fbb->session, repos_relpath, revision,
&kind, scratch_pool));
if (kind_p)
*kind_p = kind;
if (kind == svn_node_file && (props_p || file_text))
{
svn_stream_t *file_stream = NULL;
if (file_text)
{
*file_text = svn_stringbuf_create_empty(result_pool);
file_stream = svn_stream_from_stringbuf(*file_text, scratch_pool);
}
SVN_ERR(svn_ra_get_file(fbb->session, repos_relpath, revision,
file_stream, NULL, props_p, result_pool));
if (file_text)
{
SVN_ERR(svn_stream_close(file_stream));
}
}
else if (kind == svn_node_dir && (props_p || children_names))
{
SVN_ERR(svn_ra_get_dir2(fbb->session,
children_names, NULL, props_p,
repos_relpath, revision,
0 /*minimal child info*/,
result_pool));
}
/* Remove non-regular props */
if (props_p && *props_p)
{
for (hi = apr_hash_first(scratch_pool, *props_p); hi; hi = apr_hash_next(hi))
{
const char *name = apr_hash_this_key(hi);
if (svn_property_kind2(name) != svn_prop_regular_kind)
svn_hash_sets(*props_p, name, NULL);
}
}
return SVN_NO_ERROR;
}
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)
{
svn_branch__repos_t *repos;
const char *repos_root_url, *session_url, *base_relpath;
struct fb_baton *fbb = apr_palloc(result_pool, sizeof (*fbb));
if (base_revision == SVN_INVALID_REVNUM)
{
SVN_ERR(svn_ra_get_latest_revnum(session, &base_revision, scratch_pool));
}
/* fetcher */
SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, result_pool));
SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
base_relpath = svn_uri_skip_ancestor(repos_root_url, session_url, result_pool);
SVN_ERR(svn_ra__dup_session(&fbb->session, session, repos_root_url, result_pool, scratch_pool));
fbb->session_path = base_relpath;
fbb->repos_root_url = repos_root_url;
*fetch_func = fetch;
*fetch_baton = fbb;
SVN_ERR(branch_repos_fetch_info(&repos,
session, branch_info_dir,
*fetch_func, *fetch_baton,
result_pool, scratch_pool));
SVN_ERR(branch_get_mutable_state(branching_txn_p,
repos, session, branch_info_dir,
base_revision,
*fetch_func, *fetch_baton,
result_pool, scratch_pool));
return SVN_NO_ERROR;
}
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)
{
svn_branch__txn_t *branching_txn;
svn_branch__compat_fetch_func_t fetch_func;
void *fetch_baton;
const svn_delta_editor_t *deditor;
void *dedit_baton;
svn_branch__compat_shim_connector_t *shim_connector;
/* load branching info
* ### Currently we always start from a single base revision, never from
* a mixed-rev state */
SVN_ERR(svn_ra_load_branching_state(&branching_txn, &fetch_func, &fetch_baton,
session, branch_info_dir,
SVN_INVALID_REVNUM /*base_revision*/,
pool, pool));
/* arrange for branching info to be stored after commit */
remap_commit_callback(&commit_callback, &commit_baton,
session, branching_txn, branch_info_dir,
commit_callback, commit_baton, pool);
SVN_ERR(svn_ra_get_commit_editor3(session, &deditor, &dedit_baton,
revprop_table,
commit_callback, commit_baton,
lock_tokens, keep_locks, pool));
/* Convert to Ev3 */
{
const char *repos_root_url;
SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, pool));
/*SVN_ERR(svn_delta__get_debug_editor(&deditor, &dedit_baton,
deditor, dedit_baton, "", pool));*/
SVN_ERR(svn_branch__compat_txn_from_delta_for_commit(
edit_txn_p,
&shim_connector,
deditor, dedit_baton, branching_txn,
repos_root_url,
fetch_func, fetch_baton,
NULL, NULL /*cancel*/,
pool, pool));
}
return SVN_NO_ERROR;
}