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 /* Skip file externals if they are a descendant of the export,
271 BUT NOT if we are explictly exporting the file external. */
272 if (status->file_external && strcmp(eib->origin_abspath, local_abspath) != 0)
275 /* Produce overwrite errors for the export root */
276 if (strcmp(local_abspath, eib->origin_abspath) == 0)
278 svn_node_kind_t to_kind;
280 SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
282 if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
284 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
285 _("Destination file '%s' exists, and "
286 "will not be overwritten unless forced"),
287 svn_dirent_local_style(to_abspath,
289 else if (to_kind == svn_node_dir)
290 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
291 _("Destination '%s' exists. Cannot "
292 "overwrite directory with non-directory"),
293 svn_dirent_local_style(to_abspath,
297 if (eib->revision->kind != svn_opt_revision_working)
299 /* Only export 'added' files when the revision is WORKING. This is not
300 WORKING, so skip the 'added' files, since they didn't exist
301 in the BASE revision and don't have an associated text-base.
303 'replaced' files are technically the same as 'added' files.
304 ### TODO: Handle replaced nodes properly.
305 ### svn_opt_revision_base refers to the "new"
306 ### base of the node. That means, if a node is locally
307 ### replaced, export skips this node, as if it was locally
308 ### added, because svn_opt_revision_base refers to the base
309 ### of the added node, not to the node that was deleted.
310 ### In contrast, when the node is copied-here or moved-here,
311 ### the copy/move source's content will be exported.
312 ### It is currently not possible to export the revert-base
313 ### when a node is locally replaced. We need a new
314 ### svn_opt_revision_ enum value for proper distinction
315 ### between revert-base and commit-base.
317 Copied-/moved-here nodes have a base, so export both added and
318 replaced files when they involve a copy-/move-here.
320 We get all this for free from evaluating SOURCE == NULL:
322 SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
323 scratch_pool, scratch_pool));
327 SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
328 scratch_pool, scratch_pool));
332 /* ### hmm. this isn't always a specialfile. this will simply open
333 ### the file readonly if it is a regular file. */
334 SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool,
337 SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
339 if (status->node_status != svn_wc_status_normal)
343 /* We can early-exit if we're creating a special file. */
344 special = svn_hash_gets(props, SVN_PROP_SPECIAL);
347 /* Create the destination as a special file, and copy the source
348 details into the destination stream. */
349 /* ### And forget the notification */
350 SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath,
351 scratch_pool, scratch_pool));
352 return svn_error_trace(
353 svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool));
357 eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
358 keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
359 executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE);
362 SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
366 /* Use the modified time from the working copy of
368 SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
372 tm = status->changed_date;
377 svn_revnum_t changed_rev = status->changed_rev;
379 const char *url = svn_path_url_add_component2(status->repos_root_url,
380 status->repos_relpath,
382 const char *author = status->changed_author;
385 /* For locally modified files, we'll append an 'M'
386 to the revision number, and set the author to
387 "(local)" since we can't always determine the
388 current user's username */
390 author = _("(local)");
397 SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
398 apr_psprintf(scratch_pool, "%ld%s",
399 changed_rev, suffix),
400 url, status->repos_root_url, tm,
401 author, scratch_pool));
404 /* For atomicity, we translate to a tmp file and then rename the tmp file
405 over the real destination. */
406 SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
407 svn_dirent_dirname(to_abspath, scratch_pool),
408 svn_io_file_del_none, scratch_pool,
411 /* If some translation is needed, then wrap the output stream (this is
412 more efficient than wrapping the input). */
413 if (eol || (kw && (apr_hash_count(kw) > 0)))
414 dst_stream = svn_subst_stream_translated(dst_stream,
418 ! eib->ignore_keywords /* expand */,
421 /* ###: use cancel func/baton in place of NULL/NULL below. */
422 err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
424 if (!err && executable)
425 err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
428 err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
431 return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
434 /* Now that dst_tmp contains the translated data, do the atomic rename. */
435 SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool));
437 if (eib->notify_func)
439 svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath,
440 svn_wc_notify_update_add, scratch_pool);
441 notify->kind = svn_node_file;
442 (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
448 /* Abstraction of open_root.
450 * Create PATH if it does not exist and is not obstructed, and invoke
451 * NOTIFY_FUNC with NOTIFY_BATON on PATH.
453 * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
455 * If PATH is a already a directory, then error with
456 * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
457 * export into PATH with no error.
460 open_root_internal(const char *path,
462 svn_wc_notify_func2_t notify_func,
466 svn_node_kind_t kind;
468 SVN_ERR(svn_io_check_path(path, &kind, pool));
469 if (kind == svn_node_none)
470 SVN_ERR(svn_io_make_dir_recursively(path, pool));
471 else if (kind == svn_node_file)
472 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
473 _("'%s' exists and is not a directory"),
474 svn_dirent_local_style(path, pool));
475 else if ((kind != svn_node_dir) || (! force))
476 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
477 _("'%s' already exists"),
478 svn_dirent_local_style(path, pool));
482 svn_wc_notify_t *notify = svn_wc_create_notify(path,
483 svn_wc_notify_update_add,
485 notify->kind = svn_node_dir;
486 (*notify_func)(notify_baton, notify, pool);
493 /* ---------------------------------------------------------------------- */
496 /*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
501 const char *repos_root_url;
502 const char *root_path;
503 const char *root_url;
505 svn_revnum_t *target_revision;
506 apr_hash_t *externals;
507 const char *native_eol;
508 svn_boolean_t ignore_keywords;
510 svn_cancel_func_t cancel_func;
512 svn_wc_notify_func2_t notify_func;
519 struct edit_baton *edit_baton;
526 struct edit_baton *edit_baton;
531 /* We need to keep this around so we can explicitly close it in close_file,
532 thus flushing its output to disk so we can copy and translate it. */
533 svn_stream_t *tmp_stream;
535 /* The MD5 digest of the file's fulltext. This is all zeros until
536 the last textdelta window handler call returns. */
537 unsigned char text_digest[APR_MD5_DIGESTSIZE];
539 /* The three svn: properties we might actually care about. */
540 const svn_string_t *eol_style_val;
541 const svn_string_t *keywords_val;
542 const svn_string_t *executable_val;
543 svn_boolean_t special;
545 /* Any keyword vals to be substituted */
546 const char *revision;
548 const char *repos_root_url;
552 /* Pool associated with this baton. */
559 svn_txdelta_window_handler_t apply_handler;
567 set_target_revision(void *edit_baton,
568 svn_revnum_t target_revision,
571 struct edit_baton *eb = edit_baton;
573 /* Stashing a target_revision in the baton */
574 *(eb->target_revision) = target_revision;
580 /* Just ensure that the main export directory exists. */
582 open_root(void *edit_baton,
583 svn_revnum_t base_revision,
587 struct edit_baton *eb = edit_baton;
588 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
590 SVN_ERR(open_root_internal(eb->root_path, eb->force,
591 eb->notify_func, eb->notify_baton, pool));
593 /* Build our dir baton. */
594 db->path = eb->root_path;
602 /* Ensure the directory exists, and send feedback. */
604 add_directory(const char *path,
606 const char *copyfrom_path,
607 svn_revnum_t copyfrom_revision,
611 struct dir_baton *pb = parent_baton;
612 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
613 struct edit_baton *eb = pb->edit_baton;
614 const char *full_path = svn_dirent_join(eb->root_path, path, pool);
615 svn_node_kind_t kind;
617 SVN_ERR(svn_io_check_path(full_path, &kind, pool));
618 if (kind == svn_node_none)
619 SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
620 else if (kind == svn_node_file)
621 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
622 _("'%s' exists and is not a directory"),
623 svn_dirent_local_style(full_path, pool));
624 else if (! (kind == svn_node_dir && eb->force))
625 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
626 _("'%s' already exists"),
627 svn_dirent_local_style(full_path, pool));
631 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
632 svn_wc_notify_update_add,
634 notify->kind = svn_node_dir;
635 (*eb->notify_func)(eb->notify_baton, notify, pool);
638 /* Build our dir baton. */
639 db->path = full_path;
647 /* Build a file baton. */
649 add_file(const char *path,
651 const char *copyfrom_path,
652 svn_revnum_t copyfrom_revision,
656 struct dir_baton *pb = parent_baton;
657 struct edit_baton *eb = pb->edit_baton;
658 struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
659 const char *full_path = svn_dirent_join(eb->root_path, path, pool);
661 /* PATH is not canonicalized, i.e. it may still contain spaces etc.
662 * but EB->root_url is. */
663 const char *full_url = svn_path_url_add_component2(eb->root_url,
668 fb->path = full_path;
670 fb->repos_root_url = eb->repos_root_url;
679 window_handler(svn_txdelta_window_t *window, void *baton)
681 struct handler_baton *hb = baton;
684 err = hb->apply_handler(window, hb->apply_baton);
687 /* We failed to apply the patch; clean up the temporary file. */
688 err = svn_error_compose_create(
690 svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
693 return svn_error_trace(err);
698 /* Write incoming data into the tmpfile stream */
700 apply_textdelta(void *file_baton,
701 const char *base_checksum,
703 svn_txdelta_window_handler_t *handler,
704 void **handler_baton)
706 struct file_baton *fb = file_baton;
707 struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
709 /* Create a temporary file in the same directory as the file. We're going
710 to rename the thing into place when we're done. */
711 SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
712 svn_dirent_dirname(fb->path, pool),
713 svn_io_file_del_none, fb->pool, fb->pool));
716 hb->tmppath = fb->tmppath;
718 /* svn_txdelta_apply() closes the stream, but we want to close it in the
719 close_file() function, so disown it here. */
720 /* ### contrast to when we call svn_ra_get_file() which does NOT close the
721 ### tmp_stream. we *should* be much more consistent! */
722 svn_txdelta_apply(svn_stream_empty(pool),
723 svn_stream_disown(fb->tmp_stream, pool),
724 fb->text_digest, NULL, pool,
725 &hb->apply_handler, &hb->apply_baton);
728 *handler = window_handler;
734 change_file_prop(void *file_baton,
736 const svn_string_t *value,
739 struct file_baton *fb = file_baton;
744 /* Store only the magic three properties. */
745 if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
746 fb->eol_style_val = svn_string_dup(value, fb->pool);
748 else if (! fb->edit_baton->ignore_keywords &&
749 strcmp(name, SVN_PROP_KEYWORDS) == 0)
750 fb->keywords_val = svn_string_dup(value, fb->pool);
752 else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
753 fb->executable_val = svn_string_dup(value, fb->pool);
755 /* Try to fill out the baton's keywords-structure too. */
756 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
757 fb->revision = apr_pstrdup(fb->pool, value->data);
759 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
760 SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
762 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
763 fb->author = apr_pstrdup(fb->pool, value->data);
765 else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
773 change_dir_prop(void *dir_baton,
775 const svn_string_t *value,
778 struct dir_baton *db = dir_baton;
779 struct edit_baton *eb = db->edit_baton;
781 if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
782 SVN_ERR(add_externals(eb->externals, db->path, value));
788 /* Move the tmpfile to file, and send feedback. */
790 close_file(void *file_baton,
791 const char *text_digest,
794 struct file_baton *fb = file_baton;
795 struct edit_baton *eb = fb->edit_baton;
796 svn_checksum_t *text_checksum;
797 svn_checksum_t *actual_checksum;
799 /* Was a txdelta even sent? */
803 SVN_ERR(svn_stream_close(fb->tmp_stream));
805 SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
807 actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
809 /* Note that text_digest can be NULL when talking to certain repositories.
810 In that case text_checksum will be NULL and the following match code
811 will note that the checksums match */
812 if (!svn_checksum_match(text_checksum, actual_checksum))
813 return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool,
814 _("Checksum mismatch for '%s'"),
815 svn_dirent_local_style(fb->path, pool));
817 if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
819 SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
823 svn_subst_eol_style_t style;
824 const char *eol = NULL;
825 svn_boolean_t repair = FALSE;
826 apr_hash_t *final_kw = NULL;
828 if (fb->eol_style_val)
830 SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
835 if (fb->keywords_val)
836 SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
837 fb->revision, fb->url,
838 fb->repos_root_url, fb->date,
841 SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
842 eol, repair, final_kw,
845 eb->cancel_func, eb->cancel_baton,
848 SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
851 if (fb->executable_val)
852 SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
854 if (fb->date && (! fb->special))
855 SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
857 if (fb->edit_baton->notify_func)
859 svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
860 svn_wc_notify_update_add,
862 notify->kind = svn_node_file;
863 (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
871 fetch_props_func(apr_hash_t **props,
874 svn_revnum_t base_revision,
875 apr_pool_t *result_pool,
876 apr_pool_t *scratch_pool)
878 /* Always use empty props, since the node won't have pre-existing props
879 (This is an export, remember?) */
880 *props = apr_hash_make(result_pool);
886 fetch_base_func(const char **filename,
889 svn_revnum_t base_revision,
890 apr_pool_t *result_pool,
891 apr_pool_t *scratch_pool)
893 /* An export always gets text against the empty stream (i.e, full texts). */
900 get_editor_ev1(const svn_delta_editor_t **export_editor,
902 struct edit_baton *eb,
903 svn_client_ctx_t *ctx,
904 apr_pool_t *result_pool,
905 apr_pool_t *scratch_pool)
907 svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
909 editor->set_target_revision = set_target_revision;
910 editor->open_root = open_root;
911 editor->add_directory = add_directory;
912 editor->add_file = add_file;
913 editor->apply_textdelta = apply_textdelta;
914 editor->close_file = close_file;
915 editor->change_file_prop = change_file_prop;
916 editor->change_dir_prop = change_dir_prop;
918 SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
930 /*** The Ev2 Implementation ***/
933 add_file_ev2(void *baton,
935 const svn_checksum_t *checksum,
936 svn_stream_t *contents,
938 svn_revnum_t replaces_rev,
939 apr_pool_t *scratch_pool)
941 struct edit_baton *eb = baton;
942 const char *full_path = svn_dirent_join(eb->root_path, relpath,
944 /* RELPATH is not canonicalized, i.e. it may still contain spaces etc.
945 * but EB->root_url is. */
946 const char *full_url = svn_path_url_add_component2(eb->root_url,
949 const svn_string_t *val;
950 /* The four svn: properties we might actually care about. */
951 const svn_string_t *eol_style_val = NULL;
952 const svn_string_t *keywords_val = NULL;
953 const svn_string_t *executable_val = NULL;
954 svn_boolean_t special = FALSE;
955 /* Any keyword vals to be substituted */
956 const char *revision = NULL;
957 const char *author = NULL;
960 /* Look at any properties for additional information. */
961 if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
964 if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
967 if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
968 executable_val = val;
970 /* Try to fill out the baton's keywords-structure too. */
971 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
972 revision = val->data;
974 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
975 SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
977 if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
980 if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
985 svn_stream_t *tmp_stream;
987 SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path,
988 scratch_pool, scratch_pool));
989 SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
990 eb->cancel_baton, scratch_pool));
994 svn_stream_t *tmp_stream;
997 /* Create a temporary file in the same directory as the file. We're going
998 to rename the thing into place when we're done. */
999 SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath,
1000 svn_dirent_dirname(full_path,
1002 svn_io_file_del_none,
1003 scratch_pool, scratch_pool));
1005 /* Possibly wrap the stream to be translated, as dictated by
1007 if (eol_style_val || keywords_val)
1009 svn_subst_eol_style_t style;
1010 const char *eol = NULL;
1011 svn_boolean_t repair = FALSE;
1012 apr_hash_t *final_kw = NULL;
1016 SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
1022 SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
1025 date, author, scratch_pool));
1027 /* Writing through a translated stream is more efficient than
1028 reading through one, so we wrap TMP_STREAM and not CONTENTS. */
1029 tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair,
1030 final_kw, TRUE, /* expand */
1034 SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
1035 eb->cancel_baton, scratch_pool));
1037 /* Move the file into place. */
1038 SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
1042 SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
1044 if (date && (! special))
1045 SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
1047 if (eb->notify_func)
1049 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1050 svn_wc_notify_update_add,
1052 notify->kind = svn_node_file;
1053 (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1056 return SVN_NO_ERROR;
1059 static svn_error_t *
1060 add_directory_ev2(void *baton,
1061 const char *relpath,
1062 const apr_array_header_t *children,
1064 svn_revnum_t replaces_rev,
1065 apr_pool_t *scratch_pool)
1067 struct edit_baton *eb = baton;
1068 svn_node_kind_t kind;
1069 const char *full_path = svn_dirent_join(eb->root_path, relpath,
1073 SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool));
1074 if (kind == svn_node_none)
1075 SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool));
1076 else if (kind == svn_node_file)
1077 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1078 _("'%s' exists and is not a directory"),
1079 svn_dirent_local_style(full_path, scratch_pool));
1080 else if (! (kind == svn_node_dir && eb->force))
1081 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1082 _("'%s' already exists"),
1083 svn_dirent_local_style(full_path, scratch_pool));
1085 if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
1086 SVN_ERR(add_externals(eb->externals, full_path, val));
1088 if (eb->notify_func)
1090 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1091 svn_wc_notify_update_add,
1093 notify->kind = svn_node_dir;
1094 (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1097 return SVN_NO_ERROR;
1100 static svn_error_t *
1101 target_revision_func(void *baton,
1102 svn_revnum_t target_revision,
1103 apr_pool_t *scratch_pool)
1105 struct edit_baton *eb = baton;
1107 *eb->target_revision = target_revision;
1109 return SVN_NO_ERROR;
1112 static svn_error_t *
1113 get_editor_ev2(const svn_delta_editor_t **export_editor,
1115 struct edit_baton *eb,
1116 svn_client_ctx_t *ctx,
1117 apr_pool_t *result_pool,
1118 apr_pool_t *scratch_pool)
1120 svn_editor_t *editor;
1121 struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb));
1122 svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
1123 sizeof(*found_abs_paths));
1126 exb->target_revision = target_revision_func;
1128 SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton,
1129 result_pool, scratch_pool));
1130 SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2,
1132 SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
1134 *found_abs_paths = TRUE;
1136 SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
1137 editor, NULL, NULL, found_abs_paths,
1139 fetch_props_func, eb,
1140 fetch_base_func, eb,
1143 /* Create the root of the export. */
1144 SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
1145 eb->notify_baton, scratch_pool));
1147 return SVN_NO_ERROR;
1150 static svn_error_t *
1151 export_file_ev2(const char *from_path_or_url,
1152 const char *to_path,
1153 struct edit_baton *eb,
1154 svn_client__pathrev_t *loc,
1155 svn_ra_session_t *ra_session,
1156 svn_boolean_t overwrite,
1157 apr_pool_t *scratch_pool)
1159 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1161 svn_stream_t *tmp_stream;
1162 svn_node_kind_t to_kind;
1164 if (svn_path_is_empty(to_path))
1167 to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1169 to_path = svn_dirent_basename(from_path_or_url, NULL);
1170 eb->root_path = to_path;
1174 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1175 from_is_url, scratch_pool));
1176 eb->root_path = to_path;
1179 SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1181 if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1183 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1184 _("Destination file '%s' exists, and "
1185 "will not be overwritten unless forced"),
1186 svn_dirent_local_style(to_path, scratch_pool));
1187 else if (to_kind == svn_node_dir)
1188 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1189 _("Destination '%s' exists. Cannot "
1190 "overwrite directory with non-directory"),
1191 svn_dirent_local_style(to_path, scratch_pool));
1193 tmp_stream = svn_stream_buffered(scratch_pool);
1195 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1196 tmp_stream, NULL, &props, scratch_pool));
1198 /* Since you cannot actually root an editor at a file, we manually drive
1199 * a function of our editor. */
1200 SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
1203 return SVN_NO_ERROR;
1206 static svn_error_t *
1207 export_file(const char *from_path_or_url,
1208 const char *to_path,
1209 struct edit_baton *eb,
1210 svn_client__pathrev_t *loc,
1211 svn_ra_session_t *ra_session,
1212 svn_boolean_t overwrite,
1213 apr_pool_t *scratch_pool)
1216 apr_hash_index_t *hi;
1217 struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
1218 svn_node_kind_t to_kind;
1219 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1221 if (svn_path_is_empty(to_path))
1224 to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1226 to_path = svn_dirent_basename(from_path_or_url, NULL);
1227 eb->root_path = to_path;
1231 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1232 from_is_url, scratch_pool));
1233 eb->root_path = to_path;
1236 SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1238 if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1240 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1241 _("Destination file '%s' exists, and "
1242 "will not be overwritten unless forced"),
1243 svn_dirent_local_style(to_path, scratch_pool));
1244 else if (to_kind == svn_node_dir)
1245 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1246 _("Destination '%s' exists. Cannot "
1247 "overwrite directory with non-directory"),
1248 svn_dirent_local_style(to_path, scratch_pool));
1250 /* Since you cannot actually root an editor at a file, we
1251 * manually drive a few functions of our editor. */
1253 /* This is the equivalent of a parentless add_file(). */
1254 fb->edit_baton = eb;
1255 fb->path = eb->root_path;
1256 fb->url = eb->root_url;
1257 fb->pool = scratch_pool;
1258 fb->repos_root_url = eb->repos_root_url;
1260 /* Copied from apply_textdelta(). */
1261 SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
1262 svn_dirent_dirname(fb->path, scratch_pool),
1263 svn_io_file_del_none,
1264 fb->pool, fb->pool));
1266 /* Step outside the editor-likeness for a moment, to actually talk
1267 * to the repository. */
1268 /* ### note: the stream will not be closed */
1269 SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1271 NULL, &props, scratch_pool));
1273 /* Push the props into change_file_prop(), to update the file_baton
1274 * with information. */
1275 for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
1277 const char *propname = apr_hash_this_key(hi);
1278 const svn_string_t *propval = apr_hash_this_val(hi);
1280 SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1283 /* And now just use close_file() to do all the keyword and EOL
1284 * work, and put the file into place. */
1285 SVN_ERR(close_file(fb, NULL, scratch_pool));
1287 return SVN_NO_ERROR;
1290 static svn_error_t *
1291 export_directory(const char *from_path_or_url,
1292 const char *to_path,
1293 struct edit_baton *eb,
1294 svn_client__pathrev_t *loc,
1295 svn_ra_session_t *ra_session,
1296 svn_boolean_t overwrite,
1297 svn_boolean_t ignore_externals,
1298 svn_boolean_t ignore_keywords,
1300 const char *native_eol,
1301 svn_client_ctx_t *ctx,
1302 apr_pool_t *scratch_pool)
1305 const svn_delta_editor_t *export_editor;
1306 const svn_ra_reporter3_t *reporter;
1308 svn_node_kind_t kind;
1310 if (!ENABLE_EV2_IMPL)
1311 SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1312 scratch_pool, scratch_pool));
1314 SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1315 scratch_pool, scratch_pool));
1317 /* Manufacture a basic 'report' to the update reporter. */
1318 SVN_ERR(svn_ra_do_update3(ra_session,
1319 &reporter, &report_baton,
1321 "", /* no sub-target */
1323 FALSE, /* don't want copyfrom-args */
1324 FALSE, /* don't want ignore_ancestry */
1325 export_editor, edit_baton,
1326 scratch_pool, scratch_pool));
1328 SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1329 /* Depth is irrelevant, as we're
1330 passing start_empty=TRUE anyway. */
1332 TRUE, /* "help, my dir is empty!" */
1333 NULL, scratch_pool));
1335 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1337 /* Special case: Due to our sly export/checkout method of updating an
1338 * empty directory, no target will have been created if the exported
1339 * item is itself an empty directory (export_editor->open_root never
1340 * gets called, because there are no "changes" to make to the empty
1341 * dir we reported to the repository).
1343 * So we just create the empty dir manually; but we do it via
1344 * open_root_internal(), in order to get proper notification.
1346 SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
1347 if (kind == svn_node_none)
1348 SVN_ERR(open_root_internal
1349 (to_path, overwrite, ctx->notify_func2,
1350 ctx->notify_baton2, scratch_pool));
1352 if (! ignore_externals && depth == svn_depth_infinity)
1354 const char *to_abspath;
1356 SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1357 SVN_ERR(svn_client__export_externals(eb->externals,
1359 to_abspath, eb->repos_root_url,
1362 ctx, scratch_pool));
1365 return SVN_NO_ERROR;
1370 /*** Public Interfaces ***/
1373 svn_client_export5(svn_revnum_t *result_rev,
1374 const char *from_path_or_url,
1375 const char *to_path,
1376 const svn_opt_revision_t *peg_revision,
1377 const svn_opt_revision_t *revision,
1378 svn_boolean_t overwrite,
1379 svn_boolean_t ignore_externals,
1380 svn_boolean_t ignore_keywords,
1382 const char *native_eol,
1383 svn_client_ctx_t *ctx,
1386 svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1387 svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1389 SVN_ERR_ASSERT(peg_revision != NULL);
1390 SVN_ERR_ASSERT(revision != NULL);
1392 if (svn_path_is_url(to_path))
1393 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1394 _("'%s' is not a local path"), to_path);
1396 peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1398 revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1400 if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1402 svn_client__pathrev_t *loc;
1403 svn_ra_session_t *ra_session;
1404 svn_node_kind_t kind;
1405 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1407 /* Get the RA connection. */
1408 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1409 from_path_or_url, NULL,
1411 revision, ctx, pool));
1413 SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
1414 eb->root_path = to_path;
1415 eb->root_url = loc->url;
1416 eb->force = overwrite;
1417 eb->target_revision = &edit_revision;
1418 eb->externals = apr_hash_make(pool);
1419 eb->native_eol = native_eol;
1420 eb->ignore_keywords = ignore_keywords;
1421 eb->cancel_func = ctx->cancel_func;
1422 eb->cancel_baton = ctx->cancel_baton;
1423 eb->notify_func = ctx->notify_func2;
1424 eb->notify_baton = ctx->notify_baton2;
1426 SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1428 if (kind == svn_node_file)
1430 if (!ENABLE_EV2_IMPL)
1431 SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
1434 SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
1435 ra_session, overwrite, pool));
1437 else if (kind == svn_node_dir)
1439 SVN_ERR(export_directory(from_path_or_url, to_path,
1440 eb, loc, ra_session, overwrite,
1441 ignore_externals, ignore_keywords, depth,
1442 native_eol, ctx, pool));
1444 else if (kind == svn_node_none)
1446 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1447 _("URL '%s' doesn't exist"),
1450 /* kind == svn_node_unknown not handled */
1454 struct export_info_baton eib;
1455 svn_node_kind_t kind;
1456 apr_hash_t *externals = NULL;
1458 /* This is a working copy export. */
1459 /* just copy the contents of the working copy into the target path. */
1460 SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
1463 SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1465 SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1467 /* ### [JAF] If something already exists on disk at the destination path,
1468 * the behaviour depends on the node kinds of the source and destination
1469 * and on the FORCE flag. The intention (I guess) is to follow the
1470 * semantics of svn_client_export5(), semantics that are not fully
1471 * documented but would be something like:
1473 * -----------+---------------------------------------------------------
1474 * Src | DIR FILE SPECIAL
1475 * Dst (disk) +---------------------------------------------------------
1476 * NONE | simple copy simple copy (as src=file?)
1477 * DIR | merge if forced [2] inside if root [1] (as src=file?)
1478 * FILE | err overwr if forced[3] (as src=file?)
1479 * SPECIAL | ??? ??? ???
1480 * -----------+---------------------------------------------------------
1482 * [1] FILE onto DIR case: If this file is the root of the copy and thus
1483 * the only node to be copied, then copy it as a child of the
1484 * directory TO, applying these same rules again except that if this
1485 * case occurs again (the child path is already a directory) then
1486 * error out. If this file is not the root of the copy (it is
1487 * reached by recursion), then error out.
1489 * [2] DIR onto DIR case. If the 'FORCE' flag is true then copy the
1490 * source's children inside the target dir, else error out. When
1491 * copying the children, apply the same set of rules, except in the
1492 * FILE onto DIR case error out like in note [1].
1494 * [3] If the 'FORCE' flag is true then overwrite the destination file
1497 * The reality (apparently, looking at the code) is somewhat different.
1498 * For a start, to detect the source kind, it looks at what is on disk
1499 * rather than the versioned working or base node.
1501 if (kind == svn_node_file)
1502 SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1505 eib.to_path = to_path;
1506 eib.revision = revision;
1507 eib.overwrite = overwrite;
1508 eib.ignore_keywords = ignore_keywords;
1509 eib.wc_ctx = ctx->wc_ctx;
1510 eib.native_eol = native_eol;
1511 eib.notify_func = ctx->notify_func2;;
1512 eib.notify_baton = ctx->notify_baton2;
1513 eib.origin_abspath = from_path_or_url;
1514 eib.exported = FALSE;
1516 SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1518 TRUE /* no_ignore */,
1519 FALSE /* ignore_text_mods */,
1522 ctx->cancel_func, ctx->cancel_baton,
1526 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1527 _("The node '%s' was not found."),
1528 svn_dirent_local_style(from_path_or_url,
1531 if (!ignore_externals)
1532 SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1536 if (externals && apr_hash_count(externals))
1538 apr_pool_t *iterpool = svn_pool_create(pool);
1539 apr_hash_index_t *hi;
1541 for (hi = apr_hash_first(pool, externals);
1543 hi = apr_hash_next(hi))
1545 const char *external_abspath = apr_hash_this_key(hi);
1546 const char *relpath;
1547 const char *target_abspath;
1549 svn_pool_clear(iterpool);
1551 relpath = svn_dirent_skip_ancestor(from_path_or_url,
1554 target_abspath = svn_dirent_join(to_path, relpath,
1557 /* Ensure that the parent directory exists */
1558 SVN_ERR(svn_io_make_dir_recursively(
1559 svn_dirent_dirname(target_abspath, iterpool),
1562 SVN_ERR(svn_client_export5(NULL,
1563 svn_dirent_join(from_path_or_url,
1567 peg_revision, revision,
1568 TRUE, ignore_externals,
1569 ignore_keywords, depth, native_eol,
1573 svn_pool_destroy(iterpool);
1578 if (ctx->notify_func2)
1580 svn_wc_notify_t *notify
1581 = svn_wc_create_notify(to_path,
1582 svn_wc_notify_update_completed, pool);
1583 notify->revision = edit_revision;
1584 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1588 *result_rev = edit_revision;
1590 return SVN_NO_ERROR;