2 * export.c: export a tree.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
24 /* ==================================================================== */
30 #include <apr_file_io.h>
32 #include "svn_types.h"
33 #include "svn_client.h"
34 #include "svn_string.h"
35 #include "svn_error.h"
36 #include "svn_dirent_uri.h"
39 #include "svn_pools.h"
40 #include "svn_subst.h"
42 #include "svn_props.h"
45 #include "svn_private_config.h"
46 #include "private/svn_subr_private.h"
47 #include "private/svn_delta_private.h"
48 #include "private/svn_wc_private.h"
50 #ifndef ENABLE_EV2_IMPL
51 #define ENABLE_EV2_IMPL 0
57 /* Add EXTERNALS_PROP_VAL for the export destination path PATH to
60 add_externals(apr_hash_t *externals,
62 const svn_string_t *externals_prop_val)
64 apr_pool_t *pool = apr_hash_pool_get(externals);
65 const char *local_abspath;
67 if (! externals_prop_val)
70 SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
72 svn_hash_sets(externals, local_abspath,
73 apr_pstrmemdup(pool, externals_prop_val->data,
74 externals_prop_val->len));
79 /* Helper function that gets the eol style and optionally overrides the
80 EOL marker for files marked as native with the EOL marker matching
81 the string specified in requested_value which is of the same format
82 as the svn:eol-style property values. */
84 get_eol_style(svn_subst_eol_style_t *style,
87 const char *requested_value)
89 svn_subst_eol_style_from_value(style, eol, value);
90 if (requested_value && *style == svn_subst_eol_style_native)
92 svn_subst_eol_style_t requested_style;
93 const char *requested_eol;
95 svn_subst_eol_style_from_value(&requested_style, &requested_eol,
98 if (requested_style == svn_subst_eol_style_fixed)
101 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
102 _("'%s' is not a valid EOL value"),
108 /* If *APPENDABLE_DIRENT_P represents an existing directory, then append
109 * to it the basename of BASENAME_OF and return the result in
110 * *APPENDABLE_DIRENT_P. The kind of BASENAME_OF is either dirent or uri,
111 * as given by IS_URI.
114 append_basename_if_dir(const char **appendable_dirent_p,
115 const char *basename_of,
116 svn_boolean_t is_uri,
119 svn_node_kind_t local_kind;
120 SVN_ERR(svn_io_check_resolved_path(*appendable_dirent_p, &local_kind, pool));
121 if (local_kind == svn_node_dir)
123 const char *base_name;
126 base_name = svn_uri_basename(basename_of, pool);
128 base_name = svn_dirent_basename(basename_of, NULL);
130 *appendable_dirent_p = svn_dirent_join(*appendable_dirent_p,
137 /* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it
138 * to the destination path TO_ABSPATH.
140 * If REVISION is svn_opt_revision_working, copy the working version,
141 * otherwise copy the base version.
143 * Expand the file's keywords according to the source file's 'svn:keywords'
144 * property, if present. If copying a locally modified working version,
145 * append 'M' to the revision number and use '(local)' for the author.
147 * Translate the file's line endings according to the source file's
148 * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it
149 * in place of the native EOL style. Throw an error if the source file has
150 * inconsistent line endings and EOL translation is attempted.
152 * Set the destination file's modification time to the source file's
153 * modification time if copying the working version and the working version
154 * is locally modified; otherwise set it to the versioned file's last
157 * Set the destination file's 'executable' flag according to the source
158 * file's 'svn:executable' property.
161 /* baton for export_node */
162 struct export_info_baton
165 const svn_opt_revision_t *revision;
166 svn_boolean_t ignore_keywords;
167 svn_boolean_t overwrite;
168 svn_wc_context_t *wc_ctx;
169 const char *native_eol;
170 svn_wc_notify_func2_t notify_func;
172 const char *origin_abspath;
173 svn_boolean_t exported;
176 /* Export a file or directory. Implements svn_wc_status_func4_t */
178 export_node(void *baton,
179 const char *local_abspath,
180 const svn_wc_status3_t *status,
181 apr_pool_t *scratch_pool)
183 struct export_info_baton *eib = baton;
184 svn_wc_context_t *wc_ctx = eib->wc_ctx;
185 apr_hash_t *kw = NULL;
186 svn_subst_eol_style_t style;
188 svn_string_t *eol_style, *keywords, *executable, *special;
189 const char *eol = NULL;
190 svn_boolean_t local_mod = FALSE;
192 svn_stream_t *source;
193 svn_stream_t *dst_stream;
197 const char *to_abspath = svn_dirent_join(
199 svn_dirent_skip_ancestor(eib->origin_abspath,
203 eib->exported = TRUE;
205 /* Don't export 'deleted' files and directories unless it's a
206 revision other than WORKING. These files and directories
207 don't really exist in WORKING. */
208 if (eib->revision->kind == svn_opt_revision_working
209 && status->node_status == svn_wc_status_deleted)
212 if (status->kind == svn_node_dir)
214 apr_fileperms_t perm = APR_OS_DEFAULT;
216 /* Try to make the new directory. If this fails because the
217 directory already exists, check our FORCE flag to see if we
220 /* Keep the source directory's permissions if applicable.
221 Skip retrieving the umask on windows. Apr does not implement setting
222 filesystem privileges on Windows.
223 Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER
224 is documented to be 'incredibly expensive' */
226 if (eib->revision->kind == svn_opt_revision_working)
229 SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_PROT,
231 perm = finfo.protection;
234 err = svn_io_dir_make(to_abspath, perm, scratch_pool);
237 if (! APR_STATUS_IS_EEXIST(err->apr_err))
238 return svn_error_trace(err);
239 if (! eib->overwrite)
240 SVN_ERR_W(err, _("Destination directory exists, and will not be "
241 "overwritten unless forced"));
243 svn_error_clear(err);
247 && (strcmp(eib->origin_abspath, local_abspath) != 0))
249 svn_wc_notify_t *notify =
250 svn_wc_create_notify(to_abspath,
251 svn_wc_notify_update_add, scratch_pool);
253 notify->kind = svn_node_dir;
254 (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
259 else if (status->kind != svn_node_file)
261 if (strcmp(eib->origin_abspath, local_abspath) != 0)
264 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
265 _("The node '%s' was not found."),
266 svn_dirent_local_style(local_abspath,
270 if (status->file_external)
273 /* Produce overwrite errors for the export root */
274 if (strcmp(local_abspath, eib->origin_abspath) == 0)
276 svn_node_kind_t to_kind;
278 SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
280 if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
282 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
283 _("Destination file '%s' exists, and "
284 "will not be overwritten unless forced"),
285 svn_dirent_local_style(to_abspath,
287 else if (to_kind == svn_node_dir)
288 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
289 _("Destination '%s' exists. Cannot "
290 "overwrite directory with non-directory"),
291 svn_dirent_local_style(to_abspath,
295 if (eib->revision->kind != svn_opt_revision_working)
297 /* Only export 'added' files when the revision is WORKING. This is not
298 WORKING, so skip the 'added' files, since they didn't exist
299 in the BASE revision and don't have an associated text-base.
301 'replaced' files are technically the same as 'added' files.
302 ### TODO: Handle replaced nodes properly.
303 ### svn_opt_revision_base refers to the "new"
304 ### base of the node. That means, if a node is locally
305 ### replaced, export skips this node, as if it was locally
306 ### added, because svn_opt_revision_base refers to the base
307 ### of the added node, not to the node that was deleted.
308 ### In contrast, when the node is copied-here or moved-here,
309 ### the copy/move source's content will be exported.
310 ### It is currently not possible to export the revert-base
311 ### when a node is locally replaced. We need a new
312 ### svn_opt_revision_ enum value for proper distinction
313 ### between revert-base and commit-base.
315 Copied-/moved-here nodes have a base, so export both added and
316 replaced files when they involve a copy-/move-here.
318 We get all this for free from evaluating SOURCE == NULL:
320 SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
321 scratch_pool, scratch_pool));
325 SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
326 scratch_pool, scratch_pool));
330 /* ### hmm. this isn't always a specialfile. this will simply open
331 ### the file readonly if it is a regular file. */
332 SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool,
335 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
337 if (status->node_status != svn_wc_status_normal)
341 /* We can early-exit if we're creating a special file. */
342 special = svn_hash_gets(props, SVN_PROP_SPECIAL);
345 /* Create the destination as a special file, and copy the source
346 details into the destination stream. */
347 /* ### And forget the notification */
348 SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath,
349 scratch_pool, scratch_pool));
350 return svn_error_trace(
351 svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool));
355 eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
356 keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
357 executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE);
360 SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
364 /* Use the modified time from the working copy of
366 SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
370 tm = status->changed_date;
375 svn_revnum_t changed_rev = status->changed_rev;
377 const char *url = svn_path_url_add_component2(status->repos_root_url,
378 status->repos_relpath,
380 const char *author = status->changed_author;
383 /* For locally modified files, we'll append an 'M'
384 to the revision number, and set the author to
385 "(local)" since we can't always determine the
386 current user's username */
388 author = _("(local)");
395 SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
396 apr_psprintf(scratch_pool, "%ld%s",
397 changed_rev, suffix),
398 url, status->repos_root_url, tm,
399 author, scratch_pool));
402 /* For atomicity, we translate to a tmp file and then rename the tmp file
403 over the real destination. */
404 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
405 svn_dirent_dirname(to_abspath, scratch_pool),
406 svn_io_file_del_none, scratch_pool,
409 /* If some translation is needed, then wrap the output stream (this is
410 more efficient than wrapping the input). */
411 if (eol || (kw && (apr_hash_count(kw) > 0)))
412 dst_stream = svn_subst_stream_translated(dst_stream,
416 ! eib->ignore_keywords /* expand */,
419 /* ###: use cancel func/baton in place of NULL/NULL below. */
420 err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
422 if (!err && executable)
423 err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
426 err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
429 return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
432 /* Now that dst_tmp contains the translated data, do the atomic rename. */
433 SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool));
435 if (eib->notify_func)
437 svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath,
438 svn_wc_notify_update_add, scratch_pool);
439 notify->kind = svn_node_file;
440 (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
446 /* Abstraction of open_root.
448 * Create PATH if it does not exist and is not obstructed, and invoke
449 * NOTIFY_FUNC with NOTIFY_BATON on PATH.
451 * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
453 * If PATH is a already a directory, then error with
454 * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
455 * export into PATH with no error.
458 open_root_internal(const char *path,
460 svn_wc_notify_func2_t notify_func,
464 svn_node_kind_t kind;
466 SVN_ERR(svn_io_check_path(path, &kind, pool));
467 if (kind == svn_node_none)
468 SVN_ERR(svn_io_make_dir_recursively(path, pool));
469 else if (kind == svn_node_file)
470 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
471 _("'%s' exists and is not a directory"),
472 svn_dirent_local_style(path, pool));
473 else if ((kind != svn_node_dir) || (! force))
474 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
475 _("'%s' already exists"),
476 svn_dirent_local_style(path, pool));
480 svn_wc_notify_t *notify = svn_wc_create_notify(path,
481 svn_wc_notify_update_add,
483 notify->kind = svn_node_dir;
484 (*notify_func)(notify_baton, notify, pool);
491 /* ---------------------------------------------------------------------- */
494 /*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
499 const char *repos_root_url;
500 const char *root_path;
501 const char *root_url;
503 svn_revnum_t *target_revision;
504 apr_hash_t *externals;
505 const char *native_eol;
506 svn_boolean_t ignore_keywords;
508 svn_cancel_func_t cancel_func;
510 svn_wc_notify_func2_t notify_func;
517 struct edit_baton *edit_baton;
524 struct edit_baton *edit_baton;
529 /* We need to keep this around so we can explicitly close it in close_file,
530 thus flushing its output to disk so we can copy and translate it. */
531 svn_stream_t *tmp_stream;
533 /* The MD5 digest of the file's fulltext. This is all zeros until
534 the last textdelta window handler call returns. */
535 unsigned char text_digest[APR_MD5_DIGESTSIZE];
537 /* The three svn: properties we might actually care about. */
538 const svn_string_t *eol_style_val;
539 const svn_string_t *keywords_val;
540 const svn_string_t *executable_val;
541 svn_boolean_t special;
543 /* Any keyword vals to be substituted */
544 const char *revision;
546 const char *repos_root_url;
550 /* Pool associated with this baton. */
557 svn_txdelta_window_handler_t apply_handler;
565 set_target_revision(void *edit_baton,
566 svn_revnum_t target_revision,
569 struct edit_baton *eb = edit_baton;
571 /* Stashing a target_revision in the baton */
572 *(eb->target_revision) = target_revision;
578 /* Just ensure that the main export directory exists. */
580 open_root(void *edit_baton,
581 svn_revnum_t base_revision,
585 struct edit_baton *eb = edit_baton;
586 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
588 SVN_ERR(open_root_internal(eb->root_path, eb->force,
589 eb->notify_func, eb->notify_baton, pool));
591 /* Build our dir baton. */
592 db->path = eb->root_path;
600 /* Ensure the directory exists, and send feedback. */
602 add_directory(const char *path,
604 const char *copyfrom_path,
605 svn_revnum_t copyfrom_revision,
609 struct dir_baton *pb = parent_baton;
610 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
611 struct edit_baton *eb = pb->edit_baton;
612 const char *full_path = svn_dirent_join(eb->root_path, path, pool);
613 svn_node_kind_t kind;
615 SVN_ERR(svn_io_check_path(full_path, &kind, pool));
616 if (kind == svn_node_none)
617 SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
618 else if (kind == svn_node_file)
619 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
620 _("'%s' exists and is not a directory"),
621 svn_dirent_local_style(full_path, pool));
622 else if (! (kind == svn_node_dir && eb->force))
623 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
624 _("'%s' already exists"),
625 svn_dirent_local_style(full_path, pool));
629 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
630 svn_wc_notify_update_add,
632 notify->kind = svn_node_dir;
633 (*eb->notify_func)(eb->notify_baton, notify, pool);
636 /* Build our dir baton. */
637 db->path = full_path;
645 /* Build a file baton. */
647 add_file(const char *path,
649 const char *copyfrom_path,
650 svn_revnum_t copyfrom_revision,
654 struct dir_baton *pb = parent_baton;
655 struct edit_baton *eb = pb->edit_baton;
656 struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
657 const char *full_path = svn_dirent_join(eb->root_path, path, pool);
659 /* PATH is not canonicalized, i.e. it may still contain spaces etc.
660 * but EB->root_url is. */
661 const char *full_url = svn_path_url_add_component2(eb->root_url,
666 fb->path = full_path;
668 fb->repos_root_url = eb->repos_root_url;
677 window_handler(svn_txdelta_window_t *window, void *baton)
679 struct handler_baton *hb = baton;
682 err = hb->apply_handler(window, hb->apply_baton);
685 /* We failed to apply the patch; clean up the temporary file. */
686 err = svn_error_compose_create(
688 svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
691 return svn_error_trace(err);
696 /* Write incoming data into the tmpfile stream */
698 apply_textdelta(void *file_baton,
699 const char *base_checksum,
701 svn_txdelta_window_handler_t *handler,
702 void **handler_baton)
704 struct file_baton *fb = file_baton;
705 struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
707 /* Create a temporary file in the same directory as the file. We're going
708 to rename the thing into place when we're done. */
709 SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
710 svn_dirent_dirname(fb->path, pool),
711 svn_io_file_del_none, fb->pool, fb->pool));
714 hb->tmppath = fb->tmppath;
716 /* svn_txdelta_apply() closes the stream, but we want to close it in the
717 close_file() function, so disown it here. */
718 /* ### contrast to when we call svn_ra_get_file() which does NOT close the
719 ### tmp_stream. we *should* be much more consistent! */
720 svn_txdelta_apply(svn_stream_empty(pool),
721 svn_stream_disown(fb->tmp_stream, pool),
722 fb->text_digest, NULL, pool,
723 &hb->apply_handler, &hb->apply_baton);
726 *handler = window_handler;
732 change_file_prop(void *file_baton,
734 const svn_string_t *value,
737 struct file_baton *fb = file_baton;
742 /* Store only the magic three properties. */
743 if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
744 fb->eol_style_val = svn_string_dup(value, fb->pool);
746 else if (! fb->edit_baton->ignore_keywords &&
747 strcmp(name, SVN_PROP_KEYWORDS) == 0)
748 fb->keywords_val = svn_string_dup(value, fb->pool);
750 else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
751 fb->executable_val = svn_string_dup(value, fb->pool);
753 /* Try to fill out the baton's keywords-structure too. */
754 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
755 fb->revision = apr_pstrdup(fb->pool, value->data);
757 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
758 SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
760 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
761 fb->author = apr_pstrdup(fb->pool, value->data);
763 else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
771 change_dir_prop(void *dir_baton,
773 const svn_string_t *value,
776 struct dir_baton *db = dir_baton;
777 struct edit_baton *eb = db->edit_baton;
779 if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
780 SVN_ERR(add_externals(eb->externals, db->path, value));
786 /* Move the tmpfile to file, and send feedback. */
788 close_file(void *file_baton,
789 const char *text_digest,
792 struct file_baton *fb = file_baton;
793 struct edit_baton *eb = fb->edit_baton;
794 svn_checksum_t *text_checksum;
795 svn_checksum_t *actual_checksum;
797 /* Was a txdelta even sent? */
801 SVN_ERR(svn_stream_close(fb->tmp_stream));
803 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
805 actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
807 /* Note that text_digest can be NULL when talking to certain repositories.
808 In that case text_checksum will be NULL and the following match code
809 will note that the checksums match */
810 if (!svn_checksum_match(text_checksum, actual_checksum))
811 return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool,
812 _("Checksum mismatch for '%s'"),
813 svn_dirent_local_style(fb->path, pool));
815 if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
817 SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
821 svn_subst_eol_style_t style;
822 const char *eol = NULL;
823 svn_boolean_t repair = FALSE;
824 apr_hash_t *final_kw = NULL;
826 if (fb->eol_style_val)
828 SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
833 if (fb->keywords_val)
834 SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
835 fb->revision, fb->url,
836 fb->repos_root_url, fb->date,
839 SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
840 eol, repair, final_kw,
843 eb->cancel_func, eb->cancel_baton,
846 SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
849 if (fb->executable_val)
850 SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
852 if (fb->date && (! fb->special))
853 SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
855 if (fb->edit_baton->notify_func)
857 svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
858 svn_wc_notify_update_add,
860 notify->kind = svn_node_file;
861 (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
869 fetch_props_func(apr_hash_t **props,
872 svn_revnum_t base_revision,
873 apr_pool_t *result_pool,
874 apr_pool_t *scratch_pool)
876 /* Always use empty props, since the node won't have pre-existing props
877 (This is an export, remember?) */
878 *props = apr_hash_make(result_pool);
884 fetch_base_func(const char **filename,
887 svn_revnum_t base_revision,
888 apr_pool_t *result_pool,
889 apr_pool_t *scratch_pool)
891 /* An export always gets text against the empty stream (i.e, full texts). */
898 get_editor_ev1(const svn_delta_editor_t **export_editor,
900 struct edit_baton *eb,
901 svn_client_ctx_t *ctx,
902 apr_pool_t *result_pool,
903 apr_pool_t *scratch_pool)
905 svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
907 editor->set_target_revision = set_target_revision;
908 editor->open_root = open_root;
909 editor->add_directory = add_directory;
910 editor->add_file = add_file;
911 editor->apply_textdelta = apply_textdelta;
912 editor->close_file = close_file;
913 editor->change_file_prop = change_file_prop;
914 editor->change_dir_prop = change_dir_prop;
916 SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
928 /*** The Ev2 Implementation ***/
931 add_file_ev2(void *baton,
933 const svn_checksum_t *checksum,
934 svn_stream_t *contents,
936 svn_revnum_t replaces_rev,
937 apr_pool_t *scratch_pool)
939 struct edit_baton *eb = baton;
940 const char *full_path = svn_dirent_join(eb->root_path, relpath,
942 /* RELPATH is not canonicalized, i.e. it may still contain spaces etc.
943 * but EB->root_url is. */
944 const char *full_url = svn_path_url_add_component2(eb->root_url,
947 const svn_string_t *val;
948 /* The four svn: properties we might actually care about. */
949 const svn_string_t *eol_style_val = NULL;
950 const svn_string_t *keywords_val = NULL;
951 const svn_string_t *executable_val = NULL;
952 svn_boolean_t special = FALSE;
953 /* Any keyword vals to be substituted */
954 const char *revision = NULL;
955 const char *author = NULL;
958 /* Look at any properties for additional information. */
959 if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
962 if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
965 if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
966 executable_val = val;
968 /* Try to fill out the baton's keywords-structure too. */
969 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
970 revision = val->data;
972 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
973 SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
975 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
978 if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
983 svn_stream_t *tmp_stream;
985 SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path,
986 scratch_pool, scratch_pool));
987 SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
988 eb->cancel_baton, scratch_pool));
992 svn_stream_t *tmp_stream;
995 /* Create a temporary file in the same directory as the file. We're going
996 to rename the thing into place when we're done. */
997 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath,
998 svn_dirent_dirname(full_path,
1000 svn_io_file_del_none,
1001 scratch_pool, scratch_pool));
1003 /* Possibly wrap the stream to be translated, as dictated by
1005 if (eol_style_val || keywords_val)
1007 svn_subst_eol_style_t style;
1008 const char *eol = NULL;
1009 svn_boolean_t repair = FALSE;
1010 apr_hash_t *final_kw = NULL;
1014 SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
1020 SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
1023 date, author, scratch_pool));
1025 /* Writing through a translated stream is more efficient than
1026 reading through one, so we wrap TMP_STREAM and not CONTENTS. */
1027 tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair,
1028 final_kw, TRUE, /* expand */
1032 SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
1033 eb->cancel_baton, scratch_pool));
1035 /* Move the file into place. */
1036 SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
1040 SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
1042 if (date && (! special))
1043 SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
1045 if (eb->notify_func)
1047 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1048 svn_wc_notify_update_add,
1050 notify->kind = svn_node_file;
1051 (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1054 return SVN_NO_ERROR;
1057 static svn_error_t *
1058 add_directory_ev2(void *baton,
1059 const char *relpath,
1060 const apr_array_header_t *children,
1062 svn_revnum_t replaces_rev,
1063 apr_pool_t *scratch_pool)
1065 struct edit_baton *eb = baton;
1066 svn_node_kind_t kind;
1067 const char *full_path = svn_dirent_join(eb->root_path, relpath,
1071 SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool));
1072 if (kind == svn_node_none)
1073 SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool));
1074 else if (kind == svn_node_file)
1075 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1076 _("'%s' exists and is not a directory"),
1077 svn_dirent_local_style(full_path, scratch_pool));
1078 else if (! (kind == svn_node_dir && eb->force))
1079 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1080 _("'%s' already exists"),
1081 svn_dirent_local_style(full_path, scratch_pool));
1083 if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
1084 SVN_ERR(add_externals(eb->externals, full_path, val));
1086 if (eb->notify_func)
1088 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1089 svn_wc_notify_update_add,
1091 notify->kind = svn_node_dir;
1092 (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1095 return SVN_NO_ERROR;
1098 static svn_error_t *
1099 target_revision_func(void *baton,
1100 svn_revnum_t target_revision,
1101 apr_pool_t *scratch_pool)
1103 struct edit_baton *eb = baton;
1105 *eb->target_revision = target_revision;
1107 return SVN_NO_ERROR;
1110 static svn_error_t *
1111 get_editor_ev2(const svn_delta_editor_t **export_editor,
1113 struct edit_baton *eb,
1114 svn_client_ctx_t *ctx,
1115 apr_pool_t *result_pool,
1116 apr_pool_t *scratch_pool)
1118 svn_editor_t *editor;
1119 struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb));
1120 svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
1121 sizeof(*found_abs_paths));
1124 exb->target_revision = target_revision_func;
1126 SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton,
1127 result_pool, scratch_pool));
1128 SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2,
1130 SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
1132 *found_abs_paths = TRUE;
1134 SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
1135 editor, NULL, NULL, found_abs_paths,
1137 fetch_props_func, eb,
1138 fetch_base_func, eb,
1141 /* Create the root of the export. */
1142 SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
1143 eb->notify_baton, scratch_pool));
1145 return SVN_NO_ERROR;
1148 static svn_error_t *
1149 export_file_ev2(const char *from_path_or_url,
1150 const char *to_path,
1151 struct edit_baton *eb,
1152 svn_client__pathrev_t *loc,
1153 svn_ra_session_t *ra_session,
1154 svn_boolean_t overwrite,
1155 apr_pool_t *scratch_pool)
1157 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1159 svn_stream_t *tmp_stream;
1160 svn_node_kind_t to_kind;
1162 if (svn_path_is_empty(to_path))
1165 to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1167 to_path = svn_dirent_basename(from_path_or_url, NULL);
1168 eb->root_path = to_path;
1172 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1173 from_is_url, scratch_pool));
1174 eb->root_path = to_path;
1177 SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1179 if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1181 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1182 _("Destination file '%s' exists, and "
1183 "will not be overwritten unless forced"),
1184 svn_dirent_local_style(to_path, scratch_pool));
1185 else if (to_kind == svn_node_dir)
1186 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1187 _("Destination '%s' exists. Cannot "
1188 "overwrite directory with non-directory"),
1189 svn_dirent_local_style(to_path, scratch_pool));
1191 tmp_stream = svn_stream_buffered(scratch_pool);
1193 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1194 tmp_stream, NULL, &props, scratch_pool));
1196 /* Since you cannot actually root an editor at a file, we manually drive
1197 * a function of our editor. */
1198 SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
1201 return SVN_NO_ERROR;
1204 static svn_error_t *
1205 export_file(const char *from_path_or_url,
1206 const char *to_path,
1207 struct edit_baton *eb,
1208 svn_client__pathrev_t *loc,
1209 svn_ra_session_t *ra_session,
1210 svn_boolean_t overwrite,
1211 apr_pool_t *scratch_pool)
1214 apr_hash_index_t *hi;
1215 struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
1216 svn_node_kind_t to_kind;
1217 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1219 if (svn_path_is_empty(to_path))
1222 to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1224 to_path = svn_dirent_basename(from_path_or_url, NULL);
1225 eb->root_path = to_path;
1229 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1230 from_is_url, scratch_pool));
1231 eb->root_path = to_path;
1234 SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1236 if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1238 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1239 _("Destination file '%s' exists, and "
1240 "will not be overwritten unless forced"),
1241 svn_dirent_local_style(to_path, scratch_pool));
1242 else if (to_kind == svn_node_dir)
1243 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1244 _("Destination '%s' exists. Cannot "
1245 "overwrite directory with non-directory"),
1246 svn_dirent_local_style(to_path, scratch_pool));
1248 /* Since you cannot actually root an editor at a file, we
1249 * manually drive a few functions of our editor. */
1251 /* This is the equivalent of a parentless add_file(). */
1252 fb->edit_baton = eb;
1253 fb->path = eb->root_path;
1254 fb->url = eb->root_url;
1255 fb->pool = scratch_pool;
1256 fb->repos_root_url = eb->repos_root_url;
1258 /* Copied from apply_textdelta(). */
1259 SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
1260 svn_dirent_dirname(fb->path, scratch_pool),
1261 svn_io_file_del_none,
1262 fb->pool, fb->pool));
1264 /* Step outside the editor-likeness for a moment, to actually talk
1265 * to the repository. */
1266 /* ### note: the stream will not be closed */
1267 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1269 NULL, &props, scratch_pool));
1271 /* Push the props into change_file_prop(), to update the file_baton
1272 * with information. */
1273 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
1275 const char *propname = svn__apr_hash_index_key(hi);
1276 const svn_string_t *propval = svn__apr_hash_index_val(hi);
1278 SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1281 /* And now just use close_file() to do all the keyword and EOL
1282 * work, and put the file into place. */
1283 SVN_ERR(close_file(fb, NULL, scratch_pool));
1285 return SVN_NO_ERROR;
1288 static svn_error_t *
1289 export_directory(const char *from_path_or_url,
1290 const char *to_path,
1291 struct edit_baton *eb,
1292 svn_client__pathrev_t *loc,
1293 svn_ra_session_t *ra_session,
1294 svn_boolean_t overwrite,
1295 svn_boolean_t ignore_externals,
1296 svn_boolean_t ignore_keywords,
1298 const char *native_eol,
1299 svn_client_ctx_t *ctx,
1300 apr_pool_t *scratch_pool)
1303 const svn_delta_editor_t *export_editor;
1304 const svn_ra_reporter3_t *reporter;
1306 svn_node_kind_t kind;
1308 if (!ENABLE_EV2_IMPL)
1309 SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1310 scratch_pool, scratch_pool));
1312 SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1313 scratch_pool, scratch_pool));
1315 /* Manufacture a basic 'report' to the update reporter. */
1316 SVN_ERR(svn_ra_do_update3(ra_session,
1317 &reporter, &report_baton,
1319 "", /* no sub-target */
1321 FALSE, /* don't want copyfrom-args */
1322 FALSE, /* don't want ignore_ancestry */
1323 export_editor, edit_baton,
1324 scratch_pool, scratch_pool));
1326 SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1327 /* Depth is irrelevant, as we're
1328 passing start_empty=TRUE anyway. */
1330 TRUE, /* "help, my dir is empty!" */
1331 NULL, scratch_pool));
1333 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1335 /* Special case: Due to our sly export/checkout method of updating an
1336 * empty directory, no target will have been created if the exported
1337 * item is itself an empty directory (export_editor->open_root never
1338 * gets called, because there are no "changes" to make to the empty
1339 * dir we reported to the repository).
1341 * So we just create the empty dir manually; but we do it via
1342 * open_root_internal(), in order to get proper notification.
1344 SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
1345 if (kind == svn_node_none)
1346 SVN_ERR(open_root_internal
1347 (to_path, overwrite, ctx->notify_func2,
1348 ctx->notify_baton2, scratch_pool));
1350 if (! ignore_externals && depth == svn_depth_infinity)
1352 const char *to_abspath;
1354 SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1355 SVN_ERR(svn_client__export_externals(eb->externals,
1357 to_abspath, eb->repos_root_url,
1360 ctx, scratch_pool));
1363 return SVN_NO_ERROR;
1368 /*** Public Interfaces ***/
1371 svn_client_export5(svn_revnum_t *result_rev,
1372 const char *from_path_or_url,
1373 const char *to_path,
1374 const svn_opt_revision_t *peg_revision,
1375 const svn_opt_revision_t *revision,
1376 svn_boolean_t overwrite,
1377 svn_boolean_t ignore_externals,
1378 svn_boolean_t ignore_keywords,
1380 const char *native_eol,
1381 svn_client_ctx_t *ctx,
1384 svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1385 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1387 SVN_ERR_ASSERT(peg_revision != NULL);
1388 SVN_ERR_ASSERT(revision != NULL);
1390 if (svn_path_is_url(to_path))
1391 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1392 _("'%s' is not a local path"), to_path);
1394 peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1396 revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1398 if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1400 svn_client__pathrev_t *loc;
1401 svn_ra_session_t *ra_session;
1402 svn_node_kind_t kind;
1403 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1405 /* Get the RA connection. */
1406 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1407 from_path_or_url, NULL,
1409 revision, ctx, pool));
1411 SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
1412 eb->root_path = to_path;
1413 eb->root_url = loc->url;
1414 eb->force = overwrite;
1415 eb->target_revision = &edit_revision;
1416 eb->externals = apr_hash_make(pool);
1417 eb->native_eol = native_eol;
1418 eb->ignore_keywords = ignore_keywords;
1419 eb->cancel_func = ctx->cancel_func;
1420 eb->cancel_baton = ctx->cancel_baton;
1421 eb->notify_func = ctx->notify_func2;
1422 eb->notify_baton = ctx->notify_baton2;
1424 SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1426 if (kind == svn_node_file)
1428 if (!ENABLE_EV2_IMPL)
1429 SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
1432 SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
1433 ra_session, overwrite, pool));
1435 else if (kind == svn_node_dir)
1437 SVN_ERR(export_directory(from_path_or_url, to_path,
1438 eb, loc, ra_session, overwrite,
1439 ignore_externals, ignore_keywords, depth,
1440 native_eol, ctx, pool));
1442 else if (kind == svn_node_none)
1444 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1445 _("URL '%s' doesn't exist"),
1448 /* kind == svn_node_unknown not handled */
1452 struct export_info_baton eib;
1453 svn_node_kind_t kind;
1454 apr_hash_t *externals = NULL;
1456 /* This is a working copy export. */
1457 /* just copy the contents of the working copy into the target path. */
1458 SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
1461 SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1463 SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1465 /* ### [JAF] If something already exists on disk at the destination path,
1466 * the behaviour depends on the node kinds of the source and destination
1467 * and on the FORCE flag. The intention (I guess) is to follow the
1468 * semantics of svn_client_export5(), semantics that are not fully
1469 * documented but would be something like:
1471 * -----------+---------------------------------------------------------
1472 * Src | DIR FILE SPECIAL
1473 * Dst (disk) +---------------------------------------------------------
1474 * NONE | simple copy simple copy (as src=file?)
1475 * DIR | merge if forced [2] inside if root [1] (as src=file?)
1476 * FILE | err overwr if forced[3] (as src=file?)
1477 * SPECIAL | ??? ??? ???
1478 * -----------+---------------------------------------------------------
1480 * [1] FILE onto DIR case: If this file is the root of the copy and thus
1481 * the only node to be copied, then copy it as a child of the
1482 * directory TO, applying these same rules again except that if this
1483 * case occurs again (the child path is already a directory) then
1484 * error out. If this file is not the root of the copy (it is
1485 * reached by recursion), then error out.
1487 * [2] DIR onto DIR case. If the 'FORCE' flag is true then copy the
1488 * source's children inside the target dir, else error out. When
1489 * copying the children, apply the same set of rules, except in the
1490 * FILE onto DIR case error out like in note [1].
1492 * [3] If the 'FORCE' flag is true then overwrite the destination file
1495 * The reality (apparently, looking at the code) is somewhat different.
1496 * For a start, to detect the source kind, it looks at what is on disk
1497 * rather than the versioned working or base node.
1499 if (kind == svn_node_file)
1500 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1503 eib.to_path = to_path;
1504 eib.revision = revision;
1505 eib.overwrite = overwrite;
1506 eib.ignore_keywords = ignore_keywords;
1507 eib.wc_ctx = ctx->wc_ctx;
1508 eib.native_eol = native_eol;
1509 eib.notify_func = ctx->notify_func2;;
1510 eib.notify_baton = ctx->notify_baton2;
1511 eib.origin_abspath = from_path_or_url;
1512 eib.exported = FALSE;
1514 SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1516 TRUE /* no_ignore */,
1517 FALSE /* ignore_text_mods */,
1520 ctx->cancel_func, ctx->cancel_baton,
1524 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1525 _("The node '%s' was not found."),
1526 svn_dirent_local_style(from_path_or_url,
1529 if (!ignore_externals)
1530 SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1534 if (externals && apr_hash_count(externals))
1536 apr_pool_t *iterpool = svn_pool_create(pool);
1537 apr_hash_index_t *hi;
1539 for (hi = apr_hash_first(pool, externals);
1541 hi = apr_hash_next(hi))
1543 const char *external_abspath = svn__apr_hash_index_key(hi);
1544 const char *relpath;
1545 const char *target_abspath;
1547 svn_pool_clear(iterpool);
1549 relpath = svn_dirent_skip_ancestor(from_path_or_url,
1552 target_abspath = svn_dirent_join(to_path, relpath,
1555 /* Ensure that the parent directory exists */
1556 SVN_ERR(svn_io_make_dir_recursively(
1557 svn_dirent_dirname(target_abspath, iterpool),
1560 SVN_ERR(svn_client_export5(NULL,
1561 svn_dirent_join(from_path_or_url,
1565 peg_revision, revision,
1566 TRUE, ignore_externals,
1567 ignore_keywords, depth, native_eol,
1571 svn_pool_destroy(iterpool);
1576 if (ctx->notify_func2)
1578 svn_wc_notify_t *notify
1579 = svn_wc_create_notify(to_path,
1580 svn_wc_notify_update_completed, pool);
1581 notify->revision = edit_revision;
1582 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1586 *result_rev = edit_revision;
1588 return SVN_NO_ERROR;