/* * export.c: export a tree. * * ==================================================================== * Copyright (c) 2000-2009 CollabNet. All rights reserved. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms * are also available at http://subversion.tigris.org/license-1.html. * If newer versions of this license are posted there, you may use a * newer version instead, at your option. * * This software consists of voluntary contributions made by many * individuals. For exact contribution history, see the revision * history and logs, available at http://subversion.tigris.org/. * ==================================================================== */ /* ==================================================================== */ /*** Includes. ***/ #include #include #include "svn_types.h" #include "svn_client.h" #include "svn_string.h" #include "svn_error.h" #include "svn_dirent_uri.h" #include "svn_path.h" #include "svn_pools.h" #include "svn_subst.h" #include "svn_time.h" #include "svn_props.h" #include "client.h" #include "svn_private_config.h" #include "private/svn_wc_private.h" /*** Code. ***/ /* Add EXTERNALS_PROP_VAL for the export destination path PATH to TRAVERSAL_INFO. */ static void add_externals(apr_hash_t *externals, const char *path, const svn_string_t *externals_prop_val) { apr_pool_t *pool = apr_hash_pool_get(externals); if (! externals_prop_val) return; apr_hash_set(externals, apr_pstrdup(pool, path), APR_HASH_KEY_STRING, apr_pstrmemdup(pool, externals_prop_val->data, externals_prop_val->len)); } /* Helper function that gets the eol style and optionally overrides the EOL marker for files marked as native with the EOL marker matching the string specified in requested_value which is of the same format as the svn:eol-style property values. */ static svn_error_t * get_eol_style(svn_subst_eol_style_t *style, const char **eol, const char *value, const char *requested_value) { svn_subst_eol_style_from_value(style, eol, value); if (requested_value && *style == svn_subst_eol_style_native) { svn_subst_eol_style_t requested_style; const char *requested_eol; svn_subst_eol_style_from_value(&requested_style, &requested_eol, requested_value); if (requested_style == svn_subst_eol_style_fixed) *eol = requested_eol; else return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL, _("'%s' is not a valid EOL value"), requested_value); } return SVN_NO_ERROR; } static svn_error_t * copy_one_versioned_file(const char *from, const char *to, svn_wc_adm_access_t *adm_access, svn_wc_context_t *wc_ctx, const svn_opt_revision_t *revision, const char *native_eol, apr_pool_t *pool) { const svn_wc_entry_t *entry; apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *executable, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *source; svn_stream_t *dst_stream; const char *dst_tmp; svn_error_t *err; const char *from_abspath; SVN_ERR(svn_dirent_get_absolute(&from_abspath, from, pool)); SVN_ERR(svn_wc_entry(&entry, from, adm_access, FALSE, pool)); /* Only export 'added' files when the revision is WORKING. Otherwise, skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 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 && entry->schedule == svn_wc_schedule_add) || (revision->kind == svn_opt_revision_working && entry->schedule == svn_wc_schedule_delete)) return SVN_NO_ERROR; if (revision->kind != svn_opt_revision_working) { SVN_ERR(svn_wc_get_pristine_contents(&source, from, pool, pool)); SVN_ERR(svn_wc_get_prop_diffs2(NULL, &props, wc_ctx, from_abspath, pool, pool)); } else { svn_wc_status2_t *status; /* ### hmm. this isn't always a specialfile. this will simply open ### the file readonly if it is a regular file. */ SVN_ERR(svn_subst_read_specialfile(&source, from, pool, pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, from_abspath, pool, pool)); SVN_ERR(svn_wc_status2(&status, from, adm_access, pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } /* We can early-exit if we're creating a special file. */ special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (special != NULL) { /* Create the destination as a special file, and copy the source details into the destination stream. */ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to, pool, pool)); return svn_stream_copy3(source, dst_stream, NULL, NULL, pool); } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); executable = apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING); if (eol_style) SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol)); if (local_mod) { /* Use the modified time from the working copy of the file */ SVN_ERR(svn_io_file_affected_time(&tm, from, pool)); } else { tm = entry->cmt_date; } if (keywords) { const char *fmt; const char *author; if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ fmt = "%ldM"; author = _("(local)"); } else { fmt = "%ld"; author = entry->cmt_author; } SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, apr_psprintf(pool, fmt, entry->cmt_rev), entry->url, tm, author, pool)); } /* For atomicity, we translate to a tmp file and then rename the tmp file over the real destination. */ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, svn_dirent_dirname(to, pool), svn_io_file_del_none, pool, pool)); /* If some translation is needed, then wrap the output stream (this is more efficient than wrapping the input). */ if (eol || (kw && (apr_hash_count(kw) > 0))) dst_stream = svn_subst_stream_translated(dst_stream, eol, FALSE /* repair */, kw, TRUE /* expand */, pool); /* ###: use cancel func/baton in place of NULL/NULL below. */ err = svn_stream_copy3(source, dst_stream, NULL, NULL, pool); if (!err && executable) err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, pool); if (!err) err = svn_io_set_file_affected_time(tm, dst_tmp, pool); if (err) return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, pool)); /* Now that dst_tmp contains the translated data, do the atomic rename. */ return svn_io_file_rename(dst_tmp, to, pool); } static svn_error_t * copy_versioned_files(const char *from, const char *to, const svn_opt_revision_t *revision, svn_boolean_t force, svn_boolean_t ignore_externals, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, svn_wc_context_t *wc_ctx, apr_pool_t *pool) { svn_wc_adm_access_t *adm_access; const svn_wc_entry_t *entry; svn_error_t *err; apr_pool_t *iterpool; apr_hash_t *entries; apr_hash_index_t *hi; SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, from, FALSE, 0, ctx->cancel_func, ctx->cancel_baton, pool)); SVN_ERR(svn_wc__entry_versioned(&entry, from, adm_access, FALSE, pool)); /* Only export 'added' files when the revision is WORKING. Otherwise, skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 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 && entry->schedule == svn_wc_schedule_add) || (revision->kind == svn_opt_revision_working && entry->schedule == svn_wc_schedule_delete)) return SVN_NO_ERROR; if (entry->kind == svn_node_dir) { /* Try to make the new directory. If this fails because the directory already exists, check our FORCE flag to see if we care. */ /* 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' */ #ifdef WIN32 err = svn_io_dir_make(to, APR_OS_DEFAULT, pool); #else apr_finfo_t finfo; SVN_ERR(svn_io_stat(&finfo, from, APR_FINFO_PROT, pool)); err = svn_io_dir_make(to, finfo.protection, pool); #endif if (err) { if (! APR_STATUS_IS_EEXIST(err->apr_err)) return svn_error_return(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_entries_read(&entries, adm_access, FALSE, pool)); iterpool = svn_pool_create(pool); for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi)) { const char *item; const void *key; void *val; svn_pool_clear(iterpool); apr_hash_this(hi, &key, NULL, &val); item = key; entry = val; if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); /* ### We could also invoke ctx->notify_func somewhere in ### here... Is it called for, though? Not sure. */ if (entry->kind == svn_node_dir) { if (strcmp(item, SVN_WC_ENTRY_THIS_DIR) == 0) { ; /* skip this, it's the current directory that we're handling now. */ } else { if (depth == svn_depth_infinity) { const char *new_from = svn_path_join(from, item, iterpool); const char *new_to = svn_path_join(to, item, iterpool); SVN_ERR(copy_versioned_files(new_from, new_to, revision, force, ignore_externals, depth, native_eol, ctx, wc_ctx, iterpool)); } } } else if (entry->kind == svn_node_file) { const char *new_from = svn_path_join(from, item, iterpool); const char *new_to = svn_path_join(to, item, iterpool); SVN_ERR(copy_one_versioned_file(new_from, new_to, adm_access, wc_ctx, revision, native_eol, iterpool)); } } /* Handle externals. */ if (! ignore_externals && depth == svn_depth_infinity && entry->depth == svn_depth_infinity) { apr_array_header_t *ext_items; const svn_string_t *prop_val; const char *from_abspath; SVN_ERR(svn_dirent_get_absolute(&from_abspath, from, pool)); SVN_ERR(svn_wc_prop_get2(&prop_val, 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, 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_path_join(from, ext_item->target_dir, iterpool); new_to = svn_path_join(to, ext_item->target_dir, iterpool); /* The target dir might have multiple components. Guarantee the path leading down to the last component. */ if (svn_path_component_count(ext_item->target_dir) > 1) { 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, svn_depth_infinity, native_eol, ctx, wc_ctx, iterpool)); } } } svn_pool_destroy(iterpool); } else if (entry->kind == svn_node_file) { SVN_ERR(copy_one_versioned_file(from, to, adm_access, wc_ctx, revision, native_eol, pool)); } return svn_wc_adm_close2(adm_access, pool); } /* Abstraction of open_root. * * Create PATH if it does not exist and is not obstructed, and invoke * NOTIFY_FUNC with NOTIFY_BATON on PATH. * * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_DIRECTORY. * * If PATH is a already a directory, then error with * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just * export into PATH with no error. */ static svn_error_t * open_root_internal(const char *path, svn_boolean_t force, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *pool) { svn_node_kind_t kind; SVN_ERR(svn_io_check_path(path, &kind, pool)); if (kind == svn_node_none) SVN_ERR(svn_io_make_dir_recursively(path, pool)); else if (kind == svn_node_file) return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, _("'%s' exists and is not a directory"), svn_dirent_local_style(path, pool)); else if ((kind != svn_node_dir) || (! force)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' already exists"), svn_dirent_local_style(path, pool)); if (notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*notify_func)(notify_baton, notify, pool); } return SVN_NO_ERROR; } /* ---------------------------------------------------------------------- */ /*** A dedicated 'export' editor, which does no .svn/ accounting. ***/ struct edit_baton { const char *root_path; const char *root_url; svn_boolean_t force; svn_revnum_t *target_revision; apr_hash_t *externals; const char *native_eol; svn_wc_notify_func2_t notify_func; void *notify_baton; }; struct dir_baton { struct edit_baton *edit_baton; const char *path; }; struct file_baton { struct edit_baton *edit_baton; const char *path; const char *tmppath; /* We need to keep this around so we can explicitly close it in close_file, thus flushing its output to disk so we can copy and translate it. */ svn_stream_t *tmp_stream; /* The MD5 digest of the file's fulltext. This is all zeros until the last textdelta window handler call returns. */ unsigned char text_digest[APR_MD5_DIGESTSIZE]; /* The three svn: properties we might actually care about. */ const svn_string_t *eol_style_val; const svn_string_t *keywords_val; const svn_string_t *executable_val; svn_boolean_t special; /* Any keyword vals to be substituted */ const char *revision; const char *url; const char *author; apr_time_t date; /* Pool associated with this baton. */ apr_pool_t *pool; }; struct handler_baton { svn_txdelta_window_handler_t apply_handler; void *apply_baton; apr_pool_t *pool; const char *tmppath; }; static svn_error_t * set_target_revision(void *edit_baton, svn_revnum_t target_revision, apr_pool_t *pool) { struct edit_baton *eb = edit_baton; /* Stashing a target_revision in the baton */ *(eb->target_revision) = target_revision; return SVN_NO_ERROR; } /* Just ensure that the main export directory exists. */ static svn_error_t * open_root(void *edit_baton, svn_revnum_t base_revision, apr_pool_t *pool, void **root_baton) { struct edit_baton *eb = edit_baton; struct dir_baton *db = apr_pcalloc(pool, sizeof(*db)); SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func, eb->notify_baton, pool)); /* Build our dir baton. */ db->path = eb->root_path; db->edit_baton = eb; *root_baton = db; return SVN_NO_ERROR; } /* Ensure the directory exists, and send feedback. */ static svn_error_t * add_directory(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *pool, void **baton) { struct dir_baton *pb = parent_baton; struct dir_baton *db = apr_pcalloc(pool, sizeof(*db)); struct edit_baton *eb = pb->edit_baton; const char *full_path = svn_path_join(eb->root_path, path, pool); svn_node_kind_t kind; SVN_ERR(svn_io_check_path(full_path, &kind, pool)); if (kind == svn_node_none) SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool)); else if (kind == svn_node_file) return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL, _("'%s' exists and is not a directory"), svn_dirent_local_style(full_path, pool)); else if (! (kind == svn_node_dir && eb->force)) return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL, _("'%s' already exists"), svn_dirent_local_style(full_path, pool)); if (eb->notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(full_path, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*eb->notify_func)(eb->notify_baton, notify, pool); } /* Build our dir baton. */ db->path = full_path; db->edit_baton = eb; *baton = db; return SVN_NO_ERROR; } /* Build a file baton. */ static svn_error_t * add_file(const char *path, void *parent_baton, const char *copyfrom_path, svn_revnum_t copyfrom_revision, apr_pool_t *pool, void **baton) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb)); const char *full_path = svn_path_join(eb->root_path, path, pool); const char *full_url = svn_path_join(eb->root_url, path, pool); fb->edit_baton = eb; fb->path = full_path; fb->url = full_url; fb->pool = pool; *baton = fb; return SVN_NO_ERROR; } static svn_error_t * window_handler(svn_txdelta_window_t *window, void *baton) { struct handler_baton *hb = baton; svn_error_t *err; err = hb->apply_handler(window, hb->apply_baton); if (err) { /* We failed to apply the patch; clean up the temporary file. */ svn_error_clear(svn_io_remove_file2(hb->tmppath, TRUE, hb->pool)); } return svn_error_return(err); } /* Write incoming data into the tmpfile stream */ static svn_error_t * apply_textdelta(void *file_baton, const char *base_checksum, apr_pool_t *pool, svn_txdelta_window_handler_t *handler, void **handler_baton) { struct file_baton *fb = file_baton; struct handler_baton *hb = apr_palloc(pool, sizeof(*hb)); /* Create a temporary file in the same directory as the file. We're going to rename the thing into place when we're done. */ SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath, svn_dirent_dirname(fb->path, pool), svn_io_file_del_none, fb->pool, fb->pool)); hb->pool = pool; hb->tmppath = fb->tmppath; /* svn_txdelta_apply() closes the stream, but we want to close it in the close_file() function, so disown it here. */ /* ### contrast to when we call svn_ra_get_file() which does NOT close the ### tmp_stream. we *should* be much more consistent! */ svn_txdelta_apply(svn_stream_empty(pool), svn_stream_disown(fb->tmp_stream, pool), fb->text_digest, NULL, pool, &hb->apply_handler, &hb->apply_baton); *handler_baton = hb; *handler = window_handler; return SVN_NO_ERROR; } static svn_error_t * change_file_prop(void *file_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { struct file_baton *fb = file_baton; if (! value) return SVN_NO_ERROR; /* Store only the magic three properties. */ if (strcmp(name, SVN_PROP_EOL_STYLE) == 0) fb->eol_style_val = svn_string_dup(value, fb->pool); else if (strcmp(name, SVN_PROP_KEYWORDS) == 0) fb->keywords_val = svn_string_dup(value, fb->pool); else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0) fb->executable_val = svn_string_dup(value, fb->pool); /* Try to fill out the baton's keywords-structure too. */ else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0) fb->revision = apr_pstrdup(fb->pool, value->data); else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0) SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool)); else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0) fb->author = apr_pstrdup(fb->pool, value->data); else if (strcmp(name, SVN_PROP_SPECIAL) == 0) fb->special = TRUE; return SVN_NO_ERROR; } static svn_error_t * change_dir_prop(void *dir_baton, const char *name, const svn_string_t *value, apr_pool_t *pool) { struct dir_baton *db = dir_baton; struct edit_baton *eb = db->edit_baton; if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0)) add_externals(eb->externals, db->path, value); return SVN_NO_ERROR; } /* Move the tmpfile to file, and send feedback. */ static svn_error_t * close_file(void *file_baton, const char *text_checksum, apr_pool_t *pool) { struct file_baton *fb = file_baton; struct edit_baton *eb = fb->edit_baton; /* Was a txdelta even sent? */ if (! fb->tmppath) return SVN_NO_ERROR; SVN_ERR(svn_stream_close(fb->tmp_stream)); if (text_checksum) { const char *actual_checksum = svn_checksum_to_cstring(svn_checksum__from_digest(fb->text_digest, svn_checksum_md5, pool), pool); if (actual_checksum && (strcmp(text_checksum, actual_checksum) != 0)) { return svn_error_createf(SVN_ERR_CHECKSUM_MISMATCH, NULL, _("Checksum mismatch for '%s':\n" " expected: %s\n" " actual: %s\n"), svn_dirent_local_style(fb->path, pool), text_checksum, actual_checksum); } } if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special)) { SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool)); } else { svn_subst_eol_style_t style; const char *eol = NULL; svn_boolean_t repair = FALSE; apr_hash_t *final_kw = NULL; if (fb->eol_style_val) { SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data, eb->native_eol)); repair = TRUE; } if (fb->keywords_val) SVN_ERR(svn_subst_build_keywords2(&final_kw, fb->keywords_val->data, fb->revision, fb->url, fb->date, fb->author, pool)); SVN_ERR(svn_subst_copy_and_translate3 (fb->tmppath, fb->path, eol, repair, final_kw, TRUE, /* expand */ fb->special, pool)); SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool)); } if (fb->executable_val) SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool)); if (fb->date && (! fb->special)) SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool)); if (fb->edit_baton->notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(fb->path, svn_wc_notify_update_add, pool); notify->kind = svn_node_file; (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify, pool); } return SVN_NO_ERROR; } /*** Public Interfaces ***/ svn_error_t * svn_client_export4(svn_revnum_t *result_rev, const char *from, const char *to, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_boolean_t overwrite, svn_boolean_t ignore_externals, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_revnum_t edit_revision = SVN_INVALID_REVNUM; const char *url; SVN_ERR_ASSERT(peg_revision != NULL); SVN_ERR_ASSERT(revision != NULL); peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, from); revision = svn_cl__rev_default_to_peg(revision, peg_revision); if (svn_path_is_url(from) || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)) { svn_revnum_t revnum; svn_ra_session_t *ra_session; svn_node_kind_t kind; struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb)); const char *repos_root_url; /* Get the RA connection. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &url, from, NULL, peg_revision, revision, ctx, pool)); /* Get the repository root. */ SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool)); eb->root_path = to; eb->root_url = url; eb->force = overwrite; eb->target_revision = &edit_revision; eb->notify_func = ctx->notify_func2; eb->notify_baton = ctx->notify_baton2; eb->externals = apr_hash_make(pool); eb->native_eol = native_eol; SVN_ERR(svn_ra_check_path(ra_session, "", revnum, &kind, pool)); if (kind == svn_node_file) { apr_hash_t *props; apr_hash_index_t *hi; struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb)); /* Since you cannot actually root an editor at a file, we * manually drive a few functions of our editor. */ /* This is the equivalent of a parentless add_file(). */ fb->edit_baton = eb; fb->path = eb->root_path; fb->url = eb->root_url; fb->pool = pool; /* Copied from apply_textdelta(). */ SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath, svn_dirent_dirname(fb->path, pool), svn_io_file_del_none, fb->pool, fb->pool)); /* Step outside the editor-likeness for a moment, to actually talk * to the repository. */ /* ### note: the stream will not be closed */ SVN_ERR(svn_ra_get_file(ra_session, "", revnum, fb->tmp_stream, NULL, &props, pool)); /* Push the props into change_file_prop(), to update the file_baton * with information. */ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi)) { const void *key; void *val; apr_hash_this(hi, &key, NULL, &val); SVN_ERR(change_file_prop(fb, key, val, pool)); } /* And now just use close_file() to do all the keyword and EOL * work, and put the file into place. */ SVN_ERR(close_file(fb, NULL, pool)); } else if (kind == svn_node_dir) { void *edit_baton; const svn_delta_editor_t *export_editor; const svn_ra_reporter3_t *reporter; void *report_baton; svn_delta_editor_t *editor = svn_delta_default_editor(pool); svn_boolean_t use_sleep = FALSE; editor->set_target_revision = set_target_revision; editor->open_root = open_root; editor->add_directory = add_directory; editor->add_file = add_file; editor->apply_textdelta = apply_textdelta; editor->close_file = close_file; editor->change_file_prop = change_file_prop; editor->change_dir_prop = change_dir_prop; SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func, ctx->cancel_baton, editor, eb, &export_editor, &edit_baton, pool)); /* Manufacture a basic 'report' to the update reporter. */ SVN_ERR(svn_ra_do_update2(ra_session, &reporter, &report_baton, revnum, "", /* no sub-target */ depth, FALSE, /* don't want copyfrom-args */ export_editor, edit_baton, pool)); SVN_ERR(reporter->set_path(report_baton, "", revnum, /* Depth is irrelevant, as we're passing start_empty=TRUE anyway. */ svn_depth_infinity, TRUE, /* "help, my dir is empty!" */ NULL, pool)); SVN_ERR(reporter->finish_report(report_baton, pool)); /* Special case: Due to our sly export/checkout method of * updating an empty directory, no target will have been created * if the exported item is itself an empty directory * (export_editor->open_root never gets called, because there * are no "changes" to make to the empty dir we reported to the * repository). * * So we just create the empty dir manually; but we do it via * open_root_internal(), in order to get proper notification. */ SVN_ERR(svn_io_check_path(to, &kind, pool)); if (kind == svn_node_none) SVN_ERR(open_root_internal (to, overwrite, ctx->notify_func2, ctx->notify_baton2, pool)); if (! ignore_externals && depth == svn_depth_infinity) SVN_ERR(svn_client__fetch_externals(eb->externals, from, to, repos_root_url, depth, TRUE, &use_sleep, ctx, pool)); } else if (kind == svn_node_none) { return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' doesn't exist"), from); } /* kind == svn_node_unknown not handled */ } else { /* This is a working copy export. */ svn_wc_context_t *wc_ctx; if (!ctx->wc_ctx) SVN_ERR(svn_wc_context_create(&wc_ctx, NULL /* config */, pool, pool)); else wc_ctx = ctx->wc_ctx; /* just copy the contents of the working copy into the target path. */ SVN_ERR(copy_versioned_files(from, to, revision, overwrite, ignore_externals, depth, native_eol, ctx, wc_ctx, pool)); if (!ctx->wc_ctx) SVN_ERR(svn_wc_context_destroy(wc_ctx)); } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(to, svn_wc_notify_update_completed, pool); notify->revision = edit_revision; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } if (result_rev) *result_rev = edit_revision; return SVN_NO_ERROR; }