summaryrefslogtreecommitdiff
path: root/subversion/libsvn_wc/externals.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_wc/externals.c')
-rw-r--r--subversion/libsvn_wc/externals.c581
1 files changed, 516 insertions, 65 deletions
diff --git a/subversion/libsvn_wc/externals.c b/subversion/libsvn_wc/externals.c
index ccb86f9..3725b22 100644
--- a/subversion/libsvn_wc/externals.c
+++ b/subversion/libsvn_wc/externals.c
@@ -30,6 +30,7 @@
#include <apr_hash.h>
#include <apr_tables.h>
#include <apr_general.h>
+#include <apr_uri.h>
#include "svn_dirent_uri.h"
#include "svn_path.h"
@@ -44,6 +45,7 @@
#include "svn_wc.h"
#include "private/svn_skel.h"
+#include "private/svn_subr_private.h"
#include "wc.h"
#include "adm_files.h"
@@ -162,13 +164,16 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
svn_boolean_t canonicalize_url,
apr_pool_t *pool)
{
- apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
int i;
+ apr_array_header_t *externals = NULL;
+ apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
const char *parent_directory_display = svn_path_is_url(parent_directory) ?
parent_directory : svn_dirent_local_style(parent_directory, pool);
+ /* If an error occurs halfway through parsing, *externals_p should stay
+ * untouched. So, store the list in a local var first. */
if (externals_p)
- *externals_p = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
+ externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
for (i = 0; i < lines->nelts; i++)
{
@@ -200,8 +205,7 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
;
- SVN_ERR(svn_wc_external_item_create
- ((const svn_wc_external_item2_t **) &item, pool));
+ SVN_ERR(svn_wc_external_item2_create(&item, pool));
item->revision.kind = svn_opt_revision_unspecified;
item->peg_revision.kind = svn_opt_revision_unspecified;
@@ -327,13 +331,63 @@ svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
item->url = svn_dirent_canonicalize(item->url, pool);
}
- if (externals_p)
- APR_ARRAY_PUSH(*externals_p, svn_wc_external_item2_t *) = item;
+ if (externals)
+ APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
}
+ if (externals_p)
+ *externals_p = externals;
+
return SVN_NO_ERROR;
}
+svn_error_t *
+svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
+ apr_array_header_t *externals,
+ apr_pool_t *pool,
+ apr_pool_t *scratch_pool)
+{
+ int i;
+ unsigned int len;
+ unsigned int len2;
+ const char *target;
+ apr_hash_t *targets = apr_hash_make(scratch_pool);
+ apr_hash_t *targets2 = NULL;
+ *duplicate_targets = NULL;
+
+ for (i = 0; i < externals->nelts; i++)
+ {
+ target = APR_ARRAY_IDX(externals, i,
+ svn_wc_external_item2_t*)->target_dir;
+ len = apr_hash_count(targets);
+ svn_hash_sets(targets, target, "");
+ if (len == apr_hash_count(targets))
+ {
+ /* Hashtable length is unchanged. This must be a duplicate. */
+
+ /* Collapse multiple duplicates of the same target by using a second
+ * hash layer. */
+ if (! targets2)
+ targets2 = apr_hash_make(scratch_pool);
+ len2 = apr_hash_count(targets2);
+ svn_hash_sets(targets2, target, "");
+ if (len2 < apr_hash_count(targets2))
+ {
+ /* The second hash list just got bigger, i.e. this target has
+ * not been counted as duplicate before. */
+ if (! *duplicate_targets)
+ {
+ *duplicate_targets = apr_array_make(
+ pool, 1, sizeof(svn_wc_external_item2_t*));
+ }
+ APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
+ }
+ /* Else, this same target has already been recorded as a duplicate,
+ * don't count it again. */
+ }
+ }
+ return SVN_NO_ERROR;
+}
struct edit_baton
{
@@ -360,6 +414,9 @@ struct edit_baton
svn_revnum_t recorded_peg_revision;
svn_revnum_t recorded_revision;
+ /* Introducing a new file external */
+ svn_boolean_t added;
+
svn_wc_conflict_resolver_func2_t conflict_func;
void *conflict_baton;
svn_cancel_func_t cancel_func;
@@ -381,6 +438,10 @@ struct edit_baton
/* List of incoming propchanges */
apr_array_header_t *propchanges;
+ /* Array of svn_prop_inherited_item_t * structures representing the
+ properties inherited by the base node at LOCAL_ABSPATH. */
+ apr_array_header_t *iprops;
+
/* The last change information */
svn_revnum_t changed_rev;
apr_time_t changed_date;
@@ -433,6 +494,7 @@ add_file(const char *path,
*file_baton = eb;
eb->original_revision = SVN_INVALID_REVNUM;
+ eb->added = TRUE;
return SVN_NO_ERROR;
}
@@ -446,7 +508,7 @@ open_file(const char *path,
void **file_baton)
{
struct edit_baton *eb = parent_baton;
- svn_wc__db_kind_t kind;
+ svn_node_kind_t kind;
if (strcmp(path, eb->name))
return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
_("This editor can only update '%s'"),
@@ -458,11 +520,11 @@ open_file(const char *path,
NULL, NULL, NULL, &eb->changed_rev,
&eb->changed_date, &eb->changed_author,
NULL, &eb->original_checksum, NULL, NULL,
- &eb->had_props, NULL,
+ &eb->had_props, NULL, NULL,
eb->db, eb->local_abspath,
eb->pool, file_pool));
- if (kind != svn_wc__db_kind_file)
+ if (kind != svn_node_file)
return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
_("Node '%s' is no existing file external"),
svn_dirent_local_style(eb->local_abspath,
@@ -559,6 +621,7 @@ close_file(void *file_baton,
eb->file_closed = TRUE; /* We bump the revision here */
+ /* Check the checksum, if provided */
if (expected_md5_digest)
{
svn_checksum_t *expected_md5_checksum;
@@ -600,10 +663,10 @@ close_file(void *file_baton,
eb->new_pristine_abspath = NULL;
}
- /* ### TODO: Merge the changes */
-
+ /* Merge the changes */
{
svn_skel_t *all_work_items = NULL;
+ svn_skel_t *conflict_skel = NULL;
svn_skel_t *work_item;
apr_hash_t *base_props = NULL;
apr_hash_t *actual_props = NULL;
@@ -614,20 +677,19 @@ close_file(void *file_baton,
const svn_checksum_t *original_checksum = NULL;
svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
- const char *repos_relpath = svn_uri__is_child(eb->repos_root_url,
- eb->url, pool);
+ const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
+ eb->url, pool);
if (! added)
{
new_checksum = eb->original_checksum;
if (eb->had_props)
- SVN_ERR(svn_wc__db_base_get_props(&base_props, eb->db,
- eb->local_abspath,
- pool, pool));
+ SVN_ERR(svn_wc__db_base_get_props(
+ &base_props, eb->db, eb->local_abspath, pool, pool));
- SVN_ERR(svn_wc__db_read_props(&actual_props, eb->db,
- eb->local_abspath, pool, pool));
+ SVN_ERR(svn_wc__db_read_props(
+ &actual_props, eb->db, eb->local_abspath, pool, pool));
}
if (!base_props)
@@ -639,6 +701,7 @@ close_file(void *file_baton,
if (eb->new_sha1_checksum)
new_checksum = eb->new_sha1_checksum;
+ /* Merge the properties */
{
apr_array_header_t *entry_prop_changes;
apr_array_header_t *dav_prop_changes;
@@ -649,6 +712,7 @@ close_file(void *file_baton,
&dav_prop_changes, &regular_prop_changes,
pool));
+ /* Read the entry-prop changes to update the last-changed info. */
for (i = 0; i < entry_prop_changes->nelts; i++)
{
const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
@@ -670,31 +734,26 @@ close_file(void *file_baton,
pool));
}
+ /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
+ * of changes as a list of new props, but we only use this when
+ * adding a new file and it's equivalent in that case.) */
if (dav_prop_changes->nelts > 0)
new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
+ /* Merge the regular prop changes. */
if (regular_prop_changes->nelts > 0)
{
- SVN_ERR(svn_wc__merge_props(&work_item, &prop_state,
- &new_pristine_props,
+ new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
+ pool);
+ SVN_ERR(svn_wc__merge_props(&conflict_skel,
+ &prop_state,
&new_actual_props,
eb->db, eb->local_abspath,
- svn_wc__db_kind_file,
- NULL, NULL,
NULL /* server_baseprops*/,
base_props,
actual_props,
regular_prop_changes,
- TRUE /* base_merge */,
- FALSE /* dry_run */,
- eb->conflict_func,
- eb->conflict_baton,
- eb->cancel_func, eb->cancel_baton,
pool, pool));
-
- if (work_item)
- all_work_items = svn_wc__wq_merge(all_work_items, work_item,
- pool);
}
else
{
@@ -703,6 +762,7 @@ close_file(void *file_baton,
}
}
+ /* Merge the text */
if (eb->new_sha1_checksum)
{
svn_node_kind_t disk_kind;
@@ -717,7 +777,8 @@ close_file(void *file_baton,
install_pristine = TRUE;
content_state = svn_wc_notify_state_changed;
}
- else if (disk_kind != svn_node_file)
+ else if (disk_kind != svn_node_file
+ || (eb->added && disk_kind == svn_node_file))
{
/* The node is obstructed; we just change the DB */
obstructed = TRUE;
@@ -737,10 +798,12 @@ close_file(void *file_baton,
}
else
{
- enum svn_wc_merge_outcome_t merge_outcome;
+ svn_boolean_t found_text_conflict;
+
/* Ok, we have to do some work to merge a local change */
SVN_ERR(svn_wc__perform_file_merge(&work_item,
- &merge_outcome,
+ &conflict_skel,
+ &found_text_conflict,
eb->db,
eb->local_abspath,
eb->wri_abspath,
@@ -752,8 +815,6 @@ close_file(void *file_baton,
*eb->target_revision,
eb->propchanges,
eb->diff3cmd,
- eb->conflict_func,
- eb->conflict_baton,
eb->cancel_func,
eb->cancel_baton,
pool, pool));
@@ -761,7 +822,7 @@ close_file(void *file_baton,
all_work_items = svn_wc__wq_merge(all_work_items, work_item,
pool);
- if (merge_outcome == svn_wc_merge_conflict)
+ if (found_text_conflict)
content_state = svn_wc_notify_state_conflicted;
else
content_state = svn_wc_notify_state_merged;
@@ -784,6 +845,35 @@ close_file(void *file_baton,
/* ### Retranslate on magic property changes, etc. */
}
+ /* Generate a conflict description, if needed */
+ if (conflict_skel)
+ {
+ SVN_ERR(svn_wc__conflict_skel_set_op_switch(
+ conflict_skel,
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ eb->original_revision,
+ svn_node_file,
+ pool),
+ svn_wc_conflict_version_create2(
+ eb->repos_root_url,
+ eb->repos_uuid,
+ repos_relpath,
+ *eb->target_revision,
+ svn_node_file,
+ pool),
+ pool, pool));
+ SVN_ERR(svn_wc__conflict_create_markers(&work_item,
+ eb->db, eb->local_abspath,
+ conflict_skel,
+ pool, pool));
+ all_work_items = svn_wc__wq_merge(all_work_items, work_item,
+ pool);
+ }
+
+ /* Install the file in the DB */
SVN_ERR(svn_wc__db_external_add_file(
eb->db,
eb->local_abspath,
@@ -793,6 +883,7 @@ close_file(void *file_baton,
eb->repos_uuid,
*eb->target_revision,
new_pristine_props,
+ eb->iprops,
eb->changed_rev,
eb->changed_date,
eb->changed_author,
@@ -804,19 +895,28 @@ close_file(void *file_baton,
eb->recorded_revision,
TRUE, new_actual_props,
FALSE /* keep_recorded_info */,
+ conflict_skel,
all_work_items,
pool));
+ /* close_edit may also update iprops for switched files, catching
+ those for which close_file is never called (e.g. an update of a
+ file external with no changes). So as a minor optimization we
+ clear the iprops so as not to set them again in close_edit. */
+ eb->iprops = NULL;
+
+ /* Run the work queue to complete the installation */
SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
eb->cancel_func, eb->cancel_baton, pool));
}
+ /* Notify */
if (eb->notify_func)
{
svn_wc_notify_action_t action;
svn_wc_notify_t *notify;
- if (SVN_IS_VALID_REVNUM(eb->original_revision))
+ if (!eb->added)
action = obstructed ? svn_wc_notify_update_shadowed_update
: svn_wc_notify_update_update;
else
@@ -835,7 +935,6 @@ close_file(void *file_baton,
eb->notify_func(eb->notify_baton, notify, pool);
}
-
return SVN_NO_ERROR;
}
@@ -848,14 +947,60 @@ close_edit(void *edit_baton,
if (!eb->file_closed)
{
- /* The node wasn't updated, so we just have to bump its revision */
- SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
- eb->local_abspath,
- svn_depth_infinity,
- NULL, NULL, NULL,
- *eb->target_revision,
- apr_hash_make(pool),
- pool));
+ /* The file wasn't updated, but its url or revision might have...
+ e.g. switch between branches for relative externals.
+
+ Just bump the information as that is just as expensive as
+ investigating when we should and shouldn't update it...
+ and avoid hard to debug edge cases */
+
+ svn_node_kind_t kind;
+ const char *old_repos_relpath;
+ svn_revnum_t changed_rev;
+ apr_time_t changed_date;
+ const char *changed_author;
+ const svn_checksum_t *checksum;
+ apr_hash_t *pristine_props;
+ const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
+ eb->url, pool);
+
+ SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, NULL, &old_repos_relpath,
+ NULL, NULL, &changed_rev, &changed_date,
+ &changed_author, NULL, &checksum, NULL,
+ NULL, NULL, &pristine_props, NULL,
+ eb->db, eb->local_abspath,
+ pool, pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
+ _("Node '%s' is no existing file external"),
+ svn_dirent_local_style(eb->local_abspath,
+ pool));
+
+ SVN_ERR(svn_wc__db_external_add_file(
+ eb->db,
+ eb->local_abspath,
+ eb->wri_abspath,
+ repos_relpath,
+ eb->repos_root_url,
+ eb->repos_uuid,
+ *eb->target_revision,
+ pristine_props,
+ eb->iprops,
+ eb->changed_rev,
+ eb->changed_date,
+ eb->changed_author,
+ checksum,
+ NULL /* clear dav props */,
+ eb->record_ancestor_abspath,
+ eb->recorded_repos_relpath,
+ eb->recorded_peg_revision,
+ eb->recorded_revision,
+ FALSE, NULL,
+ TRUE /* keep_recorded_info */,
+ NULL /* conflict_skel */,
+ NULL /* work_items */,
+ pool));
}
return SVN_NO_ERROR;
@@ -871,6 +1016,7 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
const char *url,
const char *repos_root_url,
const char *repos_uuid,
+ apr_array_header_t *iprops,
svn_boolean_t use_commit_times,
const char *diff3_cmd,
const apr_array_header_t *preserved_exts,
@@ -906,13 +1052,15 @@ svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
+ eb->iprops = iprops;
+
eb->use_commit_times = use_commit_times;
eb->ext_patterns = preserved_exts;
eb->diff3cmd = diff3_cmd;
eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
- eb->recorded_repos_relpath = svn_uri__is_child(repos_root_url, recorded_url,
- edit_pool);
+ eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
+ edit_pool);
eb->changed_rev = SVN_INVALID_REVNUM;
@@ -965,7 +1113,7 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
{
svn_wc__db_t *db = wc_ctx->db;
svn_error_t *err;
- svn_wc__db_kind_t kind;
+ svn_node_kind_t kind;
svn_wc__db_lock_t *lock;
svn_revnum_t revision;
const char *repos_root_url;
@@ -975,12 +1123,12 @@ svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
err = svn_wc__db_base_get_info(NULL, &kind, &revision,
&repos_relpath, &repos_root_url, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, &lock,
- NULL, &update_root,
+ NULL, NULL, &update_root,
db, local_abspath,
scratch_pool, scratch_pool);
if (err
- || kind == svn_wc__db_kind_dir
+ || kind == svn_node_dir
|| !update_root)
{
if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
@@ -1064,7 +1212,7 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind,
{
const char *repos_root_url;
svn_wc__db_status_t status;
- svn_wc__db_kind_t kind;
+ svn_node_kind_t kind;
svn_error_t *err;
err = svn_wc__db_external_read(&status, &kind, defining_abspath,
@@ -1106,11 +1254,11 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind,
else
switch(kind)
{
- case svn_wc__db_kind_file:
- case svn_wc__db_kind_symlink:
+ case svn_node_file:
+ case svn_node_symlink:
*external_kind = svn_node_file;
break;
- case svn_wc__db_kind_dir:
+ case svn_node_dir:
*external_kind = svn_node_dir;
break;
default:
@@ -1125,6 +1273,108 @@ svn_wc__read_external_info(svn_node_kind_t *external_kind,
return SVN_NO_ERROR;
}
+/* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
+ * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
+ * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
+static svn_error_t *
+is_external_rolled_out(svn_boolean_t *is_rolled_out,
+ svn_wc_context_t *wc_ctx,
+ svn_wc__committable_external_info_t *xinfo,
+ apr_pool_t *scratch_pool)
+{
+ const char *repos_relpath;
+ const char *repos_root_url;
+ svn_error_t *err;
+
+ *is_rolled_out = FALSE;
+
+ err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
+ &repos_root_url, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ wc_ctx->db, xinfo->local_abspath,
+ scratch_pool, scratch_pool);
+
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
+ strcmp(xinfo->repos_relpath, repos_relpath) == 0);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__committable_externals_below(apr_array_header_t **externals,
+ svn_wc_context_t *wc_ctx,
+ const char *local_abspath,
+ svn_depth_t depth,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ apr_array_header_t *orig_externals;
+ int i;
+ apr_pool_t *iterpool;
+
+ /* For svn_depth_files, this also fetches dirs. They are filtered later. */
+ SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
+ wc_ctx->db,
+ local_abspath,
+ depth != svn_depth_infinity,
+ result_pool, scratch_pool));
+
+ if (orig_externals == NULL)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ for (i = 0; i < orig_externals->nelts; i++)
+ {
+ svn_boolean_t is_rolled_out;
+
+ svn_wc__committable_external_info_t *xinfo =
+ APR_ARRAY_IDX(orig_externals, i,
+ svn_wc__committable_external_info_t *);
+
+ /* Discard dirs for svn_depth_files (s.a.). */
+ if (depth == svn_depth_files
+ && xinfo->kind == svn_node_dir)
+ continue;
+
+ svn_pool_clear(iterpool);
+
+ /* Discard those externals that are not currently checked out. */
+ SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
+ iterpool));
+ if (! is_rolled_out)
+ continue;
+
+ if (*externals == NULL)
+ *externals = apr_array_make(
+ result_pool, 0,
+ sizeof(svn_wc__committable_external_info_t *));
+
+ APR_ARRAY_PUSH(*externals,
+ svn_wc__committable_external_info_t *) = xinfo;
+
+ if (depth != svn_depth_infinity)
+ continue;
+
+ /* Are there any nested externals? */
+ SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
+ xinfo->local_abspath,
+ svn_depth_infinity,
+ result_pool, iterpool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
svn_error_t *
svn_wc__externals_defined_below(apr_hash_t **externals,
svn_wc_context_t *wc_ctx,
@@ -1168,12 +1418,13 @@ svn_error_t *
svn_wc__external_remove(svn_wc_context_t *wc_ctx,
const char *wri_abspath,
const char *local_abspath,
+ svn_boolean_t declaration_only,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
svn_wc__db_status_t status;
- svn_wc__db_kind_t kind;
+ svn_node_kind_t kind;
SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
NULL, NULL,
@@ -1183,15 +1434,25 @@ svn_wc__external_remove(svn_wc_context_t *wc_ctx,
SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
NULL, scratch_pool));
- if (kind == svn_wc__db_kind_dir)
+ if (declaration_only)
+ return SVN_NO_ERROR;
+
+ if (kind == svn_node_dir)
SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
- TRUE, FALSE,
+ TRUE, TRUE,
cancel_func, cancel_baton,
scratch_pool));
else
{
- SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath, scratch_pool));
- SVN_ERR(svn_io_remove_file2(local_abspath, TRUE, scratch_pool));
+ SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
+ FALSE /* keep_as_working */,
+ TRUE /* queue_deletes */,
+ FALSE /* remove_locks */,
+ SVN_INVALID_REVNUM,
+ NULL, NULL, scratch_pool));
+ SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
+ cancel_func, cancel_baton,
+ scratch_pool));
}
return SVN_NO_ERROR;
@@ -1235,8 +1496,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals,
}
if (value)
- apr_hash_set(*externals, local_abspath, APR_HASH_KEY_STRING,
- value->data);
+ svn_hash_sets(*externals, local_abspath, value->data);
if (value && depths)
{
@@ -1251,8 +1511,7 @@ svn_wc__externals_gather_definitions(apr_hash_t **externals,
wc_ctx->db, local_abspath,
scratch_pool, scratch_pool));
- apr_hash_set(*depths, local_abspath, APR_HASH_KEY_STRING,
- svn_depth_to_word(node_depth));
+ svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
}
return SVN_NO_ERROR;
@@ -1268,3 +1527,195 @@ svn_wc__close_db(const char *external_abspath,
scratch_pool));
return SVN_NO_ERROR;
}
+
+/* Return the scheme of @a uri in @a scheme allocated from @a pool.
+ If @a uri does not appear to be a valid URI, then @a scheme will
+ not be updated. */
+static svn_error_t *
+uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
+{
+ apr_size_t i;
+
+ for (i = 0; uri[i] && uri[i] != ':'; ++i)
+ if (uri[i] == '/')
+ goto error;
+
+ if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
+ {
+ *scheme = apr_pstrmemdup(pool, uri, i);
+ return SVN_NO_ERROR;
+ }
+
+error:
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("URL '%s' does not begin with a scheme"),
+ uri);
+}
+
+svn_error_t *
+svn_wc__resolve_relative_external_url(const char **resolved_url,
+ const svn_wc_external_item2_t *item,
+ const char *repos_root_url,
+ const char *parent_dir_url,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ const char *url = item->url;
+ apr_uri_t parent_dir_uri;
+ apr_status_t status;
+
+ *resolved_url = item->url;
+
+ /* If the URL is already absolute, there is nothing to do. */
+ if (svn_path_is_url(url))
+ {
+ /* "http://server/path" */
+ *resolved_url = svn_uri_canonicalize(url, result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ if (url[0] == '/')
+ {
+ /* "/path", "//path", and "///path" */
+ int num_leading_slashes = 1;
+ if (url[1] == '/')
+ {
+ num_leading_slashes++;
+ if (url[2] == '/')
+ num_leading_slashes++;
+ }
+
+ /* "//schema-relative" and in some cases "///schema-relative".
+ This last format is supported on file:// schema relative. */
+ url = apr_pstrcat(scratch_pool,
+ apr_pstrndup(scratch_pool, url, num_leading_slashes),
+ svn_relpath_canonicalize(url + num_leading_slashes,
+ scratch_pool),
+ (char*)NULL);
+ }
+ else
+ {
+ /* "^/path" and "../path" */
+ url = svn_relpath_canonicalize(url, scratch_pool);
+ }
+
+ /* Parse the parent directory URL into its parts. */
+ status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal parent directory URL '%s'"),
+ parent_dir_url);
+
+ /* If the parent directory URL is at the server root, then the URL
+ may have no / after the hostname so apr_uri_parse() will leave
+ the URL's path as NULL. */
+ if (! parent_dir_uri.path)
+ parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+ parent_dir_uri.query = NULL;
+ parent_dir_uri.fragment = NULL;
+
+ /* Handle URLs relative to the current directory or to the
+ repository root. The backpaths may only remove path elements,
+ not the hostname. This allows an external to refer to another
+ repository in the same server relative to the location of this
+ repository, say using SVNParentPath. */
+ if ((0 == strncmp("../", url, 3)) ||
+ (0 == strncmp("^/", url, 2)))
+ {
+ apr_array_header_t *base_components;
+ apr_array_header_t *relative_components;
+ int i;
+
+ /* Decompose either the parent directory's URL path or the
+ repository root's URL path into components. */
+ if (0 == strncmp("../", url, 3))
+ {
+ base_components = svn_path_decompose(parent_dir_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url, scratch_pool);
+ }
+ else
+ {
+ apr_uri_t repos_root_uri;
+
+ status = apr_uri_parse(scratch_pool, repos_root_url,
+ &repos_root_uri);
+ if (status)
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Illegal repository root URL '%s'"),
+ repos_root_url);
+
+ /* If the repository root URL is at the server root, then
+ the URL may have no / after the hostname so
+ apr_uri_parse() will leave the URL's path as NULL. */
+ if (! repos_root_uri.path)
+ repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
+
+ base_components = svn_path_decompose(repos_root_uri.path,
+ scratch_pool);
+ relative_components = svn_path_decompose(url + 2, scratch_pool);
+ }
+
+ for (i = 0; i < relative_components->nelts; ++i)
+ {
+ const char *component = APR_ARRAY_IDX(relative_components,
+ i,
+ const char *);
+ if (0 == strcmp("..", component))
+ {
+ /* Constructing the final absolute URL together with
+ apr_uri_unparse() requires that the path be absolute,
+ so only pop a component if the component being popped
+ is not the component for the root directory. */
+ if (base_components->nelts > 1)
+ apr_array_pop(base_components);
+ }
+ else
+ APR_ARRAY_PUSH(base_components, const char *) = component;
+ }
+
+ parent_dir_uri.path = (char *)svn_path_compose(base_components,
+ scratch_pool);
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* The remaining URLs are relative to either the scheme or server root
+ and can only refer to locations inside that scope, so backpaths are
+ not allowed. */
+ if (svn_path_is_backpath_present(url))
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("The external relative URL '%s' cannot have "
+ "backpaths, i.e. '..'"),
+ item->url);
+
+ /* Relative to the scheme: Build a new URL from the parts we know. */
+ if (0 == strncmp("//", url, 2))
+ {
+ const char *scheme;
+
+ SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
+ *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
+ ":", url, (char *)NULL),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ /* Relative to the server root: Just replace the path portion of the
+ parent's URL. */
+ if (url[0] == '/')
+ {
+ parent_dir_uri.path = (char *)url;
+ *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
+ &parent_dir_uri, 0),
+ result_pool);
+ return SVN_NO_ERROR;
+ }
+
+ return svn_error_createf(SVN_ERR_BAD_URL, 0,
+ _("Unrecognized format for the relative external "
+ "URL '%s'"),
+ item->url);
+}