/* Check whether LOCAL_ABSPATH is an external and raise an error if it is. A file external should not be deleted since the file external is implemented as a switched file and it would delete the file the file external is switched to, which is not the behavior the user would probably want. A directory external should not be deleted since it is the root of a different working copy. */ static svn_error_t * check_external(const char *local_abspath, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_node_kind_t external_kind; const char *defining_abspath; SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL, NULL, NULL, ctx->wc_ctx, local_abspath, local_abspath, TRUE, scratch_pool, scratch_pool)); if (external_kind != svn_node_none) return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL, _("Cannot remove the external at '%s'; " "please edit or delete the svn:externals " "property on '%s'"), svn_dirent_local_style(local_abspath, scratch_pool), svn_dirent_local_style(defining_abspath, scratch_pool)); return SVN_NO_ERROR; }
svn_error_t * svn_client__can_delete(const char *path, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_opt_revision_t revision; svn_node_kind_t external_kind; const char *defining_abspath; const char* local_abspath; revision.kind = svn_opt_revision_unspecified; SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); /* A file external should not be deleted since the file external is implemented as a switched file and it would delete the file the file external is switched to, which is not the behavior the user would probably want. */ SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath, NULL, NULL, NULL, ctx->wc_ctx, local_abspath, local_abspath, TRUE, scratch_pool, scratch_pool)); if (external_kind != svn_node_none) return svn_error_createf(SVN_ERR_WC_CANNOT_DELETE_FILE_EXTERNAL, NULL, _("Cannot remove the external at '%s'; " "please edit or delete the svn:externals " "property on '%s'"), svn_dirent_local_style(local_abspath, scratch_pool), svn_dirent_local_style(defining_abspath, scratch_pool)); /* Use an infinite-depth status check to see if there's anything in or under PATH which would make it unsafe for deletion. The status callback function find_undeletables() makes the determination, returning an error if it finds anything that shouldn't be deleted. */ return svn_error_trace(svn_client_status5(NULL, ctx, path, &revision, svn_depth_infinity, FALSE, FALSE, FALSE, FALSE, FALSE, NULL, find_undeletables, NULL, scratch_pool)); }
/* Make an unversioned copy of the versioned file or directory tree at the * source path FROM_ABSPATH. Copy it to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * See copy_one_versioned_file() for details of file copying behaviour, * including IGNORE_KEYWORDS and NATIVE_EOL. * * Include externals unless IGNORE_EXTERNALS is true. * * Recurse according to DEPTH. * */ static svn_error_t * copy_versioned_files(const char *from_abspath, const char *to_abspath, const svn_opt_revision_t *revision, svn_boolean_t force, svn_boolean_t ignore_externals, svn_boolean_t ignore_keywords, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_error_t *err; apr_pool_t *iterpool; const apr_array_header_t *children; svn_node_kind_t from_kind; svn_depth_t node_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(from_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); /* Only export 'added' and 'replaced' files when the revision is WORKING; when the revision is BASE (i.e. != WORKING), only export 'added' and 'replaced' files when they are part of a copy-/move-here. Otherwise, skip them, since they don't have an associated text-base. This condition for added/replaced simply is an optimization. Added and replaced files would be handled similarly by svn_wc_get_pristine_contents2(), which would return NULL if they have no base associated. TODO: We may prefer not to duplicate this condition and rather use svn_wc_get_pristine_contents2() or a dedicated new function instead. Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind != svn_opt_revision_working) { svn_boolean_t is_added; const char *repos_relpath; SVN_ERR(svn_wc__node_get_origin(&is_added, NULL, &repos_relpath, NULL, NULL, NULL, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); if (is_added && !repos_relpath) return SVN_NO_ERROR; /* Local addition */ } else { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx, from_abspath, pool)); if (is_deleted) return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&from_kind, ctx->wc_ctx, from_abspath, FALSE, pool)); if (from_kind == svn_node_dir) { apr_fileperms_t perm = APR_OS_DEFAULT; int j; /* Try to make the new directory. If this fails because the directory already exists, check our FORCE flag to see if we care. */ /* Keep the source directory's permissions if applicable. Skip retrieving the umask on windows. Apr does not implement setting filesystem privileges on Windows. Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER is documented to be 'incredibly expensive' */ #ifndef WIN32 if (revision->kind == svn_opt_revision_working) { apr_finfo_t finfo; SVN_ERR(svn_io_stat(&finfo, from_abspath, APR_FINFO_PROT, pool)); perm = finfo.protection; } #endif err = svn_io_dir_make(to_abspath, perm, pool); if (err) { if (! APR_STATUS_IS_EEXIST(err->apr_err)) return svn_error_trace(err); if (! force) SVN_ERR_W(err, _("Destination directory exists, and will not be " "overwritten unless forced")); else svn_error_clear(err); } SVN_ERR(svn_wc__node_get_children(&children, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); iterpool = svn_pool_create(pool); for (j = 0; j < children->nelts; j++) { const char *child_abspath = APR_ARRAY_IDX(children, j, const char *); const char *child_name = svn_dirent_basename(child_abspath, NULL); const char *target_abspath; svn_node_kind_t child_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); target_abspath = svn_dirent_join(to_abspath, child_name, iterpool); SVN_ERR(svn_wc_read_kind(&child_kind, ctx->wc_ctx, child_abspath, FALSE, iterpool)); if (child_kind == svn_node_dir) { if (depth == svn_depth_infinity || depth == svn_depth_immediates) { if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } if (depth == svn_depth_infinity) SVN_ERR(copy_versioned_files(child_abspath, target_abspath, revision, force, ignore_externals, ignore_keywords, depth, native_eol, ctx, iterpool)); else SVN_ERR(svn_io_make_dir_recursively(target_abspath, iterpool)); } } else if (child_kind == svn_node_file && depth >= svn_depth_files) { svn_node_kind_t external_kind; SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, child_abspath, child_abspath, TRUE, pool, pool)); if (external_kind != svn_node_file) SVN_ERR(copy_one_versioned_file(child_abspath, target_abspath, ctx, revision, native_eol, ignore_keywords, iterpool)); } } SVN_ERR(svn_wc__node_get_depth(&node_depth, ctx->wc_ctx, from_abspath, pool)); /* Handle externals. */ if (! ignore_externals && depth == svn_depth_infinity && node_depth == svn_depth_infinity) { apr_array_header_t *ext_items; const svn_string_t *prop_val; SVN_ERR(svn_wc_prop_get2(&prop_val, ctx->wc_ctx, from_abspath, SVN_PROP_EXTERNALS, pool, pool)); if (prop_val != NULL) { int i; SVN_ERR(svn_wc_parse_externals_description3(&ext_items, from_abspath, prop_val->data, FALSE, pool)); for (i = 0; i < ext_items->nelts; ++i) { svn_wc_external_item2_t *ext_item; const char *new_from, *new_to; svn_pool_clear(iterpool); ext_item = APR_ARRAY_IDX(ext_items, i, svn_wc_external_item2_t *); new_from = svn_dirent_join(from_abspath, ext_item->target_dir, iterpool); new_to = svn_dirent_join(to_abspath, ext_item->target_dir, iterpool); /* The target dir might have parents that don't exist. Guarantee the path upto the last component. */ if (!svn_dirent_is_root(ext_item->target_dir, strlen(ext_item->target_dir))) { const char *parent = svn_dirent_dirname(new_to, iterpool); SVN_ERR(svn_io_make_dir_recursively(parent, iterpool)); } SVN_ERR(copy_versioned_files(new_from, new_to, revision, force, FALSE, ignore_keywords, svn_depth_infinity, native_eol, ctx, iterpool)); } }
/* Called when an external that is in the EXTERNALS table is no longer referenced from an svn:externals property */ static svn_error_t * handle_external_item_removal(const svn_client_ctx_t *ctx, const char *defining_abspath, const char *local_abspath, apr_pool_t *scratch_pool) { svn_error_t *err; svn_node_kind_t external_kind; svn_node_kind_t kind; svn_boolean_t removed = FALSE; /* local_abspath should be a wcroot or a file external */ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, defining_abspath, local_abspath, FALSE, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, scratch_pool)); if (external_kind != kind) external_kind = svn_node_none; /* Only remove the registration */ err = remove_external(&removed, ctx->wc_ctx, defining_abspath, local_abspath, external_kind, ctx->cancel_func, ctx->cancel_baton, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed) { svn_error_clear(err); err = NULL; /* We removed the working copy, so we can't release the lock that was stored inside */ } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_external_removed, scratch_pool); notify->kind = kind; notify->err = err; (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) { notify = svn_wc_create_notify(local_abspath, svn_wc_notify_left_local_modifications, scratch_pool); notify->kind = svn_node_dir; notify->err = err; (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } } if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) { svn_error_clear(err); err = NULL; } return svn_error_trace(err); }
/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a access baton that has a write lock. Use SCRATCH_POOL for temporary allocations, and use the client context CTX. */ static svn_error_t * switch_file_external(const char *local_abspath, const char *url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, const char *def_dir_abspath, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; svn_boolean_t use_commit_times; const char *diff3_cmd; const char *preserved_exts_str; const apr_array_header_t *preserved_exts; svn_node_kind_t kind, external_kind; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) : NULL; { const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, scratch_pool, scratch_pool)); /* File externals can only be installed inside the current working copy. So verify if the working copy that contains/will contain the target is the defining abspath, or one of its ancestors */ if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) return svn_error_createf( SVN_ERR_WC_BAD_PATH, NULL, _("Cannot insert a file external defined on '%s' " "into the working copy '%s'."), svn_dirent_local_style(def_dir_abspath, scratch_pool), svn_dirent_local_style(wcroot_abspath, scratch_pool)); } SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, scratch_pool)); SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, local_abspath, local_abspath, TRUE, scratch_pool, scratch_pool)); /* If there is a versioned item with this name, ensure it's a file external before working with it. If there is no entry in the working copy, then create an empty file and add it to the working copy. */ if (kind != svn_node_none && kind != svn_node_unknown) { if (external_kind != svn_node_file) { return svn_error_createf( SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, _("The file external from '%s' cannot overwrite the existing " "versioned item at '%s'"), url, svn_dirent_local_style(local_abspath, scratch_pool)); } } else { svn_node_kind_t disk_kind; SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); if (kind == svn_node_file || kind == svn_node_dir) return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, _("The file external '%s' can not be " "created because the node exists."), svn_dirent_local_style(local_abspath, scratch_pool)); } { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_delta_editor_t *switch_editor; void *switch_baton; svn_client__pathrev_t *switch_loc; svn_revnum_t revnum; apr_array_header_t *inherited_props; const char *dir_abspath; const char *target; svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool); /* ### Why do we open a new session? RA_SESSION is a valid ### session -- the caller used it to call svn_ra_check_path on ### this very URL, the caller also did the resolving and ### reparenting that is repeated here. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, url, dir_abspath, peg_revision, revision, ctx, scratch_pool)); /* Get the external file's iprops. */ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", switch_loc->rev, scratch_pool, scratch_pool)); SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool), scratch_pool)); SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, &revnum, ctx->wc_ctx, local_abspath, def_dir_abspath, switch_loc->url, switch_loc->repos_root_url, switch_loc->repos_uuid, inherited_props, use_commit_times, diff3_cmd, preserved_exts, def_dir_abspath, url, peg_revision, revision, ctx->conflict_func2, ctx->conflict_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool, scratch_pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, switch_loc->rev, target, svn_depth_unknown, switch_loc->url, FALSE /* send_copyfrom */, TRUE /* ignore_ancestry */, switch_editor, switch_baton, scratch_pool, scratch_pool)); SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, scratch_pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } } return SVN_NO_ERROR; }
/* Perform status operations on each external in EXTERNAL_MAP, a const char * local_abspath of all externals mapping to the const char* defining_abspath. All other options are the same as those passed to svn_client_status(). If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide properly formatted relative paths */ static svn_error_t * do_external_status(svn_client_ctx_t *ctx, apr_hash_t *external_map, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t check_out_of_date, svn_boolean_t check_working_copy, svn_boolean_t no_ignore, const apr_array_header_t *changelists, const char *anchor_abspath, const char *anchor_relpath, svn_client_status_func_t status_func, void *status_baton, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_array_header_t *externals; int i; externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically, scratch_pool); /* Loop over the hash of new values (we don't care about the old ones). This is a mapping of versioned directories to property values. */ for (i = 0; i < externals->nelts; i++) { svn_node_kind_t external_kind; svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t); const char *local_abspath = item.key; const char *defining_abspath = item.value; svn_node_kind_t kind; svn_opt_revision_t opt_rev; const char *status_path; svn_pool_clear(iterpool); /* Obtain information on the expected external. */ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, &opt_rev.value.number, ctx->wc_ctx, defining_abspath, local_abspath, FALSE, iterpool, iterpool)); if (external_kind != svn_node_dir) continue; SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool)); if (kind != svn_node_dir) continue; if (SVN_IS_VALID_REVNUM(opt_rev.value.number)) opt_rev.kind = svn_opt_revision_number; else opt_rev.kind = svn_opt_revision_unspecified; /* Tell the client we're starting an external status set. */ if (ctx->notify_func2) ctx->notify_func2( ctx->notify_baton2, svn_wc_create_notify(local_abspath, svn_wc_notify_status_external, iterpool), iterpool); status_path = local_abspath; if (anchor_abspath) { status_path = svn_dirent_join(anchor_relpath, svn_dirent_skip_ancestor(anchor_abspath, status_path), iterpool); } /* And then do the status. */ SVN_ERR(svn_client_status6(NULL, ctx, status_path, &opt_rev, depth, get_all, check_out_of_date, check_working_copy, no_ignore, FALSE /* ignore_exernals */, FALSE /* depth_as_sticky */, changelists, status_func, status_baton, iterpool)); } /* Destroy SUBPOOL and (implicitly) ITERPOOL. */ svn_pool_destroy(iterpool); return SVN_NO_ERROR; }