In the command-line client, forbid
[svn.git] / subversion / libsvn_client / export.c
bloba1f77eaa8db19dea9f2183223fe1c84f2af70d59
1 /*
2 * export.c: export a tree.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <apr_file_io.h>
26 #include <apr_md5.h>
27 #include "svn_types.h"
28 #include "svn_client.h"
29 #include "svn_string.h"
30 #include "svn_error.h"
31 #include "svn_path.h"
32 #include "svn_pools.h"
33 #include "svn_subst.h"
34 #include "svn_time.h"
35 #include "svn_md5.h"
36 #include "svn_props.h"
37 #include "client.h"
39 #include "svn_private_config.h"
40 #include "private/svn_wc_private.h"
43 /*** Code. ***/
45 /* Add EXTERNALS_PROP_VAL for the export destination path PATH to
46 TRAVERSAL_INFO. */
47 static void
48 add_externals(apr_hash_t *externals,
49 const char *path,
50 const svn_string_t *externals_prop_val)
52 apr_pool_t *pool = apr_hash_pool_get(externals);
54 if (! externals_prop_val)
55 return;
57 apr_hash_set(externals,
58 apr_pstrdup(pool, path),
59 APR_HASH_KEY_STRING,
60 apr_pstrmemdup(pool, externals_prop_val->data,
61 externals_prop_val->len));
64 /* Helper function that gets the eol style and optionally overrides the
65 EOL marker for files marked as native with the EOL marker matching
66 the string specified in requested_value which is of the same format
67 as the svn:eol-style property values. */
68 static svn_error_t *
69 get_eol_style(svn_subst_eol_style_t *style,
70 const char **eol,
71 const char *value,
72 const char *requested_value)
74 svn_subst_eol_style_from_value(style, eol, value);
75 if (requested_value && *style == svn_subst_eol_style_native)
77 svn_subst_eol_style_t requested_style;
78 const char *requested_eol;
80 svn_subst_eol_style_from_value(&requested_style, &requested_eol,
81 requested_value);
83 if (requested_style == svn_subst_eol_style_fixed)
84 *eol = requested_eol;
85 else
86 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
87 _("'%s' is not a valid EOL value"),
88 requested_value);
90 return SVN_NO_ERROR;
93 static svn_error_t *
94 copy_one_versioned_file(const char *from,
95 const char *to,
96 svn_wc_adm_access_t *adm_access,
97 svn_opt_revision_t *revision,
98 const char *native_eol,
99 apr_pool_t *pool)
101 const svn_wc_entry_t *entry;
102 apr_hash_t *kw = NULL;
103 svn_subst_eol_style_t style;
104 apr_hash_t *props;
105 const char *base;
106 svn_string_t *eol_style, *keywords, *executable, *externals, *special;
107 const char *eol = NULL;
108 svn_boolean_t local_mod = FALSE;
109 apr_time_t tm;
111 SVN_ERR(svn_wc_entry(&entry, from, adm_access, FALSE, pool));
113 /* Only export 'added' files when the revision is WORKING.
114 Otherwise, skip the 'added' files, since they didn't exist
115 in the BASE revision and don't have an associated text-base.
117 Don't export 'deleted' files and directories unless it's a
118 revision other than WORKING. These files and directories
119 don't really exist in WORKING. */
120 if ((revision->kind != svn_opt_revision_working &&
121 entry->schedule == svn_wc_schedule_add) ||
122 (revision->kind == svn_opt_revision_working &&
123 entry->schedule == svn_wc_schedule_delete))
124 return SVN_NO_ERROR;
126 if (revision->kind != svn_opt_revision_working)
128 SVN_ERR(svn_wc_get_pristine_copy_path(from, &base,
129 pool));
130 SVN_ERR(svn_wc_get_prop_diffs(NULL, &props, from,
131 adm_access, pool));
133 else
135 svn_wc_status2_t *status;
137 base = from;
138 SVN_ERR(svn_wc_prop_list(&props, from,
139 adm_access, pool));
140 SVN_ERR(svn_wc_status2(&status, from,
141 adm_access, pool));
142 if (status->text_status != svn_wc_status_normal)
143 local_mod = TRUE;
146 eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE,
147 APR_HASH_KEY_STRING);
148 keywords = apr_hash_get(props, SVN_PROP_KEYWORDS,
149 APR_HASH_KEY_STRING);
150 executable = apr_hash_get(props, SVN_PROP_EXECUTABLE,
151 APR_HASH_KEY_STRING);
152 externals = apr_hash_get(props, SVN_PROP_EXTERNALS,
153 APR_HASH_KEY_STRING);
154 special = apr_hash_get(props, SVN_PROP_SPECIAL,
155 APR_HASH_KEY_STRING);
157 if (eol_style)
158 SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol));
160 if (local_mod && (! special))
162 /* Use the modified time from the working copy of
163 the file */
164 SVN_ERR(svn_io_file_affected_time(&tm, from, pool));
166 else
168 tm = entry->cmt_date;
171 if (keywords)
173 const char *fmt;
174 const char *author;
176 if (local_mod)
178 /* For locally modified files, we'll append an 'M'
179 to the revision number, and set the author to
180 "(local)" since we can't always determine the
181 current user's username */
182 fmt = "%ldM";
183 author = _("(local)");
185 else
187 fmt = "%ld";
188 author = entry->cmt_author;
191 SVN_ERR(svn_subst_build_keywords2
192 (&kw, keywords->data,
193 apr_psprintf(pool, fmt, entry->cmt_rev),
194 entry->url, tm, author, pool));
197 SVN_ERR(svn_subst_copy_and_translate3(base, to, eol, FALSE,
198 kw, TRUE,
199 special ? TRUE : FALSE,
200 pool));
201 if (executable)
202 SVN_ERR(svn_io_set_file_executable(to, TRUE,
203 FALSE, pool));
205 if (! special)
206 SVN_ERR(svn_io_set_file_affected_time(tm, to, pool));
208 return SVN_NO_ERROR;
211 static svn_error_t *
212 copy_versioned_files(const char *from,
213 const char *to,
214 svn_opt_revision_t *revision,
215 svn_boolean_t force,
216 svn_boolean_t ignore_externals,
217 svn_depth_t depth,
218 const char *native_eol,
219 svn_client_ctx_t *ctx,
220 apr_pool_t *pool)
222 svn_wc_adm_access_t *adm_access;
223 const svn_wc_entry_t *entry;
224 svn_error_t *err;
225 apr_pool_t *iterpool;
226 apr_hash_t *entries;
227 apr_hash_index_t *hi;
228 apr_finfo_t finfo;
230 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, from, FALSE,
231 0, ctx->cancel_func, ctx->cancel_baton,
232 pool));
234 SVN_ERR(svn_wc__entry_versioned(&entry, from, adm_access, FALSE, pool));
236 /* Only export 'added' files when the revision is WORKING.
237 Otherwise, skip the 'added' files, since they didn't exist
238 in the BASE revision and don't have an associated text-base.
240 Don't export 'deleted' files and directories unless it's a
241 revision other than WORKING. These files and directories
242 don't really exist in WORKING. */
243 if ((revision->kind != svn_opt_revision_working &&
244 entry->schedule == svn_wc_schedule_add) ||
245 (revision->kind == svn_opt_revision_working &&
246 entry->schedule == svn_wc_schedule_delete))
247 return SVN_NO_ERROR;
249 if (entry->kind == svn_node_dir)
251 /* Try to make the new directory. If this fails because the
252 directory already exists, check our FORCE flag to see if we
253 care. */
254 SVN_ERR(svn_io_stat(&finfo, from, APR_FINFO_PROT, pool));
255 err = svn_io_dir_make(to, finfo.protection, pool);
256 if (err)
258 if (! APR_STATUS_IS_EEXIST(err->apr_err))
259 return err;
260 if (! force)
261 SVN_ERR_W(err, _("Destination directory exists, and will not be "
262 "overwritten unless forced"));
263 else
264 svn_error_clear(err);
267 SVN_ERR(svn_wc_entries_read(&entries, adm_access, FALSE, pool));
269 iterpool = svn_pool_create(pool);
270 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
272 const char *item;
273 const void *key;
274 void *val;
276 svn_pool_clear(iterpool);
278 apr_hash_this(hi, &key, NULL, &val);
280 item = key;
281 entry = val;
283 if (ctx->cancel_func)
284 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
286 /* ### We could also invoke ctx->notify_func somewhere in
287 ### here... Is it called for, though? Not sure. */
289 if (entry->kind == svn_node_dir)
291 if (strcmp(item, SVN_WC_ENTRY_THIS_DIR) == 0)
293 ; /* skip this, it's the current directory that we're
294 handling now. */
296 else
298 if (depth == svn_depth_infinity)
300 const char *new_from = svn_path_join(from, item,
301 iterpool);
302 const char *new_to = svn_path_join(to, item, iterpool);
304 SVN_ERR(copy_versioned_files(new_from, new_to,
305 revision, force,
306 ignore_externals, depth,
307 native_eol, ctx,
308 iterpool));
312 else if (entry->kind == svn_node_file)
314 const char *new_from = svn_path_join(from, item, iterpool);
315 const char *new_to = svn_path_join(to, item, iterpool);
317 SVN_ERR(copy_one_versioned_file(new_from, new_to, adm_access,
318 revision, native_eol,
319 iterpool));
323 /* Handle externals. */
324 if (! ignore_externals && depth == svn_depth_infinity
325 && entry->depth == svn_depth_infinity)
327 apr_array_header_t *ext_items;
328 const svn_string_t *prop_val;
330 SVN_ERR(svn_wc_prop_get(&prop_val, SVN_PROP_EXTERNALS,
331 from, adm_access, pool));
332 if (prop_val != NULL)
334 int i;
336 SVN_ERR(svn_wc_parse_externals_description3(&ext_items, from,
337 prop_val->data,
338 FALSE, pool));
339 for (i = 0; i < ext_items->nelts; ++i)
341 svn_wc_external_item2_t *ext_item;
342 const char *new_from, *new_to;
344 svn_pool_clear(iterpool);
346 ext_item = APR_ARRAY_IDX(ext_items, i,
347 svn_wc_external_item2_t *);
348 new_from = svn_path_join(from, ext_item->target_dir,
349 iterpool);
350 new_to = svn_path_join(to, ext_item->target_dir,
351 iterpool);
353 /* The target dir might have multiple components. Guarantee
354 the path leading down to the last component. */
355 if (svn_path_component_count(ext_item->target_dir) > 1)
357 const char *parent = svn_path_dirname(new_to, iterpool);
358 SVN_ERR(svn_io_make_dir_recursively(parent, iterpool));
361 SVN_ERR(copy_versioned_files(new_from, new_to,
362 revision, force, FALSE,
363 svn_depth_infinity, native_eol,
364 ctx, iterpool));
369 svn_pool_destroy(iterpool);
371 else if (entry->kind == svn_node_file)
373 SVN_ERR(copy_one_versioned_file(from, to, adm_access, revision,
374 native_eol, pool));
377 SVN_ERR(svn_wc_adm_close(adm_access));
378 return SVN_NO_ERROR;
382 /* Abstraction of open_root.
384 * Create PATH if it does not exist and is not obstructed, and invoke
385 * NOTIFY_FUNC with NOTIFY_BATON on PATH.
387 * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_DIRECTORY.
389 * If PATH is a already a directory, then error with
390 * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
391 * export into PATH with no error.
393 static svn_error_t *
394 open_root_internal(const char *path,
395 svn_boolean_t force,
396 svn_wc_notify_func2_t notify_func,
397 void *notify_baton,
398 apr_pool_t *pool)
400 svn_node_kind_t kind;
402 SVN_ERR(svn_io_check_path(path, &kind, pool));
403 if (kind == svn_node_none)
404 SVN_ERR(svn_io_make_dir_recursively(path, pool));
405 else if (kind == svn_node_file)
406 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
407 _("'%s' exists and is not a directory"),
408 svn_path_local_style(path, pool));
409 else if ((kind != svn_node_dir) || (! force))
410 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
411 _("'%s' already exists"),
412 svn_path_local_style(path, pool));
414 if (notify_func)
416 svn_wc_notify_t *notify = svn_wc_create_notify(path,
417 svn_wc_notify_update_add,
418 pool);
419 notify->kind = svn_node_dir;
420 (*notify_func)(notify_baton, notify, pool);
423 return SVN_NO_ERROR;
427 /* ---------------------------------------------------------------------- */
429 /*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
432 struct edit_baton
434 const char *root_path;
435 const char *root_url;
436 svn_boolean_t force;
437 svn_revnum_t *target_revision;
438 apr_hash_t *externals;
439 const char *native_eol;
441 svn_wc_notify_func2_t notify_func;
442 void *notify_baton;
446 struct dir_baton
448 struct edit_baton *edit_baton;
449 const char *path;
453 struct file_baton
455 struct edit_baton *edit_baton;
457 const char *path;
458 const char *tmppath;
460 /* We need to keep this around so we can explicitly close it in close_file,
461 thus flushing its output to disk so we can copy and translate it. */
462 apr_file_t *tmp_file;
464 /* The MD5 digest of the file's fulltext. This is all zeros until
465 the last textdelta window handler call returns. */
466 unsigned char text_digest[APR_MD5_DIGESTSIZE];
468 /* The three svn: properties we might actually care about. */
469 const svn_string_t *eol_style_val;
470 const svn_string_t *keywords_val;
471 const svn_string_t *executable_val;
472 svn_boolean_t special;
474 /* Any keyword vals to be substituted */
475 const char *revision;
476 const char *url;
477 const char *author;
478 apr_time_t date;
480 /* Pool associated with this baton. */
481 apr_pool_t *pool;
485 struct handler_baton
487 svn_txdelta_window_handler_t apply_handler;
488 void *apply_baton;
489 apr_pool_t *pool;
490 const char *tmppath;
494 static svn_error_t *
495 set_target_revision(void *edit_baton,
496 svn_revnum_t target_revision,
497 apr_pool_t *pool)
499 struct edit_baton *eb = edit_baton;
501 /* Stashing a target_revision in the baton */
502 *(eb->target_revision) = target_revision;
503 return SVN_NO_ERROR;
508 /* Just ensure that the main export directory exists. */
509 static svn_error_t *
510 open_root(void *edit_baton,
511 svn_revnum_t base_revision,
512 apr_pool_t *pool,
513 void **root_baton)
515 struct edit_baton *eb = edit_baton;
516 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
518 SVN_ERR(open_root_internal(eb->root_path, eb->force,
519 eb->notify_func, eb->notify_baton, pool));
521 /* Build our dir baton. */
522 db->path = eb->root_path;
523 db->edit_baton = eb;
524 *root_baton = db;
526 return SVN_NO_ERROR;
530 /* Ensure the directory exists, and send feedback. */
531 static svn_error_t *
532 add_directory(const char *path,
533 void *parent_baton,
534 const char *copyfrom_path,
535 svn_revnum_t copyfrom_revision,
536 apr_pool_t *pool,
537 void **baton)
539 struct dir_baton *pb = parent_baton;
540 struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
541 struct edit_baton *eb = pb->edit_baton;
542 const char *full_path = svn_path_join(eb->root_path, path, pool);
543 svn_node_kind_t kind;
545 SVN_ERR(svn_io_check_path(full_path, &kind, pool));
546 if (kind == svn_node_none)
547 SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
548 else if (kind == svn_node_file)
549 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
550 _("'%s' exists and is not a directory"),
551 svn_path_local_style(full_path, pool));
552 else if (! (kind == svn_node_dir && eb->force))
553 return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
554 _("'%s' already exists"),
555 svn_path_local_style(full_path, pool));
557 if (eb->notify_func)
559 svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
560 svn_wc_notify_update_add,
561 pool);
562 notify->kind = svn_node_dir;
563 (*eb->notify_func)(eb->notify_baton, notify, pool);
566 /* Build our dir baton. */
567 db->path = full_path;
568 db->edit_baton = eb;
569 *baton = db;
571 return SVN_NO_ERROR;
575 /* Build a file baton. */
576 static svn_error_t *
577 add_file(const char *path,
578 void *parent_baton,
579 const char *copyfrom_path,
580 svn_revnum_t copyfrom_revision,
581 apr_pool_t *pool,
582 void **baton)
584 struct dir_baton *pb = parent_baton;
585 struct edit_baton *eb = pb->edit_baton;
586 struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
587 const char *full_path = svn_path_join(eb->root_path, path, pool);
588 const char *full_url = svn_path_join(eb->root_url, path, pool);
590 fb->edit_baton = eb;
591 fb->path = full_path;
592 fb->url = full_url;
593 fb->pool = pool;
595 *baton = fb;
596 return SVN_NO_ERROR;
600 static svn_error_t *
601 window_handler(svn_txdelta_window_t *window, void *baton)
603 struct handler_baton *hb = baton;
604 svn_error_t *err;
606 err = hb->apply_handler(window, hb->apply_baton);
607 if (err)
609 /* We failed to apply the patch; clean up the temporary file. */
610 apr_file_remove(hb->tmppath, hb->pool);
613 return err;
618 /* Write incoming data into the tmpfile stream */
619 static svn_error_t *
620 apply_textdelta(void *file_baton,
621 const char *base_checksum,
622 apr_pool_t *pool,
623 svn_txdelta_window_handler_t *handler,
624 void **handler_baton)
626 struct file_baton *fb = file_baton;
627 struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
629 SVN_ERR(svn_io_open_unique_file2(&fb->tmp_file, &(fb->tmppath),
630 fb->path, ".tmp",
631 svn_io_file_del_none, fb->pool));
633 hb->pool = pool;
634 hb->tmppath = fb->tmppath;
636 svn_txdelta_apply(svn_stream_empty(pool),
637 svn_stream_from_aprfile(fb->tmp_file, pool),
638 fb->text_digest, NULL, pool,
639 &hb->apply_handler, &hb->apply_baton);
641 *handler_baton = hb;
642 *handler = window_handler;
643 return SVN_NO_ERROR;
647 static svn_error_t *
648 change_file_prop(void *file_baton,
649 const char *name,
650 const svn_string_t *value,
651 apr_pool_t *pool)
653 struct file_baton *fb = file_baton;
655 if (! value)
656 return SVN_NO_ERROR;
658 /* Store only the magic three properties. */
659 if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
660 fb->eol_style_val = svn_string_dup(value, fb->pool);
662 else if (strcmp(name, SVN_PROP_KEYWORDS) == 0)
663 fb->keywords_val = svn_string_dup(value, fb->pool);
665 else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
666 fb->executable_val = svn_string_dup(value, fb->pool);
668 /* Try to fill out the baton's keywords-structure too. */
669 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
670 fb->revision = apr_pstrdup(fb->pool, value->data);
672 else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
673 SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
675 else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
676 fb->author = apr_pstrdup(fb->pool, value->data);
678 else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
679 fb->special = TRUE;
681 return SVN_NO_ERROR;
685 static svn_error_t *
686 change_dir_prop(void *dir_baton,
687 const char *name,
688 const svn_string_t *value,
689 apr_pool_t *pool)
691 struct dir_baton *db = dir_baton;
692 struct edit_baton *eb = db->edit_baton;
694 if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
695 add_externals(eb->externals, db->path, value);
697 return SVN_NO_ERROR;
701 /* Move the tmpfile to file, and send feedback. */
702 static svn_error_t *
703 close_file(void *file_baton,
704 const char *text_checksum,
705 apr_pool_t *pool)
707 struct file_baton *fb = file_baton;
708 struct edit_baton *eb = fb->edit_baton;
710 /* Was a txdelta even sent? */
711 if (! fb->tmppath)
712 return SVN_NO_ERROR;
714 SVN_ERR(svn_io_file_close(fb->tmp_file, fb->pool));
716 if (text_checksum)
718 const char *actual_checksum
719 = svn_md5_digest_to_cstring(fb->text_digest, pool);
721 if (actual_checksum && (strcmp(text_checksum, actual_checksum) != 0))
723 return svn_error_createf
724 (SVN_ERR_CHECKSUM_MISMATCH, NULL,
725 _("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
726 svn_path_local_style(fb->path, pool),
727 text_checksum, actual_checksum);
731 if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
733 SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
735 else
737 svn_subst_eol_style_t style;
738 const char *eol;
739 apr_hash_t *final_kw;
741 if (fb->eol_style_val)
742 SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
743 eb->native_eol));
745 if (fb->keywords_val)
746 SVN_ERR(svn_subst_build_keywords2(&final_kw, fb->keywords_val->data,
747 fb->revision, fb->url, fb->date,
748 fb->author, pool));
750 SVN_ERR(svn_subst_copy_and_translate3
751 (fb->tmppath, fb->path,
752 fb->eol_style_val ? eol : NULL,
753 fb->eol_style_val ? TRUE : FALSE, /* repair */
754 fb->keywords_val ? final_kw : NULL,
755 TRUE, /* expand */
756 fb->special,
757 pool));
759 SVN_ERR(svn_io_remove_file(fb->tmppath, pool));
762 if (fb->executable_val)
763 SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
765 if (fb->date && (! fb->special))
766 SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
768 if (fb->edit_baton->notify_func)
770 svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
771 svn_wc_notify_update_add,
772 pool);
773 notify->kind = svn_node_file;
774 (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
775 pool);
778 return SVN_NO_ERROR;
783 /*** Public Interfaces ***/
785 svn_error_t *
786 svn_client_export4(svn_revnum_t *result_rev,
787 const char *from,
788 const char *to,
789 const svn_opt_revision_t *peg_revision,
790 const svn_opt_revision_t *revision,
791 svn_boolean_t overwrite,
792 svn_boolean_t ignore_externals,
793 svn_depth_t depth,
794 const char *native_eol,
795 svn_client_ctx_t *ctx,
796 apr_pool_t *pool)
798 svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
799 const char *url;
801 if (svn_path_is_url(from) ||
802 ! (revision->kind == svn_opt_revision_base ||
803 revision->kind == svn_opt_revision_committed ||
804 revision->kind == svn_opt_revision_working ||
805 revision->kind == svn_opt_revision_unspecified))
807 svn_revnum_t revnum;
808 svn_ra_session_t *ra_session;
809 svn_node_kind_t kind;
810 struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
811 const char *repos_root_url;
813 /* Get the RA connection. */
814 SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum,
815 &url, from, NULL,
816 peg_revision,
817 revision, ctx, pool));
819 /* Get the repository root. */
820 SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_root_url, pool));
822 eb->root_path = to;
823 eb->root_url = url;
824 eb->force = overwrite;
825 eb->target_revision = &edit_revision;
826 eb->notify_func = ctx->notify_func2;
827 eb->notify_baton = ctx->notify_baton2;
828 eb->externals = apr_hash_make(pool);
829 eb->native_eol = native_eol;
831 SVN_ERR(svn_ra_check_path(ra_session, "", revnum, &kind, pool));
833 if (kind == svn_node_file)
835 apr_hash_t *props;
836 apr_hash_index_t *hi;
837 struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
839 /* Since you cannot actually root an editor at a file, we
840 * manually drive a few functions of our editor. */
842 /* This is the equivalent of a parentless add_file(). */
843 fb->edit_baton = eb;
844 fb->path = eb->root_path;
845 fb->url = eb->root_url;
846 fb->pool = pool;
848 /* Copied from apply_textdelta(). */
849 SVN_ERR(svn_io_open_unique_file2(&fb->tmp_file, &(fb->tmppath),
850 fb->path, ".tmp",
851 svn_io_file_del_none, fb->pool));
853 /* Step outside the editor-likeness for a moment, to actually talk
854 * to the repository. */
855 SVN_ERR(svn_ra_get_file(ra_session, "", revnum,
856 svn_stream_from_aprfile(fb->tmp_file,
857 pool),
858 NULL, &props, pool));
860 /* Push the props into change_file_prop(), to update the file_baton
861 * with information. */
862 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
864 const void *key;
865 void *val;
866 apr_hash_this(hi, &key, NULL, &val);
867 SVN_ERR(change_file_prop(fb, key, val, pool));
870 /* And now just use close_file() to do all the keyword and EOL
871 * work, and put the file into place. */
872 SVN_ERR(close_file(fb, NULL, pool));
874 else if (kind == svn_node_dir)
876 void *edit_baton;
877 const svn_delta_editor_t *export_editor;
878 const svn_ra_reporter3_t *reporter;
879 void *report_baton;
880 svn_delta_editor_t *editor = svn_delta_default_editor(pool);
881 svn_boolean_t use_sleep = FALSE;
883 editor->set_target_revision = set_target_revision;
884 editor->open_root = open_root;
885 editor->add_directory = add_directory;
886 editor->add_file = add_file;
887 editor->apply_textdelta = apply_textdelta;
888 editor->close_file = close_file;
889 editor->change_file_prop = change_file_prop;
890 editor->change_dir_prop = change_dir_prop;
892 SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
893 ctx->cancel_baton,
894 editor,
896 &export_editor,
897 &edit_baton,
898 pool));
901 /* Manufacture a basic 'report' to the update reporter. */
902 SVN_ERR(svn_ra_do_update2(ra_session,
903 &reporter, &report_baton,
904 revnum,
905 "", /* no sub-target */
906 depth,
907 FALSE, /* don't want copyfrom-args */
908 export_editor, edit_baton, pool));
910 SVN_ERR(reporter->set_path(report_baton, "", revnum,
911 /* Depth is irrelevant, as we're
912 passing start_empty=TRUE anyway. */
913 svn_depth_infinity,
914 TRUE, /* "help, my dir is empty!" */
915 NULL, pool));
917 SVN_ERR(reporter->finish_report(report_baton, pool));
919 /* Special case: Due to our sly export/checkout method of
920 * updating an empty directory, no target will have been created
921 * if the exported item is itself an empty directory
922 * (export_editor->open_root never gets called, because there
923 * are no "changes" to make to the empty dir we reported to the
924 * repository).
926 * So we just create the empty dir manually; but we do it via
927 * open_root_internal(), in order to get proper notification.
929 SVN_ERR(svn_io_check_path(to, &kind, pool));
930 if (kind == svn_node_none)
931 SVN_ERR(open_root_internal
932 (to, overwrite, ctx->notify_func2,
933 ctx->notify_baton2, pool));
935 if (! ignore_externals && depth == svn_depth_infinity)
936 SVN_ERR(svn_client__fetch_externals(eb->externals, from, to,
937 repos_root_url, depth, TRUE,
938 &use_sleep, ctx, pool));
940 else if (kind == svn_node_none)
942 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
943 _("URL '%s' doesn't exist"), from);
945 /* kind == svn_node_unknown not handled */
947 else
949 svn_opt_revision_t working_revision = *revision;
950 /* This is a working copy export. */
951 if (working_revision.kind == svn_opt_revision_unspecified)
953 /* Default to WORKING in the case that we have
954 been given a working copy path */
955 working_revision.kind = svn_opt_revision_working;
958 /* just copy the contents of the working copy into the target path. */
959 SVN_ERR(copy_versioned_files(from, to, &working_revision, overwrite,
960 ignore_externals, depth, native_eol,
961 ctx, pool));
965 if (ctx->notify_func2)
967 svn_wc_notify_t *notify
968 = svn_wc_create_notify(to,
969 svn_wc_notify_update_completed, pool);
970 notify->revision = edit_revision;
971 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
974 if (result_rev)
975 *result_rev = edit_revision;
977 return SVN_NO_ERROR;
980 svn_error_t *
981 svn_client_export3(svn_revnum_t *result_rev,
982 const char *from,
983 const char *to,
984 const svn_opt_revision_t *peg_revision,
985 const svn_opt_revision_t *revision,
986 svn_boolean_t overwrite,
987 svn_boolean_t ignore_externals,
988 svn_boolean_t recurse,
989 const char *native_eol,
990 svn_client_ctx_t *ctx,
991 apr_pool_t *pool)
993 return svn_client_export4(result_rev, from, to, peg_revision, revision,
994 overwrite, ignore_externals,
995 SVN_DEPTH_INFINITY_OR_FILES(recurse),
996 native_eol, ctx, pool);
999 svn_error_t *
1000 svn_client_export2(svn_revnum_t *result_rev,
1001 const char *from,
1002 const char *to,
1003 svn_opt_revision_t *revision,
1004 svn_boolean_t force,
1005 const char *native_eol,
1006 svn_client_ctx_t *ctx,
1007 apr_pool_t *pool)
1009 svn_opt_revision_t peg_revision;
1011 peg_revision.kind = svn_opt_revision_unspecified;
1013 return svn_client_export3(result_rev, from, to, &peg_revision,
1014 revision, force, FALSE, TRUE,
1015 native_eol, ctx, pool);
1019 svn_error_t *
1020 svn_client_export(svn_revnum_t *result_rev,
1021 const char *from,
1022 const char *to,
1023 svn_opt_revision_t *revision,
1024 svn_boolean_t force,
1025 svn_client_ctx_t *ctx,
1026 apr_pool_t *pool)
1028 return svn_client_export2(result_rev, from, to, revision, force, NULL, ctx,
1029 pool);