1251881Speter/* 2251881Speter * export.c: export a tree. 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include <apr_file_io.h> 31251881Speter#include <apr_md5.h> 32251881Speter#include "svn_types.h" 33251881Speter#include "svn_client.h" 34251881Speter#include "svn_string.h" 35251881Speter#include "svn_error.h" 36251881Speter#include "svn_dirent_uri.h" 37251881Speter#include "svn_hash.h" 38251881Speter#include "svn_path.h" 39251881Speter#include "svn_pools.h" 40251881Speter#include "svn_subst.h" 41251881Speter#include "svn_time.h" 42251881Speter#include "svn_props.h" 43251881Speter#include "client.h" 44251881Speter 45251881Speter#include "svn_private_config.h" 46251881Speter#include "private/svn_subr_private.h" 47251881Speter#include "private/svn_delta_private.h" 48251881Speter#include "private/svn_wc_private.h" 49251881Speter 50251881Speter#ifndef ENABLE_EV2_IMPL 51251881Speter#define ENABLE_EV2_IMPL 0 52251881Speter#endif 53251881Speter 54251881Speter 55251881Speter/*** Code. ***/ 56251881Speter 57251881Speter/* Add EXTERNALS_PROP_VAL for the export destination path PATH to 58251881Speter TRAVERSAL_INFO. */ 59251881Speterstatic svn_error_t * 60251881Speteradd_externals(apr_hash_t *externals, 61251881Speter const char *path, 62251881Speter const svn_string_t *externals_prop_val) 63251881Speter{ 64251881Speter apr_pool_t *pool = apr_hash_pool_get(externals); 65251881Speter const char *local_abspath; 66251881Speter 67251881Speter if (! externals_prop_val) 68251881Speter return SVN_NO_ERROR; 69251881Speter 70251881Speter SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool)); 71251881Speter 72251881Speter svn_hash_sets(externals, local_abspath, 73251881Speter apr_pstrmemdup(pool, externals_prop_val->data, 74251881Speter externals_prop_val->len)); 75251881Speter 76251881Speter return SVN_NO_ERROR; 77251881Speter} 78251881Speter 79251881Speter/* Helper function that gets the eol style and optionally overrides the 80251881Speter EOL marker for files marked as native with the EOL marker matching 81251881Speter the string specified in requested_value which is of the same format 82251881Speter as the svn:eol-style property values. */ 83251881Speterstatic svn_error_t * 84251881Speterget_eol_style(svn_subst_eol_style_t *style, 85251881Speter const char **eol, 86251881Speter const char *value, 87251881Speter const char *requested_value) 88251881Speter{ 89251881Speter svn_subst_eol_style_from_value(style, eol, value); 90251881Speter if (requested_value && *style == svn_subst_eol_style_native) 91251881Speter { 92251881Speter svn_subst_eol_style_t requested_style; 93251881Speter const char *requested_eol; 94251881Speter 95251881Speter svn_subst_eol_style_from_value(&requested_style, &requested_eol, 96251881Speter requested_value); 97251881Speter 98251881Speter if (requested_style == svn_subst_eol_style_fixed) 99251881Speter *eol = requested_eol; 100251881Speter else 101251881Speter return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL, 102251881Speter _("'%s' is not a valid EOL value"), 103251881Speter requested_value); 104251881Speter } 105251881Speter return SVN_NO_ERROR; 106251881Speter} 107251881Speter 108251881Speter/* If *APPENDABLE_DIRENT_P represents an existing directory, then append 109251881Speter * to it the basename of BASENAME_OF and return the result in 110251881Speter * *APPENDABLE_DIRENT_P. The kind of BASENAME_OF is either dirent or uri, 111251881Speter * as given by IS_URI. 112251881Speter */ 113251881Speterstatic svn_error_t * 114251881Speterappend_basename_if_dir(const char **appendable_dirent_p, 115251881Speter const char *basename_of, 116251881Speter svn_boolean_t is_uri, 117251881Speter apr_pool_t *pool) 118251881Speter{ 119251881Speter svn_node_kind_t local_kind; 120251881Speter SVN_ERR(svn_io_check_resolved_path(*appendable_dirent_p, &local_kind, pool)); 121251881Speter if (local_kind == svn_node_dir) 122251881Speter { 123251881Speter const char *base_name; 124251881Speter 125251881Speter if (is_uri) 126251881Speter base_name = svn_uri_basename(basename_of, pool); 127251881Speter else 128251881Speter base_name = svn_dirent_basename(basename_of, NULL); 129251881Speter 130251881Speter *appendable_dirent_p = svn_dirent_join(*appendable_dirent_p, 131251881Speter base_name, pool); 132251881Speter } 133251881Speter 134251881Speter return SVN_NO_ERROR; 135251881Speter} 136251881Speter 137251881Speter/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it 138251881Speter * to the destination path TO_ABSPATH. 139251881Speter * 140251881Speter * If REVISION is svn_opt_revision_working, copy the working version, 141251881Speter * otherwise copy the base version. 142251881Speter * 143251881Speter * Expand the file's keywords according to the source file's 'svn:keywords' 144251881Speter * property, if present. If copying a locally modified working version, 145251881Speter * append 'M' to the revision number and use '(local)' for the author. 146251881Speter * 147251881Speter * Translate the file's line endings according to the source file's 148251881Speter * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it 149251881Speter * in place of the native EOL style. Throw an error if the source file has 150251881Speter * inconsistent line endings and EOL translation is attempted. 151251881Speter * 152251881Speter * Set the destination file's modification time to the source file's 153251881Speter * modification time if copying the working version and the working version 154251881Speter * is locally modified; otherwise set it to the versioned file's last 155251881Speter * changed time. 156251881Speter * 157251881Speter * Set the destination file's 'executable' flag according to the source 158251881Speter * file's 'svn:executable' property. 159251881Speter */ 160251881Speter 161251881Speter/* baton for export_node */ 162251881Speterstruct export_info_baton 163251881Speter{ 164251881Speter const char *to_path; 165251881Speter const svn_opt_revision_t *revision; 166251881Speter svn_boolean_t ignore_keywords; 167251881Speter svn_boolean_t overwrite; 168251881Speter svn_wc_context_t *wc_ctx; 169251881Speter const char *native_eol; 170251881Speter svn_wc_notify_func2_t notify_func; 171251881Speter void *notify_baton; 172251881Speter const char *origin_abspath; 173251881Speter svn_boolean_t exported; 174251881Speter}; 175251881Speter 176251881Speter/* Export a file or directory. Implements svn_wc_status_func4_t */ 177251881Speterstatic svn_error_t * 178251881Speterexport_node(void *baton, 179251881Speter const char *local_abspath, 180251881Speter const svn_wc_status3_t *status, 181251881Speter apr_pool_t *scratch_pool) 182251881Speter{ 183251881Speter struct export_info_baton *eib = baton; 184251881Speter svn_wc_context_t *wc_ctx = eib->wc_ctx; 185251881Speter apr_hash_t *kw = NULL; 186251881Speter svn_subst_eol_style_t style; 187251881Speter apr_hash_t *props; 188251881Speter svn_string_t *eol_style, *keywords, *executable, *special; 189251881Speter const char *eol = NULL; 190251881Speter svn_boolean_t local_mod = FALSE; 191251881Speter apr_time_t tm; 192251881Speter svn_stream_t *source; 193251881Speter svn_stream_t *dst_stream; 194251881Speter const char *dst_tmp; 195251881Speter svn_error_t *err; 196251881Speter 197251881Speter const char *to_abspath = svn_dirent_join( 198251881Speter eib->to_path, 199251881Speter svn_dirent_skip_ancestor(eib->origin_abspath, 200251881Speter local_abspath), 201251881Speter scratch_pool); 202251881Speter 203251881Speter eib->exported = TRUE; 204251881Speter 205251881Speter /* Don't export 'deleted' files and directories unless it's a 206251881Speter revision other than WORKING. These files and directories 207251881Speter don't really exist in WORKING. */ 208251881Speter if (eib->revision->kind == svn_opt_revision_working 209251881Speter && status->node_status == svn_wc_status_deleted) 210251881Speter return SVN_NO_ERROR; 211251881Speter 212251881Speter if (status->kind == svn_node_dir) 213251881Speter { 214251881Speter apr_fileperms_t perm = APR_OS_DEFAULT; 215251881Speter 216251881Speter /* Try to make the new directory. If this fails because the 217251881Speter directory already exists, check our FORCE flag to see if we 218251881Speter care. */ 219251881Speter 220251881Speter /* Keep the source directory's permissions if applicable. 221251881Speter Skip retrieving the umask on windows. Apr does not implement setting 222251881Speter filesystem privileges on Windows. 223251881Speter Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER 224251881Speter is documented to be 'incredibly expensive' */ 225251881Speter#ifndef WIN32 226251881Speter if (eib->revision->kind == svn_opt_revision_working) 227251881Speter { 228251881Speter apr_finfo_t finfo; 229251881Speter SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_PROT, 230251881Speter scratch_pool)); 231251881Speter perm = finfo.protection; 232251881Speter } 233251881Speter#endif 234251881Speter err = svn_io_dir_make(to_abspath, perm, scratch_pool); 235251881Speter if (err) 236251881Speter { 237251881Speter if (! APR_STATUS_IS_EEXIST(err->apr_err)) 238251881Speter return svn_error_trace(err); 239251881Speter if (! eib->overwrite) 240251881Speter SVN_ERR_W(err, _("Destination directory exists, and will not be " 241251881Speter "overwritten unless forced")); 242251881Speter else 243251881Speter svn_error_clear(err); 244251881Speter } 245251881Speter 246251881Speter if (eib->notify_func 247251881Speter && (strcmp(eib->origin_abspath, local_abspath) != 0)) 248251881Speter { 249251881Speter svn_wc_notify_t *notify = 250251881Speter svn_wc_create_notify(to_abspath, 251251881Speter svn_wc_notify_update_add, scratch_pool); 252251881Speter 253251881Speter notify->kind = svn_node_dir; 254251881Speter (eib->notify_func)(eib->notify_baton, notify, scratch_pool); 255251881Speter } 256251881Speter 257251881Speter return SVN_NO_ERROR; 258251881Speter } 259251881Speter else if (status->kind != svn_node_file) 260251881Speter { 261251881Speter if (strcmp(eib->origin_abspath, local_abspath) != 0) 262251881Speter return SVN_NO_ERROR; 263251881Speter 264251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 265251881Speter _("The node '%s' was not found."), 266251881Speter svn_dirent_local_style(local_abspath, 267251881Speter scratch_pool)); 268251881Speter } 269251881Speter 270269847Speter /* Skip file externals if they are a descendant of the export, 271269847Speter BUT NOT if we are explictly exporting the file external. */ 272269847Speter if (status->file_external && strcmp(eib->origin_abspath, local_abspath) != 0) 273251881Speter return SVN_NO_ERROR; 274251881Speter 275251881Speter /* Produce overwrite errors for the export root */ 276251881Speter if (strcmp(local_abspath, eib->origin_abspath) == 0) 277251881Speter { 278251881Speter svn_node_kind_t to_kind; 279251881Speter 280251881Speter SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool)); 281251881Speter 282251881Speter if ((to_kind == svn_node_file || to_kind == svn_node_unknown) 283251881Speter && !eib->overwrite) 284251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 285251881Speter _("Destination file '%s' exists, and " 286251881Speter "will not be overwritten unless forced"), 287251881Speter svn_dirent_local_style(to_abspath, 288251881Speter scratch_pool)); 289251881Speter else if (to_kind == svn_node_dir) 290251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 291251881Speter _("Destination '%s' exists. Cannot " 292251881Speter "overwrite directory with non-directory"), 293251881Speter svn_dirent_local_style(to_abspath, 294251881Speter scratch_pool)); 295251881Speter } 296251881Speter 297251881Speter if (eib->revision->kind != svn_opt_revision_working) 298251881Speter { 299251881Speter /* Only export 'added' files when the revision is WORKING. This is not 300251881Speter WORKING, so skip the 'added' files, since they didn't exist 301251881Speter in the BASE revision and don't have an associated text-base. 302251881Speter 303251881Speter 'replaced' files are technically the same as 'added' files. 304251881Speter ### TODO: Handle replaced nodes properly. 305251881Speter ### svn_opt_revision_base refers to the "new" 306251881Speter ### base of the node. That means, if a node is locally 307251881Speter ### replaced, export skips this node, as if it was locally 308251881Speter ### added, because svn_opt_revision_base refers to the base 309251881Speter ### of the added node, not to the node that was deleted. 310251881Speter ### In contrast, when the node is copied-here or moved-here, 311251881Speter ### the copy/move source's content will be exported. 312251881Speter ### It is currently not possible to export the revert-base 313251881Speter ### when a node is locally replaced. We need a new 314251881Speter ### svn_opt_revision_ enum value for proper distinction 315251881Speter ### between revert-base and commit-base. 316251881Speter 317251881Speter Copied-/moved-here nodes have a base, so export both added and 318251881Speter replaced files when they involve a copy-/move-here. 319251881Speter 320251881Speter We get all this for free from evaluating SOURCE == NULL: 321251881Speter */ 322251881Speter SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath, 323251881Speter scratch_pool, scratch_pool)); 324251881Speter if (source == NULL) 325251881Speter return SVN_NO_ERROR; 326251881Speter 327251881Speter SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath, 328251881Speter scratch_pool, scratch_pool)); 329251881Speter } 330251881Speter else 331251881Speter { 332251881Speter /* ### hmm. this isn't always a specialfile. this will simply open 333251881Speter ### the file readonly if it is a regular file. */ 334251881Speter SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool, 335251881Speter scratch_pool)); 336251881Speter 337251881Speter SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool, 338251881Speter scratch_pool)); 339251881Speter if (status->node_status != svn_wc_status_normal) 340251881Speter local_mod = TRUE; 341251881Speter } 342251881Speter 343251881Speter /* We can early-exit if we're creating a special file. */ 344251881Speter special = svn_hash_gets(props, SVN_PROP_SPECIAL); 345251881Speter if (special != NULL) 346251881Speter { 347251881Speter /* Create the destination as a special file, and copy the source 348251881Speter details into the destination stream. */ 349251881Speter /* ### And forget the notification */ 350251881Speter SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath, 351251881Speter scratch_pool, scratch_pool)); 352251881Speter return svn_error_trace( 353251881Speter svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool)); 354251881Speter } 355251881Speter 356251881Speter 357251881Speter eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE); 358251881Speter keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS); 359251881Speter executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE); 360251881Speter 361251881Speter if (eol_style) 362251881Speter SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol)); 363251881Speter 364251881Speter if (local_mod) 365251881Speter { 366251881Speter /* Use the modified time from the working copy of 367251881Speter the file */ 368251881Speter SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool)); 369251881Speter } 370251881Speter else 371251881Speter { 372251881Speter tm = status->changed_date; 373251881Speter } 374251881Speter 375251881Speter if (keywords) 376251881Speter { 377251881Speter svn_revnum_t changed_rev = status->changed_rev; 378251881Speter const char *suffix; 379251881Speter const char *url = svn_path_url_add_component2(status->repos_root_url, 380251881Speter status->repos_relpath, 381251881Speter scratch_pool); 382251881Speter const char *author = status->changed_author; 383251881Speter if (local_mod) 384251881Speter { 385251881Speter /* For locally modified files, we'll append an 'M' 386251881Speter to the revision number, and set the author to 387251881Speter "(local)" since we can't always determine the 388251881Speter current user's username */ 389251881Speter suffix = "M"; 390251881Speter author = _("(local)"); 391251881Speter } 392251881Speter else 393251881Speter { 394251881Speter suffix = ""; 395251881Speter } 396251881Speter 397251881Speter SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data, 398251881Speter apr_psprintf(scratch_pool, "%ld%s", 399251881Speter changed_rev, suffix), 400251881Speter url, status->repos_root_url, tm, 401251881Speter author, scratch_pool)); 402251881Speter } 403251881Speter 404251881Speter /* For atomicity, we translate to a tmp file and then rename the tmp file 405251881Speter over the real destination. */ 406251881Speter SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, 407251881Speter svn_dirent_dirname(to_abspath, scratch_pool), 408251881Speter svn_io_file_del_none, scratch_pool, 409251881Speter scratch_pool)); 410251881Speter 411251881Speter /* If some translation is needed, then wrap the output stream (this is 412251881Speter more efficient than wrapping the input). */ 413251881Speter if (eol || (kw && (apr_hash_count(kw) > 0))) 414251881Speter dst_stream = svn_subst_stream_translated(dst_stream, 415251881Speter eol, 416251881Speter FALSE /* repair */, 417251881Speter kw, 418251881Speter ! eib->ignore_keywords /* expand */, 419251881Speter scratch_pool); 420251881Speter 421251881Speter /* ###: use cancel func/baton in place of NULL/NULL below. */ 422251881Speter err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool); 423251881Speter 424251881Speter if (!err && executable) 425251881Speter err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool); 426251881Speter 427251881Speter if (!err) 428251881Speter err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool); 429251881Speter 430251881Speter if (err) 431251881Speter return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, 432251881Speter scratch_pool)); 433251881Speter 434251881Speter /* Now that dst_tmp contains the translated data, do the atomic rename. */ 435251881Speter SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool)); 436251881Speter 437251881Speter if (eib->notify_func) 438251881Speter { 439251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath, 440251881Speter svn_wc_notify_update_add, scratch_pool); 441251881Speter notify->kind = svn_node_file; 442251881Speter (eib->notify_func)(eib->notify_baton, notify, scratch_pool); 443251881Speter } 444251881Speter 445251881Speter return SVN_NO_ERROR; 446251881Speter} 447251881Speter 448251881Speter/* Abstraction of open_root. 449251881Speter * 450251881Speter * Create PATH if it does not exist and is not obstructed, and invoke 451251881Speter * NOTIFY_FUNC with NOTIFY_BATON on PATH. 452251881Speter * 453251881Speter * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY. 454251881Speter * 455251881Speter * If PATH is a already a directory, then error with 456251881Speter * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just 457251881Speter * export into PATH with no error. 458251881Speter */ 459251881Speterstatic svn_error_t * 460251881Speteropen_root_internal(const char *path, 461251881Speter svn_boolean_t force, 462251881Speter svn_wc_notify_func2_t notify_func, 463251881Speter void *notify_baton, 464251881Speter apr_pool_t *pool) 465251881Speter{ 466251881Speter svn_node_kind_t kind; 467251881Speter 468251881Speter SVN_ERR(svn_io_check_path(path, &kind, pool)); 469251881Speter if (kind == svn_node_none) 470251881Speter SVN_ERR(svn_io_make_dir_recursively(path, pool)); 471251881Speter else if (kind == svn_node_file) 472251881Speter return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 473251881Speter _("'%s' exists and is not a directory"), 474251881Speter svn_dirent_local_style(path, pool)); 475251881Speter else if ((kind != svn_node_dir) || (! force)) 476251881Speter return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 477251881Speter _("'%s' already exists"), 478251881Speter svn_dirent_local_style(path, pool)); 479251881Speter 480251881Speter if (notify_func) 481251881Speter { 482251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(path, 483251881Speter svn_wc_notify_update_add, 484251881Speter pool); 485251881Speter notify->kind = svn_node_dir; 486251881Speter (*notify_func)(notify_baton, notify, pool); 487251881Speter } 488251881Speter 489251881Speter return SVN_NO_ERROR; 490251881Speter} 491251881Speter 492251881Speter 493251881Speter/* ---------------------------------------------------------------------- */ 494251881Speter 495251881Speter 496251881Speter/*** A dedicated 'export' editor, which does no .svn/ accounting. ***/ 497251881Speter 498251881Speter 499251881Speterstruct edit_baton 500251881Speter{ 501251881Speter const char *repos_root_url; 502251881Speter const char *root_path; 503251881Speter const char *root_url; 504251881Speter svn_boolean_t force; 505251881Speter svn_revnum_t *target_revision; 506251881Speter apr_hash_t *externals; 507251881Speter const char *native_eol; 508251881Speter svn_boolean_t ignore_keywords; 509251881Speter 510251881Speter svn_cancel_func_t cancel_func; 511251881Speter void *cancel_baton; 512251881Speter svn_wc_notify_func2_t notify_func; 513251881Speter void *notify_baton; 514251881Speter}; 515251881Speter 516251881Speter 517251881Speterstruct dir_baton 518251881Speter{ 519251881Speter struct edit_baton *edit_baton; 520251881Speter const char *path; 521251881Speter}; 522251881Speter 523251881Speter 524251881Speterstruct file_baton 525251881Speter{ 526251881Speter struct edit_baton *edit_baton; 527251881Speter 528251881Speter const char *path; 529251881Speter const char *tmppath; 530251881Speter 531251881Speter /* We need to keep this around so we can explicitly close it in close_file, 532251881Speter thus flushing its output to disk so we can copy and translate it. */ 533251881Speter svn_stream_t *tmp_stream; 534251881Speter 535251881Speter /* The MD5 digest of the file's fulltext. This is all zeros until 536251881Speter the last textdelta window handler call returns. */ 537251881Speter unsigned char text_digest[APR_MD5_DIGESTSIZE]; 538251881Speter 539251881Speter /* The three svn: properties we might actually care about. */ 540251881Speter const svn_string_t *eol_style_val; 541251881Speter const svn_string_t *keywords_val; 542251881Speter const svn_string_t *executable_val; 543251881Speter svn_boolean_t special; 544251881Speter 545251881Speter /* Any keyword vals to be substituted */ 546251881Speter const char *revision; 547251881Speter const char *url; 548251881Speter const char *repos_root_url; 549251881Speter const char *author; 550251881Speter apr_time_t date; 551251881Speter 552251881Speter /* Pool associated with this baton. */ 553251881Speter apr_pool_t *pool; 554251881Speter}; 555251881Speter 556251881Speter 557251881Speterstruct handler_baton 558251881Speter{ 559251881Speter svn_txdelta_window_handler_t apply_handler; 560251881Speter void *apply_baton; 561251881Speter apr_pool_t *pool; 562251881Speter const char *tmppath; 563251881Speter}; 564251881Speter 565251881Speter 566251881Speterstatic svn_error_t * 567251881Speterset_target_revision(void *edit_baton, 568251881Speter svn_revnum_t target_revision, 569251881Speter apr_pool_t *pool) 570251881Speter{ 571251881Speter struct edit_baton *eb = edit_baton; 572251881Speter 573251881Speter /* Stashing a target_revision in the baton */ 574251881Speter *(eb->target_revision) = target_revision; 575251881Speter return SVN_NO_ERROR; 576251881Speter} 577251881Speter 578251881Speter 579251881Speter 580251881Speter/* Just ensure that the main export directory exists. */ 581251881Speterstatic svn_error_t * 582251881Speteropen_root(void *edit_baton, 583251881Speter svn_revnum_t base_revision, 584251881Speter apr_pool_t *pool, 585251881Speter void **root_baton) 586251881Speter{ 587251881Speter struct edit_baton *eb = edit_baton; 588251881Speter struct dir_baton *db = apr_pcalloc(pool, sizeof(*db)); 589251881Speter 590251881Speter SVN_ERR(open_root_internal(eb->root_path, eb->force, 591251881Speter eb->notify_func, eb->notify_baton, pool)); 592251881Speter 593251881Speter /* Build our dir baton. */ 594251881Speter db->path = eb->root_path; 595251881Speter db->edit_baton = eb; 596251881Speter *root_baton = db; 597251881Speter 598251881Speter return SVN_NO_ERROR; 599251881Speter} 600251881Speter 601251881Speter 602251881Speter/* Ensure the directory exists, and send feedback. */ 603251881Speterstatic svn_error_t * 604251881Speteradd_directory(const char *path, 605251881Speter void *parent_baton, 606251881Speter const char *copyfrom_path, 607251881Speter svn_revnum_t copyfrom_revision, 608251881Speter apr_pool_t *pool, 609251881Speter void **baton) 610251881Speter{ 611251881Speter struct dir_baton *pb = parent_baton; 612251881Speter struct dir_baton *db = apr_pcalloc(pool, sizeof(*db)); 613251881Speter struct edit_baton *eb = pb->edit_baton; 614251881Speter const char *full_path = svn_dirent_join(eb->root_path, path, pool); 615251881Speter svn_node_kind_t kind; 616251881Speter 617251881Speter SVN_ERR(svn_io_check_path(full_path, &kind, pool)); 618251881Speter if (kind == svn_node_none) 619251881Speter SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool)); 620251881Speter else if (kind == svn_node_file) 621251881Speter return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 622251881Speter _("'%s' exists and is not a directory"), 623251881Speter svn_dirent_local_style(full_path, pool)); 624251881Speter else if (! (kind == svn_node_dir && eb->force)) 625251881Speter return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 626251881Speter _("'%s' already exists"), 627251881Speter svn_dirent_local_style(full_path, pool)); 628251881Speter 629251881Speter if (eb->notify_func) 630251881Speter { 631251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(full_path, 632251881Speter svn_wc_notify_update_add, 633251881Speter pool); 634251881Speter notify->kind = svn_node_dir; 635251881Speter (*eb->notify_func)(eb->notify_baton, notify, pool); 636251881Speter } 637251881Speter 638251881Speter /* Build our dir baton. */ 639251881Speter db->path = full_path; 640251881Speter db->edit_baton = eb; 641251881Speter *baton = db; 642251881Speter 643251881Speter return SVN_NO_ERROR; 644251881Speter} 645251881Speter 646251881Speter 647251881Speter/* Build a file baton. */ 648251881Speterstatic svn_error_t * 649251881Speteradd_file(const char *path, 650251881Speter void *parent_baton, 651251881Speter const char *copyfrom_path, 652251881Speter svn_revnum_t copyfrom_revision, 653251881Speter apr_pool_t *pool, 654251881Speter void **baton) 655251881Speter{ 656251881Speter struct dir_baton *pb = parent_baton; 657251881Speter struct edit_baton *eb = pb->edit_baton; 658251881Speter struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb)); 659251881Speter const char *full_path = svn_dirent_join(eb->root_path, path, pool); 660251881Speter 661251881Speter /* PATH is not canonicalized, i.e. it may still contain spaces etc. 662251881Speter * but EB->root_url is. */ 663251881Speter const char *full_url = svn_path_url_add_component2(eb->root_url, 664251881Speter path, 665251881Speter pool); 666251881Speter 667251881Speter fb->edit_baton = eb; 668251881Speter fb->path = full_path; 669251881Speter fb->url = full_url; 670251881Speter fb->repos_root_url = eb->repos_root_url; 671251881Speter fb->pool = pool; 672251881Speter 673251881Speter *baton = fb; 674251881Speter return SVN_NO_ERROR; 675251881Speter} 676251881Speter 677251881Speter 678251881Speterstatic svn_error_t * 679251881Speterwindow_handler(svn_txdelta_window_t *window, void *baton) 680251881Speter{ 681251881Speter struct handler_baton *hb = baton; 682251881Speter svn_error_t *err; 683251881Speter 684251881Speter err = hb->apply_handler(window, hb->apply_baton); 685251881Speter if (err) 686251881Speter { 687251881Speter /* We failed to apply the patch; clean up the temporary file. */ 688251881Speter err = svn_error_compose_create( 689251881Speter err, 690251881Speter svn_io_remove_file2(hb->tmppath, TRUE, hb->pool)); 691251881Speter } 692251881Speter 693251881Speter return svn_error_trace(err); 694251881Speter} 695251881Speter 696251881Speter 697251881Speter 698251881Speter/* Write incoming data into the tmpfile stream */ 699251881Speterstatic svn_error_t * 700251881Speterapply_textdelta(void *file_baton, 701251881Speter const char *base_checksum, 702251881Speter apr_pool_t *pool, 703251881Speter svn_txdelta_window_handler_t *handler, 704251881Speter void **handler_baton) 705251881Speter{ 706251881Speter struct file_baton *fb = file_baton; 707251881Speter struct handler_baton *hb = apr_palloc(pool, sizeof(*hb)); 708251881Speter 709251881Speter /* Create a temporary file in the same directory as the file. We're going 710251881Speter to rename the thing into place when we're done. */ 711251881Speter SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath, 712251881Speter svn_dirent_dirname(fb->path, pool), 713251881Speter svn_io_file_del_none, fb->pool, fb->pool)); 714251881Speter 715251881Speter hb->pool = pool; 716251881Speter hb->tmppath = fb->tmppath; 717251881Speter 718251881Speter /* svn_txdelta_apply() closes the stream, but we want to close it in the 719251881Speter close_file() function, so disown it here. */ 720251881Speter /* ### contrast to when we call svn_ra_get_file() which does NOT close the 721251881Speter ### tmp_stream. we *should* be much more consistent! */ 722251881Speter svn_txdelta_apply(svn_stream_empty(pool), 723251881Speter svn_stream_disown(fb->tmp_stream, pool), 724251881Speter fb->text_digest, NULL, pool, 725251881Speter &hb->apply_handler, &hb->apply_baton); 726251881Speter 727251881Speter *handler_baton = hb; 728251881Speter *handler = window_handler; 729251881Speter return SVN_NO_ERROR; 730251881Speter} 731251881Speter 732251881Speter 733251881Speterstatic svn_error_t * 734251881Speterchange_file_prop(void *file_baton, 735251881Speter const char *name, 736251881Speter const svn_string_t *value, 737251881Speter apr_pool_t *pool) 738251881Speter{ 739251881Speter struct file_baton *fb = file_baton; 740251881Speter 741251881Speter if (! value) 742251881Speter return SVN_NO_ERROR; 743251881Speter 744251881Speter /* Store only the magic three properties. */ 745251881Speter if (strcmp(name, SVN_PROP_EOL_STYLE) == 0) 746251881Speter fb->eol_style_val = svn_string_dup(value, fb->pool); 747251881Speter 748251881Speter else if (! fb->edit_baton->ignore_keywords && 749251881Speter strcmp(name, SVN_PROP_KEYWORDS) == 0) 750251881Speter fb->keywords_val = svn_string_dup(value, fb->pool); 751251881Speter 752251881Speter else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0) 753251881Speter fb->executable_val = svn_string_dup(value, fb->pool); 754251881Speter 755251881Speter /* Try to fill out the baton's keywords-structure too. */ 756251881Speter else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) 757251881Speter fb->revision = apr_pstrdup(fb->pool, value->data); 758251881Speter 759251881Speter else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) 760251881Speter SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool)); 761251881Speter 762251881Speter else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) 763251881Speter fb->author = apr_pstrdup(fb->pool, value->data); 764251881Speter 765251881Speter else if (strcmp(name, SVN_PROP_SPECIAL) == 0) 766251881Speter fb->special = TRUE; 767251881Speter 768251881Speter return SVN_NO_ERROR; 769251881Speter} 770251881Speter 771251881Speter 772251881Speterstatic svn_error_t * 773251881Speterchange_dir_prop(void *dir_baton, 774251881Speter const char *name, 775251881Speter const svn_string_t *value, 776251881Speter apr_pool_t *pool) 777251881Speter{ 778251881Speter struct dir_baton *db = dir_baton; 779251881Speter struct edit_baton *eb = db->edit_baton; 780251881Speter 781251881Speter if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0)) 782251881Speter SVN_ERR(add_externals(eb->externals, db->path, value)); 783251881Speter 784251881Speter return SVN_NO_ERROR; 785251881Speter} 786251881Speter 787251881Speter 788251881Speter/* Move the tmpfile to file, and send feedback. */ 789251881Speterstatic svn_error_t * 790251881Speterclose_file(void *file_baton, 791251881Speter const char *text_digest, 792251881Speter apr_pool_t *pool) 793251881Speter{ 794251881Speter struct file_baton *fb = file_baton; 795251881Speter struct edit_baton *eb = fb->edit_baton; 796251881Speter svn_checksum_t *text_checksum; 797251881Speter svn_checksum_t *actual_checksum; 798251881Speter 799251881Speter /* Was a txdelta even sent? */ 800251881Speter if (! fb->tmppath) 801251881Speter return SVN_NO_ERROR; 802251881Speter 803251881Speter SVN_ERR(svn_stream_close(fb->tmp_stream)); 804251881Speter 805251881Speter SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest, 806251881Speter pool)); 807251881Speter actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool); 808251881Speter 809251881Speter /* Note that text_digest can be NULL when talking to certain repositories. 810251881Speter In that case text_checksum will be NULL and the following match code 811251881Speter will note that the checksums match */ 812251881Speter if (!svn_checksum_match(text_checksum, actual_checksum)) 813251881Speter return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool, 814251881Speter _("Checksum mismatch for '%s'"), 815251881Speter svn_dirent_local_style(fb->path, pool)); 816251881Speter 817251881Speter if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special)) 818251881Speter { 819251881Speter SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool)); 820251881Speter } 821251881Speter else 822251881Speter { 823251881Speter svn_subst_eol_style_t style; 824251881Speter const char *eol = NULL; 825251881Speter svn_boolean_t repair = FALSE; 826251881Speter apr_hash_t *final_kw = NULL; 827251881Speter 828251881Speter if (fb->eol_style_val) 829251881Speter { 830251881Speter SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data, 831251881Speter eb->native_eol)); 832251881Speter repair = TRUE; 833251881Speter } 834251881Speter 835251881Speter if (fb->keywords_val) 836251881Speter SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data, 837251881Speter fb->revision, fb->url, 838251881Speter fb->repos_root_url, fb->date, 839251881Speter fb->author, pool)); 840251881Speter 841251881Speter SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path, 842251881Speter eol, repair, final_kw, 843251881Speter TRUE, /* expand */ 844251881Speter fb->special, 845251881Speter eb->cancel_func, eb->cancel_baton, 846251881Speter pool)); 847251881Speter 848251881Speter SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool)); 849251881Speter } 850251881Speter 851251881Speter if (fb->executable_val) 852251881Speter SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool)); 853251881Speter 854251881Speter if (fb->date && (! fb->special)) 855251881Speter SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool)); 856251881Speter 857251881Speter if (fb->edit_baton->notify_func) 858251881Speter { 859251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(fb->path, 860251881Speter svn_wc_notify_update_add, 861251881Speter pool); 862251881Speter notify->kind = svn_node_file; 863251881Speter (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify, 864251881Speter pool); 865251881Speter } 866251881Speter 867251881Speter return SVN_NO_ERROR; 868251881Speter} 869251881Speter 870251881Speterstatic svn_error_t * 871251881Speterfetch_props_func(apr_hash_t **props, 872251881Speter void *baton, 873251881Speter const char *path, 874251881Speter svn_revnum_t base_revision, 875251881Speter apr_pool_t *result_pool, 876251881Speter apr_pool_t *scratch_pool) 877251881Speter{ 878251881Speter /* Always use empty props, since the node won't have pre-existing props 879251881Speter (This is an export, remember?) */ 880251881Speter *props = apr_hash_make(result_pool); 881251881Speter 882251881Speter return SVN_NO_ERROR; 883251881Speter} 884251881Speter 885251881Speterstatic svn_error_t * 886251881Speterfetch_base_func(const char **filename, 887251881Speter void *baton, 888251881Speter const char *path, 889251881Speter svn_revnum_t base_revision, 890251881Speter apr_pool_t *result_pool, 891251881Speter apr_pool_t *scratch_pool) 892251881Speter{ 893251881Speter /* An export always gets text against the empty stream (i.e, full texts). */ 894251881Speter *filename = NULL; 895251881Speter 896251881Speter return SVN_NO_ERROR; 897251881Speter} 898251881Speter 899251881Speterstatic svn_error_t * 900251881Speterget_editor_ev1(const svn_delta_editor_t **export_editor, 901251881Speter void **edit_baton, 902251881Speter struct edit_baton *eb, 903251881Speter svn_client_ctx_t *ctx, 904251881Speter apr_pool_t *result_pool, 905251881Speter apr_pool_t *scratch_pool) 906251881Speter{ 907251881Speter svn_delta_editor_t *editor = svn_delta_default_editor(result_pool); 908251881Speter 909251881Speter editor->set_target_revision = set_target_revision; 910251881Speter editor->open_root = open_root; 911251881Speter editor->add_directory = add_directory; 912251881Speter editor->add_file = add_file; 913251881Speter editor->apply_textdelta = apply_textdelta; 914251881Speter editor->close_file = close_file; 915251881Speter editor->change_file_prop = change_file_prop; 916251881Speter editor->change_dir_prop = change_dir_prop; 917251881Speter 918251881Speter SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func, 919251881Speter ctx->cancel_baton, 920251881Speter editor, 921251881Speter eb, 922251881Speter export_editor, 923251881Speter edit_baton, 924251881Speter result_pool)); 925251881Speter 926251881Speter return SVN_NO_ERROR; 927251881Speter} 928251881Speter 929251881Speter 930251881Speter/*** The Ev2 Implementation ***/ 931251881Speter 932251881Speterstatic svn_error_t * 933251881Speteradd_file_ev2(void *baton, 934251881Speter const char *relpath, 935251881Speter const svn_checksum_t *checksum, 936251881Speter svn_stream_t *contents, 937251881Speter apr_hash_t *props, 938251881Speter svn_revnum_t replaces_rev, 939251881Speter apr_pool_t *scratch_pool) 940251881Speter{ 941251881Speter struct edit_baton *eb = baton; 942251881Speter const char *full_path = svn_dirent_join(eb->root_path, relpath, 943251881Speter scratch_pool); 944251881Speter /* RELPATH is not canonicalized, i.e. it may still contain spaces etc. 945251881Speter * but EB->root_url is. */ 946251881Speter const char *full_url = svn_path_url_add_component2(eb->root_url, 947251881Speter relpath, 948251881Speter scratch_pool); 949251881Speter const svn_string_t *val; 950251881Speter /* The four svn: properties we might actually care about. */ 951251881Speter const svn_string_t *eol_style_val = NULL; 952251881Speter const svn_string_t *keywords_val = NULL; 953251881Speter const svn_string_t *executable_val = NULL; 954251881Speter svn_boolean_t special = FALSE; 955251881Speter /* Any keyword vals to be substituted */ 956251881Speter const char *revision = NULL; 957251881Speter const char *author = NULL; 958251881Speter apr_time_t date = 0; 959251881Speter 960251881Speter /* Look at any properties for additional information. */ 961251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) ) 962251881Speter eol_style_val = val; 963251881Speter 964251881Speter if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) ) 965251881Speter keywords_val = val; 966251881Speter 967251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) ) 968251881Speter executable_val = val; 969251881Speter 970251881Speter /* Try to fill out the baton's keywords-structure too. */ 971251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) ) 972251881Speter revision = val->data; 973251881Speter 974251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) ) 975251881Speter SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool)); 976251881Speter 977251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) ) 978251881Speter author = val->data; 979251881Speter 980251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) ) 981251881Speter special = TRUE; 982251881Speter 983251881Speter if (special) 984251881Speter { 985251881Speter svn_stream_t *tmp_stream; 986251881Speter 987251881Speter SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path, 988251881Speter scratch_pool, scratch_pool)); 989251881Speter SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func, 990251881Speter eb->cancel_baton, scratch_pool)); 991251881Speter } 992251881Speter else 993251881Speter { 994251881Speter svn_stream_t *tmp_stream; 995251881Speter const char *tmppath; 996251881Speter 997251881Speter /* Create a temporary file in the same directory as the file. We're going 998251881Speter to rename the thing into place when we're done. */ 999251881Speter SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath, 1000251881Speter svn_dirent_dirname(full_path, 1001251881Speter scratch_pool), 1002251881Speter svn_io_file_del_none, 1003251881Speter scratch_pool, scratch_pool)); 1004251881Speter 1005251881Speter /* Possibly wrap the stream to be translated, as dictated by 1006251881Speter the props. */ 1007251881Speter if (eol_style_val || keywords_val) 1008251881Speter { 1009251881Speter svn_subst_eol_style_t style; 1010251881Speter const char *eol = NULL; 1011251881Speter svn_boolean_t repair = FALSE; 1012251881Speter apr_hash_t *final_kw = NULL; 1013251881Speter 1014251881Speter if (eol_style_val) 1015251881Speter { 1016251881Speter SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data, 1017251881Speter eb->native_eol)); 1018251881Speter repair = TRUE; 1019251881Speter } 1020251881Speter 1021251881Speter if (keywords_val) 1022251881Speter SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data, 1023251881Speter revision, full_url, 1024251881Speter eb->repos_root_url, 1025251881Speter date, author, scratch_pool)); 1026251881Speter 1027251881Speter /* Writing through a translated stream is more efficient than 1028251881Speter reading through one, so we wrap TMP_STREAM and not CONTENTS. */ 1029251881Speter tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair, 1030251881Speter final_kw, TRUE, /* expand */ 1031251881Speter scratch_pool); 1032251881Speter } 1033251881Speter 1034251881Speter SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func, 1035251881Speter eb->cancel_baton, scratch_pool)); 1036251881Speter 1037251881Speter /* Move the file into place. */ 1038251881Speter SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool)); 1039251881Speter } 1040251881Speter 1041251881Speter if (executable_val) 1042251881Speter SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool)); 1043251881Speter 1044251881Speter if (date && (! special)) 1045251881Speter SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool)); 1046251881Speter 1047251881Speter if (eb->notify_func) 1048251881Speter { 1049251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(full_path, 1050251881Speter svn_wc_notify_update_add, 1051251881Speter scratch_pool); 1052251881Speter notify->kind = svn_node_file; 1053251881Speter (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); 1054251881Speter } 1055251881Speter 1056251881Speter return SVN_NO_ERROR; 1057251881Speter} 1058251881Speter 1059251881Speterstatic svn_error_t * 1060251881Speteradd_directory_ev2(void *baton, 1061251881Speter const char *relpath, 1062251881Speter const apr_array_header_t *children, 1063251881Speter apr_hash_t *props, 1064251881Speter svn_revnum_t replaces_rev, 1065251881Speter apr_pool_t *scratch_pool) 1066251881Speter{ 1067251881Speter struct edit_baton *eb = baton; 1068251881Speter svn_node_kind_t kind; 1069251881Speter const char *full_path = svn_dirent_join(eb->root_path, relpath, 1070251881Speter scratch_pool); 1071251881Speter svn_string_t *val; 1072251881Speter 1073251881Speter SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool)); 1074251881Speter if (kind == svn_node_none) 1075251881Speter SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool)); 1076251881Speter else if (kind == svn_node_file) 1077251881Speter return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, 1078251881Speter _("'%s' exists and is not a directory"), 1079251881Speter svn_dirent_local_style(full_path, scratch_pool)); 1080251881Speter else if (! (kind == svn_node_dir && eb->force)) 1081251881Speter return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, 1082251881Speter _("'%s' already exists"), 1083251881Speter svn_dirent_local_style(full_path, scratch_pool)); 1084251881Speter 1085251881Speter if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) ) 1086251881Speter SVN_ERR(add_externals(eb->externals, full_path, val)); 1087251881Speter 1088251881Speter if (eb->notify_func) 1089251881Speter { 1090251881Speter svn_wc_notify_t *notify = svn_wc_create_notify(full_path, 1091251881Speter svn_wc_notify_update_add, 1092251881Speter scratch_pool); 1093251881Speter notify->kind = svn_node_dir; 1094251881Speter (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); 1095251881Speter } 1096251881Speter 1097251881Speter return SVN_NO_ERROR; 1098251881Speter} 1099251881Speter 1100251881Speterstatic svn_error_t * 1101251881Spetertarget_revision_func(void *baton, 1102251881Speter svn_revnum_t target_revision, 1103251881Speter apr_pool_t *scratch_pool) 1104251881Speter{ 1105251881Speter struct edit_baton *eb = baton; 1106251881Speter 1107251881Speter *eb->target_revision = target_revision; 1108251881Speter 1109251881Speter return SVN_NO_ERROR; 1110251881Speter} 1111251881Speter 1112251881Speterstatic svn_error_t * 1113251881Speterget_editor_ev2(const svn_delta_editor_t **export_editor, 1114251881Speter void **edit_baton, 1115251881Speter struct edit_baton *eb, 1116251881Speter svn_client_ctx_t *ctx, 1117251881Speter apr_pool_t *result_pool, 1118251881Speter apr_pool_t *scratch_pool) 1119251881Speter{ 1120251881Speter svn_editor_t *editor; 1121251881Speter struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb)); 1122251881Speter svn_boolean_t *found_abs_paths = apr_palloc(result_pool, 1123251881Speter sizeof(*found_abs_paths)); 1124251881Speter 1125251881Speter exb->baton = eb; 1126251881Speter exb->target_revision = target_revision_func; 1127251881Speter 1128251881Speter SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton, 1129251881Speter result_pool, scratch_pool)); 1130251881Speter SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2, 1131251881Speter scratch_pool)); 1132251881Speter SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool)); 1133251881Speter 1134251881Speter *found_abs_paths = TRUE; 1135251881Speter 1136251881Speter SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton, 1137251881Speter editor, NULL, NULL, found_abs_paths, 1138251881Speter NULL, NULL, 1139251881Speter fetch_props_func, eb, 1140251881Speter fetch_base_func, eb, 1141251881Speter exb, result_pool)); 1142251881Speter 1143251881Speter /* Create the root of the export. */ 1144251881Speter SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func, 1145251881Speter eb->notify_baton, scratch_pool)); 1146251881Speter 1147251881Speter return SVN_NO_ERROR; 1148251881Speter} 1149251881Speter 1150251881Speterstatic svn_error_t * 1151251881Speterexport_file_ev2(const char *from_path_or_url, 1152251881Speter const char *to_path, 1153251881Speter struct edit_baton *eb, 1154251881Speter svn_client__pathrev_t *loc, 1155251881Speter svn_ra_session_t *ra_session, 1156251881Speter svn_boolean_t overwrite, 1157251881Speter apr_pool_t *scratch_pool) 1158251881Speter{ 1159251881Speter svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url); 1160251881Speter apr_hash_t *props; 1161251881Speter svn_stream_t *tmp_stream; 1162251881Speter svn_node_kind_t to_kind; 1163251881Speter 1164251881Speter if (svn_path_is_empty(to_path)) 1165251881Speter { 1166251881Speter if (from_is_url) 1167251881Speter to_path = svn_uri_basename(from_path_or_url, scratch_pool); 1168251881Speter else 1169251881Speter to_path = svn_dirent_basename(from_path_or_url, NULL); 1170251881Speter eb->root_path = to_path; 1171251881Speter } 1172251881Speter else 1173251881Speter { 1174251881Speter SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, 1175251881Speter from_is_url, scratch_pool)); 1176251881Speter eb->root_path = to_path; 1177251881Speter } 1178251881Speter 1179251881Speter SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool)); 1180251881Speter 1181251881Speter if ((to_kind == svn_node_file || to_kind == svn_node_unknown) && 1182251881Speter ! overwrite) 1183251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1184251881Speter _("Destination file '%s' exists, and " 1185251881Speter "will not be overwritten unless forced"), 1186251881Speter svn_dirent_local_style(to_path, scratch_pool)); 1187251881Speter else if (to_kind == svn_node_dir) 1188251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1189251881Speter _("Destination '%s' exists. Cannot " 1190251881Speter "overwrite directory with non-directory"), 1191251881Speter svn_dirent_local_style(to_path, scratch_pool)); 1192251881Speter 1193251881Speter tmp_stream = svn_stream_buffered(scratch_pool); 1194251881Speter 1195251881Speter SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, 1196251881Speter tmp_stream, NULL, &props, scratch_pool)); 1197251881Speter 1198251881Speter /* Since you cannot actually root an editor at a file, we manually drive 1199251881Speter * a function of our editor. */ 1200251881Speter SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM, 1201251881Speter scratch_pool)); 1202251881Speter 1203251881Speter return SVN_NO_ERROR; 1204251881Speter} 1205251881Speter 1206251881Speterstatic svn_error_t * 1207251881Speterexport_file(const char *from_path_or_url, 1208251881Speter const char *to_path, 1209251881Speter struct edit_baton *eb, 1210251881Speter svn_client__pathrev_t *loc, 1211251881Speter svn_ra_session_t *ra_session, 1212251881Speter svn_boolean_t overwrite, 1213251881Speter apr_pool_t *scratch_pool) 1214251881Speter{ 1215251881Speter apr_hash_t *props; 1216251881Speter apr_hash_index_t *hi; 1217251881Speter struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb)); 1218251881Speter svn_node_kind_t to_kind; 1219251881Speter svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url); 1220251881Speter 1221251881Speter if (svn_path_is_empty(to_path)) 1222251881Speter { 1223251881Speter if (from_is_url) 1224251881Speter to_path = svn_uri_basename(from_path_or_url, scratch_pool); 1225251881Speter else 1226251881Speter to_path = svn_dirent_basename(from_path_or_url, NULL); 1227251881Speter eb->root_path = to_path; 1228251881Speter } 1229251881Speter else 1230251881Speter { 1231251881Speter SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, 1232251881Speter from_is_url, scratch_pool)); 1233251881Speter eb->root_path = to_path; 1234251881Speter } 1235251881Speter 1236251881Speter SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool)); 1237251881Speter 1238251881Speter if ((to_kind == svn_node_file || to_kind == svn_node_unknown) && 1239251881Speter ! overwrite) 1240251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1241251881Speter _("Destination file '%s' exists, and " 1242251881Speter "will not be overwritten unless forced"), 1243251881Speter svn_dirent_local_style(to_path, scratch_pool)); 1244251881Speter else if (to_kind == svn_node_dir) 1245251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1246251881Speter _("Destination '%s' exists. Cannot " 1247251881Speter "overwrite directory with non-directory"), 1248251881Speter svn_dirent_local_style(to_path, scratch_pool)); 1249251881Speter 1250251881Speter /* Since you cannot actually root an editor at a file, we 1251251881Speter * manually drive a few functions of our editor. */ 1252251881Speter 1253251881Speter /* This is the equivalent of a parentless add_file(). */ 1254251881Speter fb->edit_baton = eb; 1255251881Speter fb->path = eb->root_path; 1256251881Speter fb->url = eb->root_url; 1257251881Speter fb->pool = scratch_pool; 1258251881Speter fb->repos_root_url = eb->repos_root_url; 1259251881Speter 1260251881Speter /* Copied from apply_textdelta(). */ 1261251881Speter SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath, 1262251881Speter svn_dirent_dirname(fb->path, scratch_pool), 1263251881Speter svn_io_file_del_none, 1264251881Speter fb->pool, fb->pool)); 1265251881Speter 1266251881Speter /* Step outside the editor-likeness for a moment, to actually talk 1267251881Speter * to the repository. */ 1268251881Speter /* ### note: the stream will not be closed */ 1269251881Speter SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev, 1270251881Speter fb->tmp_stream, 1271251881Speter NULL, &props, scratch_pool)); 1272251881Speter 1273251881Speter /* Push the props into change_file_prop(), to update the file_baton 1274251881Speter * with information. */ 1275251881Speter for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi)) 1276251881Speter { 1277251881Speter const char *propname = svn__apr_hash_index_key(hi); 1278251881Speter const svn_string_t *propval = svn__apr_hash_index_val(hi); 1279251881Speter 1280251881Speter SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool)); 1281251881Speter } 1282251881Speter 1283251881Speter /* And now just use close_file() to do all the keyword and EOL 1284251881Speter * work, and put the file into place. */ 1285251881Speter SVN_ERR(close_file(fb, NULL, scratch_pool)); 1286251881Speter 1287251881Speter return SVN_NO_ERROR; 1288251881Speter} 1289251881Speter 1290251881Speterstatic svn_error_t * 1291251881Speterexport_directory(const char *from_path_or_url, 1292251881Speter const char *to_path, 1293251881Speter struct edit_baton *eb, 1294251881Speter svn_client__pathrev_t *loc, 1295251881Speter svn_ra_session_t *ra_session, 1296251881Speter svn_boolean_t overwrite, 1297251881Speter svn_boolean_t ignore_externals, 1298251881Speter svn_boolean_t ignore_keywords, 1299251881Speter svn_depth_t depth, 1300251881Speter const char *native_eol, 1301251881Speter svn_client_ctx_t *ctx, 1302251881Speter apr_pool_t *scratch_pool) 1303251881Speter{ 1304251881Speter void *edit_baton; 1305251881Speter const svn_delta_editor_t *export_editor; 1306251881Speter const svn_ra_reporter3_t *reporter; 1307251881Speter void *report_baton; 1308251881Speter svn_node_kind_t kind; 1309251881Speter 1310251881Speter if (!ENABLE_EV2_IMPL) 1311251881Speter SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx, 1312251881Speter scratch_pool, scratch_pool)); 1313251881Speter else 1314251881Speter SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx, 1315251881Speter scratch_pool, scratch_pool)); 1316251881Speter 1317251881Speter /* Manufacture a basic 'report' to the update reporter. */ 1318251881Speter SVN_ERR(svn_ra_do_update3(ra_session, 1319251881Speter &reporter, &report_baton, 1320251881Speter loc->rev, 1321251881Speter "", /* no sub-target */ 1322251881Speter depth, 1323251881Speter FALSE, /* don't want copyfrom-args */ 1324251881Speter FALSE, /* don't want ignore_ancestry */ 1325251881Speter export_editor, edit_baton, 1326251881Speter scratch_pool, scratch_pool)); 1327251881Speter 1328251881Speter SVN_ERR(reporter->set_path(report_baton, "", loc->rev, 1329251881Speter /* Depth is irrelevant, as we're 1330251881Speter passing start_empty=TRUE anyway. */ 1331251881Speter svn_depth_infinity, 1332251881Speter TRUE, /* "help, my dir is empty!" */ 1333251881Speter NULL, scratch_pool)); 1334251881Speter 1335251881Speter SVN_ERR(reporter->finish_report(report_baton, scratch_pool)); 1336251881Speter 1337251881Speter /* Special case: Due to our sly export/checkout method of updating an 1338251881Speter * empty directory, no target will have been created if the exported 1339251881Speter * item is itself an empty directory (export_editor->open_root never 1340251881Speter * gets called, because there are no "changes" to make to the empty 1341251881Speter * dir we reported to the repository). 1342251881Speter * 1343251881Speter * So we just create the empty dir manually; but we do it via 1344251881Speter * open_root_internal(), in order to get proper notification. 1345251881Speter */ 1346251881Speter SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool)); 1347251881Speter if (kind == svn_node_none) 1348251881Speter SVN_ERR(open_root_internal 1349251881Speter (to_path, overwrite, ctx->notify_func2, 1350251881Speter ctx->notify_baton2, scratch_pool)); 1351251881Speter 1352251881Speter if (! ignore_externals && depth == svn_depth_infinity) 1353251881Speter { 1354251881Speter const char *to_abspath; 1355251881Speter 1356251881Speter SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool)); 1357251881Speter SVN_ERR(svn_client__export_externals(eb->externals, 1358251881Speter from_path_or_url, 1359251881Speter to_abspath, eb->repos_root_url, 1360251881Speter depth, native_eol, 1361251881Speter ignore_keywords, 1362251881Speter ctx, scratch_pool)); 1363251881Speter } 1364251881Speter 1365251881Speter return SVN_NO_ERROR; 1366251881Speter} 1367251881Speter 1368251881Speter 1369251881Speter 1370251881Speter/*** Public Interfaces ***/ 1371251881Speter 1372251881Spetersvn_error_t * 1373251881Spetersvn_client_export5(svn_revnum_t *result_rev, 1374251881Speter const char *from_path_or_url, 1375251881Speter const char *to_path, 1376251881Speter const svn_opt_revision_t *peg_revision, 1377251881Speter const svn_opt_revision_t *revision, 1378251881Speter svn_boolean_t overwrite, 1379251881Speter svn_boolean_t ignore_externals, 1380251881Speter svn_boolean_t ignore_keywords, 1381251881Speter svn_depth_t depth, 1382251881Speter const char *native_eol, 1383251881Speter svn_client_ctx_t *ctx, 1384251881Speter apr_pool_t *pool) 1385251881Speter{ 1386251881Speter svn_revnum_t edit_revision = SVN_INVALID_REVNUM; 1387251881Speter svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url); 1388251881Speter 1389251881Speter SVN_ERR_ASSERT(peg_revision != NULL); 1390251881Speter SVN_ERR_ASSERT(revision != NULL); 1391251881Speter 1392251881Speter if (svn_path_is_url(to_path)) 1393251881Speter return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, 1394251881Speter _("'%s' is not a local path"), to_path); 1395251881Speter 1396251881Speter peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, 1397251881Speter from_path_or_url); 1398251881Speter revision = svn_cl__rev_default_to_peg(revision, peg_revision); 1399251881Speter 1400251881Speter if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) 1401251881Speter { 1402251881Speter svn_client__pathrev_t *loc; 1403251881Speter svn_ra_session_t *ra_session; 1404251881Speter svn_node_kind_t kind; 1405251881Speter struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); 1406251881Speter 1407251881Speter /* Get the RA connection. */ 1408251881Speter SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, 1409251881Speter from_path_or_url, NULL, 1410251881Speter peg_revision, 1411251881Speter revision, ctx, pool)); 1412251881Speter 1413251881Speter SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool)); 1414251881Speter eb->root_path = to_path; 1415251881Speter eb->root_url = loc->url; 1416251881Speter eb->force = overwrite; 1417251881Speter eb->target_revision = &edit_revision; 1418251881Speter eb->externals = apr_hash_make(pool); 1419251881Speter eb->native_eol = native_eol; 1420251881Speter eb->ignore_keywords = ignore_keywords; 1421251881Speter eb->cancel_func = ctx->cancel_func; 1422251881Speter eb->cancel_baton = ctx->cancel_baton; 1423251881Speter eb->notify_func = ctx->notify_func2; 1424251881Speter eb->notify_baton = ctx->notify_baton2; 1425251881Speter 1426251881Speter SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool)); 1427251881Speter 1428251881Speter if (kind == svn_node_file) 1429251881Speter { 1430251881Speter if (!ENABLE_EV2_IMPL) 1431251881Speter SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session, 1432251881Speter overwrite, pool)); 1433251881Speter else 1434251881Speter SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc, 1435251881Speter ra_session, overwrite, pool)); 1436251881Speter } 1437251881Speter else if (kind == svn_node_dir) 1438251881Speter { 1439251881Speter SVN_ERR(export_directory(from_path_or_url, to_path, 1440251881Speter eb, loc, ra_session, overwrite, 1441251881Speter ignore_externals, ignore_keywords, depth, 1442251881Speter native_eol, ctx, pool)); 1443251881Speter } 1444251881Speter else if (kind == svn_node_none) 1445251881Speter { 1446251881Speter return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, 1447251881Speter _("URL '%s' doesn't exist"), 1448251881Speter from_path_or_url); 1449251881Speter } 1450251881Speter /* kind == svn_node_unknown not handled */ 1451251881Speter } 1452251881Speter else 1453251881Speter { 1454251881Speter struct export_info_baton eib; 1455251881Speter svn_node_kind_t kind; 1456251881Speter apr_hash_t *externals = NULL; 1457251881Speter 1458251881Speter /* This is a working copy export. */ 1459251881Speter /* just copy the contents of the working copy into the target path. */ 1460251881Speter SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url, 1461251881Speter pool)); 1462251881Speter 1463251881Speter SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool)); 1464251881Speter 1465251881Speter SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool)); 1466251881Speter 1467251881Speter /* ### [JAF] If something already exists on disk at the destination path, 1468251881Speter * the behaviour depends on the node kinds of the source and destination 1469251881Speter * and on the FORCE flag. The intention (I guess) is to follow the 1470251881Speter * semantics of svn_client_export5(), semantics that are not fully 1471251881Speter * documented but would be something like: 1472251881Speter * 1473251881Speter * -----------+--------------------------------------------------------- 1474251881Speter * Src | DIR FILE SPECIAL 1475251881Speter * Dst (disk) +--------------------------------------------------------- 1476251881Speter * NONE | simple copy simple copy (as src=file?) 1477251881Speter * DIR | merge if forced [2] inside if root [1] (as src=file?) 1478251881Speter * FILE | err overwr if forced[3] (as src=file?) 1479251881Speter * SPECIAL | ??? ??? ??? 1480251881Speter * -----------+--------------------------------------------------------- 1481251881Speter * 1482251881Speter * [1] FILE onto DIR case: If this file is the root of the copy and thus 1483251881Speter * the only node to be copied, then copy it as a child of the 1484251881Speter * directory TO, applying these same rules again except that if this 1485251881Speter * case occurs again (the child path is already a directory) then 1486251881Speter * error out. If this file is not the root of the copy (it is 1487251881Speter * reached by recursion), then error out. 1488251881Speter * 1489251881Speter * [2] DIR onto DIR case. If the 'FORCE' flag is true then copy the 1490251881Speter * source's children inside the target dir, else error out. When 1491251881Speter * copying the children, apply the same set of rules, except in the 1492251881Speter * FILE onto DIR case error out like in note [1]. 1493251881Speter * 1494251881Speter * [3] If the 'FORCE' flag is true then overwrite the destination file 1495251881Speter * else error out. 1496251881Speter * 1497251881Speter * The reality (apparently, looking at the code) is somewhat different. 1498251881Speter * For a start, to detect the source kind, it looks at what is on disk 1499251881Speter * rather than the versioned working or base node. 1500251881Speter */ 1501251881Speter if (kind == svn_node_file) 1502251881Speter SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE, 1503251881Speter pool)); 1504251881Speter 1505251881Speter eib.to_path = to_path; 1506251881Speter eib.revision = revision; 1507251881Speter eib.overwrite = overwrite; 1508251881Speter eib.ignore_keywords = ignore_keywords; 1509251881Speter eib.wc_ctx = ctx->wc_ctx; 1510251881Speter eib.native_eol = native_eol; 1511251881Speter eib.notify_func = ctx->notify_func2;; 1512251881Speter eib.notify_baton = ctx->notify_baton2; 1513251881Speter eib.origin_abspath = from_path_or_url; 1514251881Speter eib.exported = FALSE; 1515251881Speter 1516251881Speter SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth, 1517251881Speter TRUE /* get_all */, 1518251881Speter TRUE /* no_ignore */, 1519251881Speter FALSE /* ignore_text_mods */, 1520251881Speter NULL, 1521251881Speter export_node, &eib, 1522251881Speter ctx->cancel_func, ctx->cancel_baton, 1523251881Speter pool)); 1524251881Speter 1525251881Speter if (!eib.exported) 1526251881Speter return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, 1527251881Speter _("The node '%s' was not found."), 1528251881Speter svn_dirent_local_style(from_path_or_url, 1529251881Speter pool)); 1530251881Speter 1531251881Speter if (!ignore_externals) 1532251881Speter SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx, 1533251881Speter from_path_or_url, 1534251881Speter pool, pool)); 1535251881Speter 1536251881Speter if (externals && apr_hash_count(externals)) 1537251881Speter { 1538251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 1539251881Speter apr_hash_index_t *hi; 1540251881Speter 1541251881Speter for (hi = apr_hash_first(pool, externals); 1542251881Speter hi; 1543251881Speter hi = apr_hash_next(hi)) 1544251881Speter { 1545251881Speter const char *external_abspath = svn__apr_hash_index_key(hi); 1546251881Speter const char *relpath; 1547251881Speter const char *target_abspath; 1548251881Speter 1549251881Speter svn_pool_clear(iterpool); 1550251881Speter 1551251881Speter relpath = svn_dirent_skip_ancestor(from_path_or_url, 1552251881Speter external_abspath); 1553251881Speter 1554251881Speter target_abspath = svn_dirent_join(to_path, relpath, 1555251881Speter iterpool); 1556251881Speter 1557251881Speter /* Ensure that the parent directory exists */ 1558251881Speter SVN_ERR(svn_io_make_dir_recursively( 1559251881Speter svn_dirent_dirname(target_abspath, iterpool), 1560251881Speter iterpool)); 1561251881Speter 1562251881Speter SVN_ERR(svn_client_export5(NULL, 1563251881Speter svn_dirent_join(from_path_or_url, 1564251881Speter relpath, 1565251881Speter iterpool), 1566251881Speter target_abspath, 1567251881Speter peg_revision, revision, 1568251881Speter TRUE, ignore_externals, 1569251881Speter ignore_keywords, depth, native_eol, 1570251881Speter ctx, iterpool)); 1571251881Speter } 1572251881Speter 1573251881Speter svn_pool_destroy(iterpool); 1574251881Speter } 1575251881Speter } 1576251881Speter 1577251881Speter 1578251881Speter if (ctx->notify_func2) 1579251881Speter { 1580251881Speter svn_wc_notify_t *notify 1581251881Speter = svn_wc_create_notify(to_path, 1582251881Speter svn_wc_notify_update_completed, pool); 1583251881Speter notify->revision = edit_revision; 1584251881Speter (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); 1585251881Speter } 1586251881Speter 1587251881Speter if (result_rev) 1588251881Speter *result_rev = edit_revision; 1589251881Speter 1590251881Speter return SVN_NO_ERROR; 1591251881Speter} 1592269847Speter 1593