]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/export.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_client / export.c
1 /*
2  * export.c:  export a tree.
3  *
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
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
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
20  *    under the License.
21  * ====================================================================
22  */
23
24 /* ==================================================================== */
25
26
27 \f
28 /*** Includes. ***/
29
30 #include <apr_file_io.h>
31 #include <apr_md5.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"
37 #include "svn_hash.h"
38 #include "svn_path.h"
39 #include "svn_pools.h"
40 #include "svn_subst.h"
41 #include "svn_time.h"
42 #include "svn_props.h"
43 #include "client.h"
44
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"
49
50 #ifndef ENABLE_EV2_IMPL
51 #define ENABLE_EV2_IMPL 0
52 #endif
53
54 \f
55 /*** Code. ***/
56
57 /* Add EXTERNALS_PROP_VAL for the export destination path PATH to
58    TRAVERSAL_INFO.  */
59 static svn_error_t *
60 add_externals(apr_hash_t *externals,
61               const char *path,
62               const svn_string_t *externals_prop_val)
63 {
64   apr_pool_t *pool = apr_hash_pool_get(externals);
65   const char *local_abspath;
66
67   if (! externals_prop_val)
68     return SVN_NO_ERROR;
69
70   SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
71
72   svn_hash_sets(externals, local_abspath,
73                 apr_pstrmemdup(pool, externals_prop_val->data,
74                                externals_prop_val->len));
75
76   return SVN_NO_ERROR;
77 }
78
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. */
83 static svn_error_t *
84 get_eol_style(svn_subst_eol_style_t *style,
85               const char **eol,
86               const char *value,
87               const char *requested_value)
88 {
89   svn_subst_eol_style_from_value(style, eol, value);
90   if (requested_value && *style == svn_subst_eol_style_native)
91     {
92       svn_subst_eol_style_t requested_style;
93       const char *requested_eol;
94
95       svn_subst_eol_style_from_value(&requested_style, &requested_eol,
96                                      requested_value);
97
98       if (requested_style == svn_subst_eol_style_fixed)
99         *eol = requested_eol;
100       else
101         return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
102                                  _("'%s' is not a valid EOL value"),
103                                  requested_value);
104     }
105   return SVN_NO_ERROR;
106 }
107
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.
112  */
113 static svn_error_t *
114 append_basename_if_dir(const char **appendable_dirent_p,
115                        const char *basename_of,
116                        svn_boolean_t is_uri,
117                        apr_pool_t *pool)
118 {
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)
122     {
123       const char *base_name;
124
125       if (is_uri)
126         base_name = svn_uri_basename(basename_of, pool);
127       else
128         base_name = svn_dirent_basename(basename_of, NULL);
129
130       *appendable_dirent_p = svn_dirent_join(*appendable_dirent_p,
131                                              base_name, pool);
132     }
133
134   return SVN_NO_ERROR;
135 }
136
137 /* Make an unversioned copy of the versioned file at FROM_ABSPATH.  Copy it
138  * to the destination path TO_ABSPATH.
139  *
140  * If REVISION is svn_opt_revision_working, copy the working version,
141  * otherwise copy the base version.
142  *
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.
146  *
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.
151  *
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
155  * changed time.
156  *
157  * Set the destination file's 'executable' flag according to the source
158  * file's 'svn:executable' property.
159  */
160
161 /* baton for export_node */
162 struct export_info_baton
163 {
164   const char *to_path;
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;
171   void *notify_baton;
172   const char *origin_abspath;
173   svn_boolean_t exported;
174 };
175
176 /* Export a file or directory. Implements svn_wc_status_func4_t */
177 static svn_error_t *
178 export_node(void *baton,
179             const char *local_abspath,
180             const svn_wc_status3_t *status,
181             apr_pool_t *scratch_pool)
182 {
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;
187   apr_hash_t *props;
188   svn_string_t *eol_style, *keywords, *executable, *special;
189   const char *eol = NULL;
190   svn_boolean_t local_mod = FALSE;
191   apr_time_t tm;
192   svn_stream_t *source;
193   svn_stream_t *dst_stream;
194   const char *dst_tmp;
195   svn_error_t *err;
196
197   const char *to_abspath = svn_dirent_join(
198                                 eib->to_path,
199                                 svn_dirent_skip_ancestor(eib->origin_abspath,
200                                                          local_abspath),
201                                 scratch_pool);
202
203   eib->exported = TRUE;
204
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)
210     return SVN_NO_ERROR;
211
212   if (status->kind == svn_node_dir)
213     {
214       apr_fileperms_t perm = APR_OS_DEFAULT;
215
216       /* Try to make the new directory.  If this fails because the
217          directory already exists, check our FORCE flag to see if we
218          care. */
219
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' */
225 #ifndef WIN32
226       if (eib->revision->kind == svn_opt_revision_working)
227         {
228           apr_finfo_t finfo;
229           SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_PROT,
230                               scratch_pool));
231           perm = finfo.protection;
232         }
233 #endif
234       err = svn_io_dir_make(to_abspath, perm, scratch_pool);
235       if (err)
236         {
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"));
242           else
243             svn_error_clear(err);
244         }
245
246       if (eib->notify_func
247           && (strcmp(eib->origin_abspath, local_abspath) != 0))
248         {
249           svn_wc_notify_t *notify =
250               svn_wc_create_notify(to_abspath,
251                                    svn_wc_notify_update_add, scratch_pool);
252
253           notify->kind = svn_node_dir;
254           (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
255         }
256
257       return SVN_NO_ERROR;
258     }
259   else if (status->kind != svn_node_file)
260     {
261       if (strcmp(eib->origin_abspath, local_abspath) != 0)
262         return SVN_NO_ERROR;
263
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,
267                                                       scratch_pool));
268     }
269
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)
273     return SVN_NO_ERROR;
274
275   /* Produce overwrite errors for the export root */
276   if (strcmp(local_abspath, eib->origin_abspath) == 0)
277     {
278       svn_node_kind_t to_kind;
279
280       SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
281
282       if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
283           && !eib->overwrite)
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,
288                                                         scratch_pool));
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,
294                                                         scratch_pool));
295     }
296
297   if (eib->revision->kind != svn_opt_revision_working)
298     {
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.
302
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.
316
317          Copied-/moved-here nodes have a base, so export both added and
318          replaced files when they involve a copy-/move-here.
319
320          We get all this for free from evaluating SOURCE == NULL:
321        */
322       SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
323                                             scratch_pool, scratch_pool));
324       if (source == NULL)
325         return SVN_NO_ERROR;
326
327       SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
328                                         scratch_pool, scratch_pool));
329     }
330   else
331     {
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,
335                                          scratch_pool));
336
337       SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
338                                 scratch_pool));
339       if (status->node_status != svn_wc_status_normal)
340         local_mod = TRUE;
341     }
342
343   /* We can early-exit if we're creating a special file. */
344   special = svn_hash_gets(props, SVN_PROP_SPECIAL);
345   if (special != NULL)
346     {
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));
354     }
355
356
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);
360
361   if (eol_style)
362     SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
363
364   if (local_mod)
365     {
366       /* Use the modified time from the working copy of
367          the file */
368       SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
369     }
370   else
371     {
372       tm = status->changed_date;
373     }
374
375   if (keywords)
376     {
377       svn_revnum_t changed_rev = status->changed_rev;
378       const char *suffix;
379       const char *url = svn_path_url_add_component2(status->repos_root_url,
380                                                     status->repos_relpath,
381                                                     scratch_pool);
382       const char *author = status->changed_author;
383       if (local_mod)
384         {
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 */
389           suffix = "M";
390           author = _("(local)");
391         }
392       else
393         {
394           suffix = "";
395         }
396
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));
402     }
403
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,
409                                  scratch_pool));
410
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,
415                                              eol,
416                                              FALSE /* repair */,
417                                              kw,
418                                              ! eib->ignore_keywords /* expand */,
419                                              scratch_pool);
420
421   /* ###: use cancel func/baton in place of NULL/NULL below. */
422   err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
423
424   if (!err && executable)
425     err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
426
427   if (!err)
428     err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
429
430   if (err)
431     return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
432                                                              scratch_pool));
433
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));
436
437   if (eib->notify_func)
438     {
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);
443     }
444
445   return SVN_NO_ERROR;
446 }
447
448 /* Abstraction of open_root.
449  *
450  * Create PATH if it does not exist and is not obstructed, and invoke
451  * NOTIFY_FUNC with NOTIFY_BATON on PATH.
452  *
453  * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
454  *
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.
458  */
459 static svn_error_t *
460 open_root_internal(const char *path,
461                    svn_boolean_t force,
462                    svn_wc_notify_func2_t notify_func,
463                    void *notify_baton,
464                    apr_pool_t *pool)
465 {
466   svn_node_kind_t kind;
467
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));
479
480   if (notify_func)
481     {
482       svn_wc_notify_t *notify = svn_wc_create_notify(path,
483                                                      svn_wc_notify_update_add,
484                                                      pool);
485       notify->kind = svn_node_dir;
486       (*notify_func)(notify_baton, notify, pool);
487     }
488
489   return SVN_NO_ERROR;
490 }
491
492
493 /* ---------------------------------------------------------------------- */
494
495 \f
496 /*** A dedicated 'export' editor, which does no .svn/ accounting.  ***/
497
498
499 struct edit_baton
500 {
501   const char *repos_root_url;
502   const char *root_path;
503   const char *root_url;
504   svn_boolean_t force;
505   svn_revnum_t *target_revision;
506   apr_hash_t *externals;
507   const char *native_eol;
508   svn_boolean_t ignore_keywords;
509
510   svn_cancel_func_t cancel_func;
511   void *cancel_baton;
512   svn_wc_notify_func2_t notify_func;
513   void *notify_baton;
514 };
515
516
517 struct dir_baton
518 {
519   struct edit_baton *edit_baton;
520   const char *path;
521 };
522
523
524 struct file_baton
525 {
526   struct edit_baton *edit_baton;
527
528   const char *path;
529   const char *tmppath;
530
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;
534
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];
538
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;
544
545   /* Any keyword vals to be substituted */
546   const char *revision;
547   const char *url;
548   const char *repos_root_url;
549   const char *author;
550   apr_time_t date;
551
552   /* Pool associated with this baton. */
553   apr_pool_t *pool;
554 };
555
556
557 struct handler_baton
558 {
559   svn_txdelta_window_handler_t apply_handler;
560   void *apply_baton;
561   apr_pool_t *pool;
562   const char *tmppath;
563 };
564
565
566 static svn_error_t *
567 set_target_revision(void *edit_baton,
568                     svn_revnum_t target_revision,
569                     apr_pool_t *pool)
570 {
571   struct edit_baton *eb = edit_baton;
572
573   /* Stashing a target_revision in the baton */
574   *(eb->target_revision) = target_revision;
575   return SVN_NO_ERROR;
576 }
577
578
579
580 /* Just ensure that the main export directory exists. */
581 static svn_error_t *
582 open_root(void *edit_baton,
583           svn_revnum_t base_revision,
584           apr_pool_t *pool,
585           void **root_baton)
586 {
587   struct edit_baton *eb = edit_baton;
588   struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
589
590   SVN_ERR(open_root_internal(eb->root_path, eb->force,
591                              eb->notify_func, eb->notify_baton, pool));
592
593   /* Build our dir baton. */
594   db->path = eb->root_path;
595   db->edit_baton = eb;
596   *root_baton = db;
597
598   return SVN_NO_ERROR;
599 }
600
601
602 /* Ensure the directory exists, and send feedback. */
603 static svn_error_t *
604 add_directory(const char *path,
605               void *parent_baton,
606               const char *copyfrom_path,
607               svn_revnum_t copyfrom_revision,
608               apr_pool_t *pool,
609               void **baton)
610 {
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;
616
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));
628
629   if (eb->notify_func)
630     {
631       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
632                                                      svn_wc_notify_update_add,
633                                                      pool);
634       notify->kind = svn_node_dir;
635       (*eb->notify_func)(eb->notify_baton, notify, pool);
636     }
637
638   /* Build our dir baton. */
639   db->path = full_path;
640   db->edit_baton = eb;
641   *baton = db;
642
643   return SVN_NO_ERROR;
644 }
645
646
647 /* Build a file baton. */
648 static svn_error_t *
649 add_file(const char *path,
650          void *parent_baton,
651          const char *copyfrom_path,
652          svn_revnum_t copyfrom_revision,
653          apr_pool_t *pool,
654          void **baton)
655 {
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);
660
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,
664                                                      path,
665                                                      pool);
666
667   fb->edit_baton = eb;
668   fb->path = full_path;
669   fb->url = full_url;
670   fb->repos_root_url = eb->repos_root_url;
671   fb->pool = pool;
672
673   *baton = fb;
674   return SVN_NO_ERROR;
675 }
676
677
678 static svn_error_t *
679 window_handler(svn_txdelta_window_t *window, void *baton)
680 {
681   struct handler_baton *hb = baton;
682   svn_error_t *err;
683
684   err = hb->apply_handler(window, hb->apply_baton);
685   if (err)
686     {
687       /* We failed to apply the patch; clean up the temporary file.  */
688       err = svn_error_compose_create(
689                     err,
690                     svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
691     }
692
693   return svn_error_trace(err);
694 }
695
696
697
698 /* Write incoming data into the tmpfile stream */
699 static svn_error_t *
700 apply_textdelta(void *file_baton,
701                 const char *base_checksum,
702                 apr_pool_t *pool,
703                 svn_txdelta_window_handler_t *handler,
704                 void **handler_baton)
705 {
706   struct file_baton *fb = file_baton;
707   struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
708
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));
714
715   hb->pool = pool;
716   hb->tmppath = fb->tmppath;
717
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);
726
727   *handler_baton = hb;
728   *handler = window_handler;
729   return SVN_NO_ERROR;
730 }
731
732
733 static svn_error_t *
734 change_file_prop(void *file_baton,
735                  const char *name,
736                  const svn_string_t *value,
737                  apr_pool_t *pool)
738 {
739   struct file_baton *fb = file_baton;
740
741   if (! value)
742     return SVN_NO_ERROR;
743
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);
747
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);
751
752   else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
753     fb->executable_val = svn_string_dup(value, fb->pool);
754
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);
758
759   else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
760     SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
761
762   else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
763     fb->author = apr_pstrdup(fb->pool, value->data);
764
765   else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
766     fb->special = TRUE;
767
768   return SVN_NO_ERROR;
769 }
770
771
772 static svn_error_t *
773 change_dir_prop(void *dir_baton,
774                 const char *name,
775                 const svn_string_t *value,
776                 apr_pool_t *pool)
777 {
778   struct dir_baton *db = dir_baton;
779   struct edit_baton *eb = db->edit_baton;
780
781   if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
782     SVN_ERR(add_externals(eb->externals, db->path, value));
783
784   return SVN_NO_ERROR;
785 }
786
787
788 /* Move the tmpfile to file, and send feedback. */
789 static svn_error_t *
790 close_file(void *file_baton,
791            const char *text_digest,
792            apr_pool_t *pool)
793 {
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;
798
799   /* Was a txdelta even sent? */
800   if (! fb->tmppath)
801     return SVN_NO_ERROR;
802
803   SVN_ERR(svn_stream_close(fb->tmp_stream));
804
805   SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
806                                  pool));
807   actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
808
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));
816
817   if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
818     {
819       SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
820     }
821   else
822     {
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;
827
828       if (fb->eol_style_val)
829         {
830           SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
831                                 eb->native_eol));
832           repair = TRUE;
833         }
834
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,
839                                           fb->author, pool));
840
841       SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
842                                             eol, repair, final_kw,
843                                             TRUE, /* expand */
844                                             fb->special,
845                                             eb->cancel_func, eb->cancel_baton,
846                                             pool));
847
848       SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
849     }
850
851   if (fb->executable_val)
852     SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
853
854   if (fb->date && (! fb->special))
855     SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
856
857   if (fb->edit_baton->notify_func)
858     {
859       svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
860                                                      svn_wc_notify_update_add,
861                                                      pool);
862       notify->kind = svn_node_file;
863       (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
864                                      pool);
865     }
866
867   return SVN_NO_ERROR;
868 }
869
870 static svn_error_t *
871 fetch_props_func(apr_hash_t **props,
872                  void *baton,
873                  const char *path,
874                  svn_revnum_t base_revision,
875                  apr_pool_t *result_pool,
876                  apr_pool_t *scratch_pool)
877 {
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);
881
882   return SVN_NO_ERROR;
883 }
884
885 static svn_error_t *
886 fetch_base_func(const char **filename,
887                 void *baton,
888                 const char *path,
889                 svn_revnum_t base_revision,
890                 apr_pool_t *result_pool,
891                 apr_pool_t *scratch_pool)
892 {
893   /* An export always gets text against the empty stream (i.e, full texts). */
894   *filename = NULL;
895
896   return SVN_NO_ERROR;
897 }
898
899 static svn_error_t *
900 get_editor_ev1(const svn_delta_editor_t **export_editor,
901                void **edit_baton,
902                struct edit_baton *eb,
903                svn_client_ctx_t *ctx,
904                apr_pool_t *result_pool,
905                apr_pool_t *scratch_pool)
906 {
907   svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
908
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;
917
918   SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
919                                             ctx->cancel_baton,
920                                             editor,
921                                             eb,
922                                             export_editor,
923                                             edit_baton,
924                                             result_pool));
925
926   return SVN_NO_ERROR;
927 }
928
929 \f
930 /*** The Ev2 Implementation ***/
931
932 static svn_error_t *
933 add_file_ev2(void *baton,
934              const char *relpath,
935              const svn_checksum_t *checksum,
936              svn_stream_t *contents,
937              apr_hash_t *props,
938              svn_revnum_t replaces_rev,
939              apr_pool_t *scratch_pool)
940 {
941   struct edit_baton *eb = baton;
942   const char *full_path = svn_dirent_join(eb->root_path, relpath,
943                                           scratch_pool);
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,
947                                                      relpath,
948                                                      scratch_pool);
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;
958   apr_time_t date = 0;
959
960   /* Look at any properties for additional information. */
961   if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
962     eol_style_val = val;
963
964   if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
965     keywords_val = val;
966
967   if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
968     executable_val = val;
969
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;
973
974   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
975     SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
976
977   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
978     author = val->data;
979
980   if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
981     special = TRUE;
982
983   if (special)
984     {
985       svn_stream_t *tmp_stream;
986
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));
991     }
992   else
993     {
994       svn_stream_t *tmp_stream;
995       const char *tmppath;
996
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,
1001                                                         scratch_pool),
1002                                      svn_io_file_del_none,
1003                                      scratch_pool, scratch_pool));
1004
1005       /* Possibly wrap the stream to be translated, as dictated by
1006          the props. */
1007       if (eol_style_val || keywords_val)
1008         {
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;
1013
1014           if (eol_style_val)
1015             {
1016               SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
1017                                     eb->native_eol));
1018               repair = TRUE;
1019             }
1020
1021           if (keywords_val)
1022             SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
1023                                               revision, full_url,
1024                                               eb->repos_root_url,
1025                                               date, author, scratch_pool));
1026
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 */
1031                                                    scratch_pool);
1032         }
1033
1034       SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
1035                                eb->cancel_baton, scratch_pool));
1036
1037       /* Move the file into place. */
1038       SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
1039     }
1040
1041   if (executable_val)
1042     SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
1043
1044   if (date && (! special))
1045     SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
1046
1047   if (eb->notify_func)
1048     {
1049       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1050                                                      svn_wc_notify_update_add,
1051                                                      scratch_pool);
1052       notify->kind = svn_node_file;
1053       (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1054     }
1055
1056   return SVN_NO_ERROR;
1057 }
1058
1059 static svn_error_t *
1060 add_directory_ev2(void *baton,
1061                   const char *relpath,
1062                   const apr_array_header_t *children,
1063                   apr_hash_t *props,
1064                   svn_revnum_t replaces_rev,
1065                   apr_pool_t *scratch_pool)
1066 {
1067   struct edit_baton *eb = baton;
1068   svn_node_kind_t kind;
1069   const char *full_path = svn_dirent_join(eb->root_path, relpath,
1070                                           scratch_pool);
1071   svn_string_t *val;
1072
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));
1084
1085   if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
1086     SVN_ERR(add_externals(eb->externals, full_path, val));
1087
1088   if (eb->notify_func)
1089     {
1090       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1091                                                      svn_wc_notify_update_add,
1092                                                      scratch_pool);
1093       notify->kind = svn_node_dir;
1094       (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1095     }
1096
1097   return SVN_NO_ERROR;
1098 }
1099
1100 static svn_error_t *
1101 target_revision_func(void *baton,
1102                      svn_revnum_t target_revision,
1103                      apr_pool_t *scratch_pool)
1104 {
1105   struct edit_baton *eb = baton;
1106
1107   *eb->target_revision = target_revision;
1108
1109   return SVN_NO_ERROR;
1110 }
1111
1112 static svn_error_t *
1113 get_editor_ev2(const svn_delta_editor_t **export_editor,
1114                void **edit_baton,
1115                struct edit_baton *eb,
1116                svn_client_ctx_t *ctx,
1117                apr_pool_t *result_pool,
1118                apr_pool_t *scratch_pool)
1119 {
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));
1124
1125   exb->baton = eb;
1126   exb->target_revision = target_revision_func;
1127
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,
1131                                          scratch_pool));
1132   SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
1133
1134   *found_abs_paths = TRUE;
1135
1136   SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
1137                                        editor, NULL, NULL, found_abs_paths,
1138                                        NULL, NULL,
1139                                        fetch_props_func, eb,
1140                                        fetch_base_func, eb,
1141                                        exb, result_pool));
1142
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));
1146
1147   return SVN_NO_ERROR;
1148 }
1149
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)
1158 {
1159   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1160   apr_hash_t *props;
1161   svn_stream_t *tmp_stream;
1162   svn_node_kind_t to_kind;
1163
1164   if (svn_path_is_empty(to_path))
1165     {
1166       if (from_is_url)
1167         to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1168       else
1169         to_path = svn_dirent_basename(from_path_or_url, NULL);
1170       eb->root_path = to_path;
1171     }
1172   else
1173     {
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;
1177     }
1178
1179   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1180
1181   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1182       ! overwrite)
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));
1192
1193   tmp_stream = svn_stream_buffered(scratch_pool);
1194
1195   SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1196                           tmp_stream, NULL, &props, scratch_pool));
1197
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,
1201                        scratch_pool));
1202
1203   return SVN_NO_ERROR;
1204 }
1205
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)
1214 {
1215   apr_hash_t *props;
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);
1220
1221   if (svn_path_is_empty(to_path))
1222     {
1223       if (from_is_url)
1224         to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1225       else
1226         to_path = svn_dirent_basename(from_path_or_url, NULL);
1227       eb->root_path = to_path;
1228     }
1229   else
1230     {
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;
1234     }
1235
1236   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1237
1238   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1239       ! overwrite)
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));
1249
1250   /* Since you cannot actually root an editor at a file, we
1251    * manually drive a few functions of our editor. */
1252
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;
1259
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));
1265
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,
1270                           fb->tmp_stream,
1271                           NULL, &props, scratch_pool));
1272
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))
1276     {
1277       const char *propname = svn__apr_hash_index_key(hi);
1278       const svn_string_t *propval = svn__apr_hash_index_val(hi);
1279
1280       SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1281     }
1282
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));
1286
1287   return SVN_NO_ERROR;
1288 }
1289
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,
1299                  svn_depth_t depth,
1300                  const char *native_eol,
1301                  svn_client_ctx_t *ctx,
1302                  apr_pool_t *scratch_pool)
1303 {
1304   void *edit_baton;
1305   const svn_delta_editor_t *export_editor;
1306   const svn_ra_reporter3_t *reporter;
1307   void *report_baton;
1308   svn_node_kind_t kind;
1309
1310   if (!ENABLE_EV2_IMPL)
1311     SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1312                            scratch_pool, scratch_pool));
1313   else
1314     SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1315                            scratch_pool, scratch_pool));
1316
1317   /* Manufacture a basic 'report' to the update reporter. */
1318   SVN_ERR(svn_ra_do_update3(ra_session,
1319                             &reporter, &report_baton,
1320                             loc->rev,
1321                             "", /* no sub-target */
1322                             depth,
1323                             FALSE, /* don't want copyfrom-args */
1324                             FALSE, /* don't want ignore_ancestry */
1325                             export_editor, edit_baton,
1326                             scratch_pool, scratch_pool));
1327
1328   SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1329                              /* Depth is irrelevant, as we're
1330                                 passing start_empty=TRUE anyway. */
1331                              svn_depth_infinity,
1332                              TRUE, /* "help, my dir is empty!" */
1333                              NULL, scratch_pool));
1334
1335   SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1336
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).
1342    *
1343    * So we just create the empty dir manually; but we do it via
1344    * open_root_internal(), in order to get proper notification.
1345    */
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));
1351
1352   if (! ignore_externals && depth == svn_depth_infinity)
1353     {
1354       const char *to_abspath;
1355
1356       SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1357       SVN_ERR(svn_client__export_externals(eb->externals,
1358                                            from_path_or_url,
1359                                            to_abspath, eb->repos_root_url,
1360                                            depth, native_eol,
1361                                            ignore_keywords,
1362                                            ctx, scratch_pool));
1363     }
1364
1365   return SVN_NO_ERROR;
1366 }
1367
1368
1369 \f
1370 /*** Public Interfaces ***/
1371
1372 svn_error_t *
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,
1381                    svn_depth_t depth,
1382                    const char *native_eol,
1383                    svn_client_ctx_t *ctx,
1384                    apr_pool_t *pool)
1385 {
1386   svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1387   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1388
1389   SVN_ERR_ASSERT(peg_revision != NULL);
1390   SVN_ERR_ASSERT(revision != NULL);
1391
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);
1395
1396   peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1397                                                         from_path_or_url);
1398   revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1399
1400   if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1401     {
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));
1406
1407       /* Get the RA connection. */
1408       SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1409                                                 from_path_or_url, NULL,
1410                                                 peg_revision,
1411                                                 revision, ctx, pool));
1412
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;
1425
1426       SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1427
1428       if (kind == svn_node_file)
1429         {
1430           if (!ENABLE_EV2_IMPL)
1431             SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
1432                                 overwrite, pool));
1433           else
1434             SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
1435                                     ra_session, overwrite, pool));
1436         }
1437       else if (kind == svn_node_dir)
1438         {
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));
1443         }
1444       else if (kind == svn_node_none)
1445         {
1446           return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1447                                    _("URL '%s' doesn't exist"),
1448                                    from_path_or_url);
1449         }
1450       /* kind == svn_node_unknown not handled */
1451     }
1452   else
1453     {
1454       struct export_info_baton eib;
1455       svn_node_kind_t kind;
1456       apr_hash_t *externals = NULL;
1457
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,
1461                                       pool));
1462
1463       SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1464
1465       SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1466
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:
1472        *
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        * -----------+---------------------------------------------------------
1481        *
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.
1488        *
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].
1493        *
1494        * [3] If the 'FORCE' flag is true then overwrite the destination file
1495        *     else error out.
1496        *
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.
1500        */
1501       if (kind == svn_node_file)
1502         SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1503                                        pool));
1504
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;
1515
1516       SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1517                                  TRUE /* get_all */,
1518                                  TRUE /* no_ignore */,
1519                                  FALSE /* ignore_text_mods */,
1520                                  NULL,
1521                                  export_node, &eib,
1522                                  ctx->cancel_func, ctx->cancel_baton,
1523                                  pool));
1524
1525       if (!eib.exported)
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,
1529                                                         pool));
1530
1531       if (!ignore_externals)
1532         SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1533                                                 from_path_or_url,
1534                                                 pool, pool));
1535
1536       if (externals && apr_hash_count(externals))
1537         {
1538           apr_pool_t *iterpool = svn_pool_create(pool);
1539           apr_hash_index_t *hi;
1540
1541           for (hi = apr_hash_first(pool, externals);
1542                hi;
1543                hi = apr_hash_next(hi))
1544             {
1545               const char *external_abspath = svn__apr_hash_index_key(hi);
1546               const char *relpath;
1547               const char *target_abspath;
1548
1549               svn_pool_clear(iterpool);
1550
1551               relpath = svn_dirent_skip_ancestor(from_path_or_url,
1552                                                  external_abspath);
1553
1554               target_abspath = svn_dirent_join(to_path, relpath,
1555                                                          iterpool);
1556
1557               /* Ensure that the parent directory exists */
1558               SVN_ERR(svn_io_make_dir_recursively(
1559                             svn_dirent_dirname(target_abspath, iterpool),
1560                             iterpool));
1561
1562               SVN_ERR(svn_client_export5(NULL,
1563                                          svn_dirent_join(from_path_or_url,
1564                                                          relpath,
1565                                                          iterpool),
1566                                          target_abspath,
1567                                          peg_revision, revision,
1568                                          TRUE, ignore_externals,
1569                                          ignore_keywords, depth, native_eol,
1570                                          ctx, iterpool));
1571             }
1572
1573           svn_pool_destroy(iterpool);
1574         }
1575     }
1576
1577
1578   if (ctx->notify_func2)
1579     {
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);
1585     }
1586
1587   if (result_rev)
1588     *result_rev = edit_revision;
1589
1590   return SVN_NO_ERROR;
1591 }
1592