diff options
Diffstat (limited to 'subversion/svnsync/sync.c')
-rw-r--r-- | subversion/svnsync/sync.c | 643 |
1 files changed, 643 insertions, 0 deletions
diff --git a/subversion/svnsync/sync.c b/subversion/svnsync/sync.c new file mode 100644 index 0000000..5d86ac2 --- /dev/null +++ b/subversion/svnsync/sync.c @@ -0,0 +1,643 @@ +/* + * ==================================================================== + * 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. + * ==================================================================== + */ + +#include "svn_hash.h" +#include "svn_cmdline.h" +#include "svn_config.h" +#include "svn_pools.h" +#include "svn_delta.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_props.h" +#include "svn_auth.h" +#include "svn_opt.h" +#include "svn_ra.h" +#include "svn_utf.h" +#include "svn_subst.h" +#include "svn_string.h" + +#include "sync.h" + +#include "svn_private_config.h" + +#include <apr_network_io.h> +#include <apr_signal.h> +#include <apr_uuid.h> + + +/* Normalize the encoding and line ending style of *STR, so that it contains + * only LF (\n) line endings and is encoded in UTF-8. After return, *STR may + * point at a new svn_string_t* allocated in RESULT_POOL. + * + * If SOURCE_PROP_ENCODING is NULL, then *STR is presumed to be encoded in + * UTF-8. + * + * *WAS_NORMALIZED is set to TRUE when *STR needed line ending normalization. + * Otherwise it is set to FALSE. + * + * SCRATCH_POOL is used for temporary allocations. + */ +static svn_error_t * +normalize_string(const svn_string_t **str, + svn_boolean_t *was_normalized, + const char *source_prop_encoding, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_string_t *new_str; + + *was_normalized = FALSE; + + if (*str == NULL) + return SVN_NO_ERROR; + + SVN_ERR_ASSERT((*str)->data != NULL); + + if (source_prop_encoding == NULL) + source_prop_encoding = "UTF-8"; + + new_str = NULL; + SVN_ERR(svn_subst_translate_string2(&new_str, NULL, was_normalized, + *str, source_prop_encoding, TRUE, + result_pool, scratch_pool)); + *str = new_str; + + return SVN_NO_ERROR; +} + + +/* Normalize the encoding and line ending style of the values of properties + * in REV_PROPS that "need translation" (according to + * svn_prop_needs_translation(), which is currently all svn:* props) so that + * they are encoded in UTF-8 and contain only LF (\n) line endings. + * + * The number of properties that needed line ending normalization is returned in + * *NORMALIZED_COUNT. + * + * No re-encoding is performed if SOURCE_PROP_ENCODING is NULL. + */ +svn_error_t * +svnsync_normalize_revprops(apr_hash_t *rev_props, + int *normalized_count, + const char *source_prop_encoding, + apr_pool_t *pool) +{ + apr_hash_index_t *hi; + *normalized_count = 0; + + for (hi = apr_hash_first(pool, rev_props); + hi; + hi = apr_hash_next(hi)) + { + const char *propname = svn__apr_hash_index_key(hi); + const svn_string_t *propval = svn__apr_hash_index_val(hi); + + if (svn_prop_needs_translation(propname)) + { + svn_boolean_t was_normalized; + SVN_ERR(normalize_string(&propval, &was_normalized, + source_prop_encoding, pool, pool)); + + /* Replace the existing prop value. */ + svn_hash_sets(rev_props, propname, propval); + + if (was_normalized) + (*normalized_count)++; /* Count it. */ + } + } + return SVN_NO_ERROR; +} + + +/*** Synchronization Editor ***/ + +/* This editor has a couple of jobs. + * + * First, it needs to filter out the propchanges that can't be passed over + * libsvn_ra. + * + * Second, it needs to adjust for the fact that we might not actually have + * permission to see all of the data from the remote repository, which means + * we could get revisions that are totally empty from our point of view. + * + * Third, it needs to adjust copyfrom paths, adding the root url for the + * destination repository to the beginning of them. + */ + + +/* Edit baton */ +typedef struct edit_baton_t { + const svn_delta_editor_t *wrapped_editor; + void *wrapped_edit_baton; + const char *to_url; /* URL we're copying into, for correct copyfrom URLs */ + const char *source_prop_encoding; + svn_boolean_t called_open_root; + svn_boolean_t got_textdeltas; + svn_revnum_t base_revision; + svn_boolean_t quiet; + svn_boolean_t strip_mergeinfo; /* Are we stripping svn:mergeinfo? */ + svn_boolean_t migrate_svnmerge; /* Are we converting svnmerge.py data? */ + svn_boolean_t mergeinfo_stripped; /* Did we strip svn:mergeinfo? */ + svn_boolean_t svnmerge_migrated; /* Did we convert svnmerge.py data? */ + svn_boolean_t svnmerge_blocked; /* Was there any blocked svnmerge data? */ + int *normalized_node_props_counter; /* Where to count normalizations? */ +} edit_baton_t; + + +/* A dual-purpose baton for files and directories. */ +typedef struct node_baton_t { + void *edit_baton; + void *wrapped_node_baton; +} node_baton_t; + + +/*** Editor vtable functions ***/ + +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + edit_baton_t *eb = edit_baton; + return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, + target_revision, pool); +} + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + edit_baton_t *eb = edit_baton; + node_baton_t *dir_baton = apr_palloc(pool, sizeof(*dir_baton)); + + SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, + base_revision, pool, + &dir_baton->wrapped_node_baton)); + + eb->called_open_root = TRUE; + dir_baton->edit_baton = edit_baton; + *root_baton = dir_baton; + + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + node_baton_t *pb = parent_baton; + edit_baton_t *eb = pb->edit_baton; + + return eb->wrapped_editor->delete_entry(path, base_revision, + pb->wrapped_node_baton, pool); +} + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **child_baton) +{ + node_baton_t *pb = parent_baton; + edit_baton_t *eb = pb->edit_baton; + node_baton_t *b = apr_palloc(pool, sizeof(*b)); + + /* if copyfrom_path is an fspath create a proper uri */ + if (copyfrom_path && copyfrom_path[0] == '/') + copyfrom_path = svn_path_url_add_component2(eb->to_url, + copyfrom_path + 1, pool); + + SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_node_baton, + copyfrom_path, + copyfrom_rev, pool, + &b->wrapped_node_baton)); + + b->edit_baton = eb; + *child_baton = b; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + node_baton_t *pb = parent_baton; + edit_baton_t *eb = pb->edit_baton; + node_baton_t *db = apr_palloc(pool, sizeof(*db)); + + SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_node_baton, + base_revision, pool, + &db->wrapped_node_baton)); + + db->edit_baton = eb; + *child_baton = db; + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_rev, + apr_pool_t *pool, + void **file_baton) +{ + node_baton_t *pb = parent_baton; + edit_baton_t *eb = pb->edit_baton; + node_baton_t *fb = apr_palloc(pool, sizeof(*fb)); + + /* if copyfrom_path is an fspath create a proper uri */ + if (copyfrom_path && copyfrom_path[0] == '/') + copyfrom_path = svn_path_url_add_component2(eb->to_url, + copyfrom_path + 1, pool); + + SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_node_baton, + copyfrom_path, copyfrom_rev, + pool, &fb->wrapped_node_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + node_baton_t *pb = parent_baton; + edit_baton_t *eb = pb->edit_baton; + node_baton_t *fb = apr_palloc(pool, sizeof(*fb)); + + SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_node_baton, + base_revision, pool, + &fb->wrapped_node_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + node_baton_t *fb = file_baton; + edit_baton_t *eb = fb->edit_baton; + + if (! eb->quiet) + { + if (! eb->got_textdeltas) + SVN_ERR(svn_cmdline_printf(pool, _("Transmitting file data "))); + SVN_ERR(svn_cmdline_printf(pool, ".")); + SVN_ERR(svn_cmdline_fflush(stdout)); + } + + eb->got_textdeltas = TRUE; + return eb->wrapped_editor->apply_textdelta(fb->wrapped_node_baton, + base_checksum, pool, + handler, handler_baton); +} + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + node_baton_t *fb = file_baton; + edit_baton_t *eb = fb->edit_baton; + return eb->wrapped_editor->close_file(fb->wrapped_node_baton, + text_checksum, pool); +} + +static svn_error_t * +absent_file(const char *path, + void *file_baton, + apr_pool_t *pool) +{ + node_baton_t *fb = file_baton; + edit_baton_t *eb = fb->edit_baton; + return eb->wrapped_editor->absent_file(path, fb->wrapped_node_baton, pool); +} + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + node_baton_t *db = dir_baton; + edit_baton_t *eb = db->edit_baton; + return eb->wrapped_editor->close_directory(db->wrapped_node_baton, pool); +} + +static svn_error_t * +absent_directory(const char *path, + void *dir_baton, + apr_pool_t *pool) +{ + node_baton_t *db = dir_baton; + edit_baton_t *eb = db->edit_baton; + return eb->wrapped_editor->absent_directory(path, db->wrapped_node_baton, + pool); +} + +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + node_baton_t *fb = file_baton; + edit_baton_t *eb = fb->edit_baton; + + /* only regular properties can pass over libsvn_ra */ + if (svn_property_kind2(name) != svn_prop_regular_kind) + return SVN_NO_ERROR; + + /* Maybe drop svn:mergeinfo. */ + if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0)) + { + eb->mergeinfo_stripped = TRUE; + return SVN_NO_ERROR; + } + + /* Maybe drop (errantly set, as this is a file) svnmerge.py properties. */ + if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0)) + { + eb->svnmerge_migrated = TRUE; + return SVN_NO_ERROR; + } + + /* Remember if we see any svnmerge-blocked properties. (They really + shouldn't be here, as this is a file, but whatever...) */ + if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0)) + { + eb->svnmerge_blocked = TRUE; + } + + /* Normalize svn:* properties as necessary. */ + if (svn_prop_needs_translation(name)) + { + svn_boolean_t was_normalized; + SVN_ERR(normalize_string(&value, &was_normalized, + eb->source_prop_encoding, pool, pool)); + if (was_normalized) + (*(eb->normalized_node_props_counter))++; + } + + return eb->wrapped_editor->change_file_prop(fb->wrapped_node_baton, + name, value, pool); +} + +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + node_baton_t *db = dir_baton; + edit_baton_t *eb = db->edit_baton; + + /* Only regular properties can pass over libsvn_ra */ + if (svn_property_kind2(name) != svn_prop_regular_kind) + return SVN_NO_ERROR; + + /* Maybe drop svn:mergeinfo. */ + if (eb->strip_mergeinfo && (strcmp(name, SVN_PROP_MERGEINFO) == 0)) + { + eb->mergeinfo_stripped = TRUE; + return SVN_NO_ERROR; + } + + /* Maybe convert svnmerge-integrated data into svn:mergeinfo. (We + ignore svnmerge-blocked for now.) */ + /* ### FIXME: Consult the mirror repository's HEAD prop values and + ### merge svn:mergeinfo, svnmerge-integrated, and svnmerge-blocked. */ + if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-integrated") == 0)) + { + if (value) + { + /* svnmerge-integrated differs from svn:mergeinfo in a pair + of ways. First, it can use tabs, newlines, or spaces to + delimit source information. Secondly, the source paths + are relative URLs, whereas svn:mergeinfo uses relative + paths (not URI-encoded). */ + svn_error_t *err; + svn_stringbuf_t *mergeinfo_buf = svn_stringbuf_create_empty(pool); + svn_mergeinfo_t mergeinfo; + int i; + apr_array_header_t *sources = + svn_cstring_split(value->data, " \t\n", TRUE, pool); + svn_string_t *new_value; + + for (i = 0; i < sources->nelts; i++) + { + const char *rel_path; + apr_array_header_t *path_revs = + svn_cstring_split(APR_ARRAY_IDX(sources, i, const char *), + ":", TRUE, pool); + + /* ### TODO: Warn? */ + if (path_revs->nelts != 2) + continue; + + /* Append this source's mergeinfo data. */ + rel_path = APR_ARRAY_IDX(path_revs, 0, const char *); + rel_path = svn_path_uri_decode(rel_path, pool); + svn_stringbuf_appendcstr(mergeinfo_buf, rel_path); + svn_stringbuf_appendcstr(mergeinfo_buf, ":"); + svn_stringbuf_appendcstr(mergeinfo_buf, + APR_ARRAY_IDX(path_revs, 1, + const char *)); + svn_stringbuf_appendcstr(mergeinfo_buf, "\n"); + } + + /* Try to parse the mergeinfo string we've created, just to + check for bogosity. If all goes well, we'll unparse it + again and use that as our property value. */ + err = svn_mergeinfo_parse(&mergeinfo, mergeinfo_buf->data, pool); + if (err) + { + svn_error_clear(err); + return SVN_NO_ERROR; + } + SVN_ERR(svn_mergeinfo_to_string(&new_value, mergeinfo, pool)); + value = new_value; + } + name = SVN_PROP_MERGEINFO; + eb->svnmerge_migrated = TRUE; + } + + /* Remember if we see any svnmerge-blocked properties. */ + if (eb->migrate_svnmerge && (strcmp(name, "svnmerge-blocked") == 0)) + { + eb->svnmerge_blocked = TRUE; + } + + /* Normalize svn:* properties as necessary. */ + if (svn_prop_needs_translation(name)) + { + svn_boolean_t was_normalized; + SVN_ERR(normalize_string(&value, &was_normalized, eb->source_prop_encoding, + pool, pool)); + if (was_normalized) + (*(eb->normalized_node_props_counter))++; + } + + return eb->wrapped_editor->change_dir_prop(db->wrapped_node_baton, + name, value, pool); +} + +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + edit_baton_t *eb = edit_baton; + + /* If we haven't opened the root yet, that means we're transfering + an empty revision, probably because we aren't allowed to see the + contents for some reason. In any event, we need to open the root + and close it again, before we can close out the edit, or the + commit will fail. */ + + if (! eb->called_open_root) + { + void *baton; + SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, + eb->base_revision, pool, + &baton)); + SVN_ERR(eb->wrapped_editor->close_directory(baton, pool)); + } + + if (! eb->quiet) + { + if (eb->got_textdeltas) + SVN_ERR(svn_cmdline_printf(pool, "\n")); + if (eb->mergeinfo_stripped) + SVN_ERR(svn_cmdline_printf(pool, + "NOTE: Dropped Subversion mergeinfo " + "from this revision.\n")); + if (eb->svnmerge_migrated) + SVN_ERR(svn_cmdline_printf(pool, + "NOTE: Migrated 'svnmerge-integrated' in " + "this revision.\n")); + if (eb->svnmerge_blocked) + SVN_ERR(svn_cmdline_printf(pool, + "NOTE: Saw 'svnmerge-blocked' in this " + "revision (but didn't migrate it).\n")); + } + + return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); +} + +static svn_error_t * +abort_edit(void *edit_baton, + apr_pool_t *pool) +{ + edit_baton_t *eb = edit_baton; + return eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool); +} + + +/*** Editor factory function ***/ + +svn_error_t * +svnsync_get_sync_editor(const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + svn_revnum_t base_revision, + const char *to_url, + const char *source_prop_encoding, + svn_boolean_t quiet, + const svn_delta_editor_t **editor, + void **edit_baton, + int *normalized_node_props_counter, + apr_pool_t *pool) +{ + svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool); + edit_baton_t *eb = apr_pcalloc(pool, sizeof(*eb)); + + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->absent_directory = absent_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->absent_file = absent_file; + tree_editor->close_edit = close_edit; + tree_editor->abort_edit = abort_edit; + + eb->wrapped_editor = wrapped_editor; + eb->wrapped_edit_baton = wrapped_edit_baton; + eb->base_revision = base_revision; + eb->to_url = to_url; + eb->source_prop_encoding = source_prop_encoding; + eb->quiet = quiet; + eb->normalized_node_props_counter = normalized_node_props_counter; + + if (getenv("SVNSYNC_UNSUPPORTED_STRIP_MERGEINFO")) + { + eb->strip_mergeinfo = TRUE; + } + if (getenv("SVNSYNC_UNSUPPORTED_MIGRATE_SVNMERGE")) + { + /* Current we can't merge property values. That's only possible + if all the properties to be merged were always modified in + exactly the same revisions, or if we allow ourselves to + lookup the current state of properties in the sync + destination. So for now, migrating svnmerge.py data implies + stripping pre-existing svn:mergeinfo. */ + /* ### FIXME: Do a real migration by consulting the mirror + ### repository's HEAD propvalues and merging svn:mergeinfo, + ### svnmerge-integrated, and svnmerge-blocked together. */ + eb->migrate_svnmerge = TRUE; + eb->strip_mergeinfo = TRUE; + } + + *editor = tree_editor; + *edit_baton = eb; + + return SVN_NO_ERROR; +} + |