]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/export.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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   if (status->file_external)
271     return SVN_NO_ERROR;
272
273   /* Produce overwrite errors for the export root */
274   if (strcmp(local_abspath, eib->origin_abspath) == 0)
275     {
276       svn_node_kind_t to_kind;
277
278       SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
279
280       if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
281           && !eib->overwrite)
282         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
283                                  _("Destination file '%s' exists, and "
284                                    "will not be overwritten unless forced"),
285                                  svn_dirent_local_style(to_abspath,
286                                                         scratch_pool));
287       else if (to_kind == svn_node_dir)
288         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
289                                  _("Destination '%s' exists. Cannot "
290                                    "overwrite directory with non-directory"),
291                                  svn_dirent_local_style(to_abspath,
292                                                         scratch_pool));
293     }
294
295   if (eib->revision->kind != svn_opt_revision_working)
296     {
297       /* Only export 'added' files when the revision is WORKING. This is not
298          WORKING, so skip the 'added' files, since they didn't exist
299          in the BASE revision and don't have an associated text-base.
300
301          'replaced' files are technically the same as 'added' files.
302          ### TODO: Handle replaced nodes properly.
303          ###       svn_opt_revision_base refers to the "new"
304          ###       base of the node. That means, if a node is locally
305          ###       replaced, export skips this node, as if it was locally
306          ###       added, because svn_opt_revision_base refers to the base
307          ###       of the added node, not to the node that was deleted.
308          ###       In contrast, when the node is copied-here or moved-here,
309          ###       the copy/move source's content will be exported.
310          ###       It is currently not possible to export the revert-base
311          ###       when a node is locally replaced. We need a new
312          ###       svn_opt_revision_ enum value for proper distinction
313          ###       between revert-base and commit-base.
314
315          Copied-/moved-here nodes have a base, so export both added and
316          replaced files when they involve a copy-/move-here.
317
318          We get all this for free from evaluating SOURCE == NULL:
319        */
320       SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
321                                             scratch_pool, scratch_pool));
322       if (source == NULL)
323         return SVN_NO_ERROR;
324
325       SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
326                                         scratch_pool, scratch_pool));
327     }
328   else
329     {
330       /* ### hmm. this isn't always a specialfile. this will simply open
331          ### the file readonly if it is a regular file. */
332       SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool,
333                                          scratch_pool));
334
335       SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
336                                 scratch_pool));
337       if (status->node_status != svn_wc_status_normal)
338         local_mod = TRUE;
339     }
340
341   /* We can early-exit if we're creating a special file. */
342   special = svn_hash_gets(props, SVN_PROP_SPECIAL);
343   if (special != NULL)
344     {
345       /* Create the destination as a special file, and copy the source
346          details into the destination stream. */
347       /* ### And forget the notification */
348       SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath,
349                                            scratch_pool, scratch_pool));
350       return svn_error_trace(
351         svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool));
352     }
353
354
355   eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
356   keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
357   executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE);
358
359   if (eol_style)
360     SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
361
362   if (local_mod)
363     {
364       /* Use the modified time from the working copy of
365          the file */
366       SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
367     }
368   else
369     {
370       tm = status->changed_date;
371     }
372
373   if (keywords)
374     {
375       svn_revnum_t changed_rev = status->changed_rev;
376       const char *suffix;
377       const char *url = svn_path_url_add_component2(status->repos_root_url,
378                                                     status->repos_relpath,
379                                                     scratch_pool);
380       const char *author = status->changed_author;
381       if (local_mod)
382         {
383           /* For locally modified files, we'll append an 'M'
384              to the revision number, and set the author to
385              "(local)" since we can't always determine the
386              current user's username */
387           suffix = "M";
388           author = _("(local)");
389         }
390       else
391         {
392           suffix = "";
393         }
394
395       SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
396                                         apr_psprintf(scratch_pool, "%ld%s",
397                                                      changed_rev, suffix),
398                                         url, status->repos_root_url, tm,
399                                         author, scratch_pool));
400     }
401
402   /* For atomicity, we translate to a tmp file and then rename the tmp file
403      over the real destination. */
404   SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
405                                  svn_dirent_dirname(to_abspath, scratch_pool),
406                                  svn_io_file_del_none, scratch_pool,
407                                  scratch_pool));
408
409   /* If some translation is needed, then wrap the output stream (this is
410      more efficient than wrapping the input). */
411   if (eol || (kw && (apr_hash_count(kw) > 0)))
412     dst_stream = svn_subst_stream_translated(dst_stream,
413                                              eol,
414                                              FALSE /* repair */,
415                                              kw,
416                                              ! eib->ignore_keywords /* expand */,
417                                              scratch_pool);
418
419   /* ###: use cancel func/baton in place of NULL/NULL below. */
420   err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
421
422   if (!err && executable)
423     err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
424
425   if (!err)
426     err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
427
428   if (err)
429     return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
430                                                              scratch_pool));
431
432   /* Now that dst_tmp contains the translated data, do the atomic rename. */
433   SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool));
434
435   if (eib->notify_func)
436     {
437       svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath,
438                                       svn_wc_notify_update_add, scratch_pool);
439       notify->kind = svn_node_file;
440       (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
441     }
442
443   return SVN_NO_ERROR;
444 }
445
446 /* Abstraction of open_root.
447  *
448  * Create PATH if it does not exist and is not obstructed, and invoke
449  * NOTIFY_FUNC with NOTIFY_BATON on PATH.
450  *
451  * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
452  *
453  * If PATH is a already a directory, then error with
454  * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
455  * export into PATH with no error.
456  */
457 static svn_error_t *
458 open_root_internal(const char *path,
459                    svn_boolean_t force,
460                    svn_wc_notify_func2_t notify_func,
461                    void *notify_baton,
462                    apr_pool_t *pool)
463 {
464   svn_node_kind_t kind;
465
466   SVN_ERR(svn_io_check_path(path, &kind, pool));
467   if (kind == svn_node_none)
468     SVN_ERR(svn_io_make_dir_recursively(path, pool));
469   else if (kind == svn_node_file)
470     return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
471                              _("'%s' exists and is not a directory"),
472                              svn_dirent_local_style(path, pool));
473   else if ((kind != svn_node_dir) || (! force))
474     return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
475                              _("'%s' already exists"),
476                              svn_dirent_local_style(path, pool));
477
478   if (notify_func)
479     {
480       svn_wc_notify_t *notify = svn_wc_create_notify(path,
481                                                      svn_wc_notify_update_add,
482                                                      pool);
483       notify->kind = svn_node_dir;
484       (*notify_func)(notify_baton, notify, pool);
485     }
486
487   return SVN_NO_ERROR;
488 }
489
490
491 /* ---------------------------------------------------------------------- */
492
493 \f
494 /*** A dedicated 'export' editor, which does no .svn/ accounting.  ***/
495
496
497 struct edit_baton
498 {
499   const char *repos_root_url;
500   const char *root_path;
501   const char *root_url;
502   svn_boolean_t force;
503   svn_revnum_t *target_revision;
504   apr_hash_t *externals;
505   const char *native_eol;
506   svn_boolean_t ignore_keywords;
507
508   svn_cancel_func_t cancel_func;
509   void *cancel_baton;
510   svn_wc_notify_func2_t notify_func;
511   void *notify_baton;
512 };
513
514
515 struct dir_baton
516 {
517   struct edit_baton *edit_baton;
518   const char *path;
519 };
520
521
522 struct file_baton
523 {
524   struct edit_baton *edit_baton;
525
526   const char *path;
527   const char *tmppath;
528
529   /* We need to keep this around so we can explicitly close it in close_file,
530      thus flushing its output to disk so we can copy and translate it. */
531   svn_stream_t *tmp_stream;
532
533   /* The MD5 digest of the file's fulltext.  This is all zeros until
534      the last textdelta window handler call returns. */
535   unsigned char text_digest[APR_MD5_DIGESTSIZE];
536
537   /* The three svn: properties we might actually care about. */
538   const svn_string_t *eol_style_val;
539   const svn_string_t *keywords_val;
540   const svn_string_t *executable_val;
541   svn_boolean_t special;
542
543   /* Any keyword vals to be substituted */
544   const char *revision;
545   const char *url;
546   const char *repos_root_url;
547   const char *author;
548   apr_time_t date;
549
550   /* Pool associated with this baton. */
551   apr_pool_t *pool;
552 };
553
554
555 struct handler_baton
556 {
557   svn_txdelta_window_handler_t apply_handler;
558   void *apply_baton;
559   apr_pool_t *pool;
560   const char *tmppath;
561 };
562
563
564 static svn_error_t *
565 set_target_revision(void *edit_baton,
566                     svn_revnum_t target_revision,
567                     apr_pool_t *pool)
568 {
569   struct edit_baton *eb = edit_baton;
570
571   /* Stashing a target_revision in the baton */
572   *(eb->target_revision) = target_revision;
573   return SVN_NO_ERROR;
574 }
575
576
577
578 /* Just ensure that the main export directory exists. */
579 static svn_error_t *
580 open_root(void *edit_baton,
581           svn_revnum_t base_revision,
582           apr_pool_t *pool,
583           void **root_baton)
584 {
585   struct edit_baton *eb = edit_baton;
586   struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
587
588   SVN_ERR(open_root_internal(eb->root_path, eb->force,
589                              eb->notify_func, eb->notify_baton, pool));
590
591   /* Build our dir baton. */
592   db->path = eb->root_path;
593   db->edit_baton = eb;
594   *root_baton = db;
595
596   return SVN_NO_ERROR;
597 }
598
599
600 /* Ensure the directory exists, and send feedback. */
601 static svn_error_t *
602 add_directory(const char *path,
603               void *parent_baton,
604               const char *copyfrom_path,
605               svn_revnum_t copyfrom_revision,
606               apr_pool_t *pool,
607               void **baton)
608 {
609   struct dir_baton *pb = parent_baton;
610   struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
611   struct edit_baton *eb = pb->edit_baton;
612   const char *full_path = svn_dirent_join(eb->root_path, path, pool);
613   svn_node_kind_t kind;
614
615   SVN_ERR(svn_io_check_path(full_path, &kind, pool));
616   if (kind == svn_node_none)
617     SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
618   else if (kind == svn_node_file)
619     return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
620                              _("'%s' exists and is not a directory"),
621                              svn_dirent_local_style(full_path, pool));
622   else if (! (kind == svn_node_dir && eb->force))
623     return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
624                              _("'%s' already exists"),
625                              svn_dirent_local_style(full_path, pool));
626
627   if (eb->notify_func)
628     {
629       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
630                                                      svn_wc_notify_update_add,
631                                                      pool);
632       notify->kind = svn_node_dir;
633       (*eb->notify_func)(eb->notify_baton, notify, pool);
634     }
635
636   /* Build our dir baton. */
637   db->path = full_path;
638   db->edit_baton = eb;
639   *baton = db;
640
641   return SVN_NO_ERROR;
642 }
643
644
645 /* Build a file baton. */
646 static svn_error_t *
647 add_file(const char *path,
648          void *parent_baton,
649          const char *copyfrom_path,
650          svn_revnum_t copyfrom_revision,
651          apr_pool_t *pool,
652          void **baton)
653 {
654   struct dir_baton *pb = parent_baton;
655   struct edit_baton *eb = pb->edit_baton;
656   struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
657   const char *full_path = svn_dirent_join(eb->root_path, path, pool);
658
659   /* PATH is not canonicalized, i.e. it may still contain spaces etc.
660    * but EB->root_url is. */
661   const char *full_url = svn_path_url_add_component2(eb->root_url,
662                                                      path,
663                                                      pool);
664
665   fb->edit_baton = eb;
666   fb->path = full_path;
667   fb->url = full_url;
668   fb->repos_root_url = eb->repos_root_url;
669   fb->pool = pool;
670
671   *baton = fb;
672   return SVN_NO_ERROR;
673 }
674
675
676 static svn_error_t *
677 window_handler(svn_txdelta_window_t *window, void *baton)
678 {
679   struct handler_baton *hb = baton;
680   svn_error_t *err;
681
682   err = hb->apply_handler(window, hb->apply_baton);
683   if (err)
684     {
685       /* We failed to apply the patch; clean up the temporary file.  */
686       err = svn_error_compose_create(
687                     err,
688                     svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
689     }
690
691   return svn_error_trace(err);
692 }
693
694
695
696 /* Write incoming data into the tmpfile stream */
697 static svn_error_t *
698 apply_textdelta(void *file_baton,
699                 const char *base_checksum,
700                 apr_pool_t *pool,
701                 svn_txdelta_window_handler_t *handler,
702                 void **handler_baton)
703 {
704   struct file_baton *fb = file_baton;
705   struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
706
707   /* Create a temporary file in the same directory as the file. We're going
708      to rename the thing into place when we're done. */
709   SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
710                                  svn_dirent_dirname(fb->path, pool),
711                                  svn_io_file_del_none, fb->pool, fb->pool));
712
713   hb->pool = pool;
714   hb->tmppath = fb->tmppath;
715
716   /* svn_txdelta_apply() closes the stream, but we want to close it in the
717      close_file() function, so disown it here. */
718   /* ### contrast to when we call svn_ra_get_file() which does NOT close the
719      ### tmp_stream. we *should* be much more consistent! */
720   svn_txdelta_apply(svn_stream_empty(pool),
721                     svn_stream_disown(fb->tmp_stream, pool),
722                     fb->text_digest, NULL, pool,
723                     &hb->apply_handler, &hb->apply_baton);
724
725   *handler_baton = hb;
726   *handler = window_handler;
727   return SVN_NO_ERROR;
728 }
729
730
731 static svn_error_t *
732 change_file_prop(void *file_baton,
733                  const char *name,
734                  const svn_string_t *value,
735                  apr_pool_t *pool)
736 {
737   struct file_baton *fb = file_baton;
738
739   if (! value)
740     return SVN_NO_ERROR;
741
742   /* Store only the magic three properties. */
743   if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
744     fb->eol_style_val = svn_string_dup(value, fb->pool);
745
746   else if (! fb->edit_baton->ignore_keywords &&
747            strcmp(name, SVN_PROP_KEYWORDS) == 0)
748     fb->keywords_val = svn_string_dup(value, fb->pool);
749
750   else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
751     fb->executable_val = svn_string_dup(value, fb->pool);
752
753   /* Try to fill out the baton's keywords-structure too. */
754   else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
755     fb->revision = apr_pstrdup(fb->pool, value->data);
756
757   else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
758     SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
759
760   else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
761     fb->author = apr_pstrdup(fb->pool, value->data);
762
763   else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
764     fb->special = TRUE;
765
766   return SVN_NO_ERROR;
767 }
768
769
770 static svn_error_t *
771 change_dir_prop(void *dir_baton,
772                 const char *name,
773                 const svn_string_t *value,
774                 apr_pool_t *pool)
775 {
776   struct dir_baton *db = dir_baton;
777   struct edit_baton *eb = db->edit_baton;
778
779   if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
780     SVN_ERR(add_externals(eb->externals, db->path, value));
781
782   return SVN_NO_ERROR;
783 }
784
785
786 /* Move the tmpfile to file, and send feedback. */
787 static svn_error_t *
788 close_file(void *file_baton,
789            const char *text_digest,
790            apr_pool_t *pool)
791 {
792   struct file_baton *fb = file_baton;
793   struct edit_baton *eb = fb->edit_baton;
794   svn_checksum_t *text_checksum;
795   svn_checksum_t *actual_checksum;
796
797   /* Was a txdelta even sent? */
798   if (! fb->tmppath)
799     return SVN_NO_ERROR;
800
801   SVN_ERR(svn_stream_close(fb->tmp_stream));
802
803   SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
804                                  pool));
805   actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
806
807   /* Note that text_digest can be NULL when talking to certain repositories.
808      In that case text_checksum will be NULL and the following match code
809      will note that the checksums match */
810   if (!svn_checksum_match(text_checksum, actual_checksum))
811     return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool,
812                                      _("Checksum mismatch for '%s'"),
813                                      svn_dirent_local_style(fb->path, pool));
814
815   if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
816     {
817       SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
818     }
819   else
820     {
821       svn_subst_eol_style_t style;
822       const char *eol = NULL;
823       svn_boolean_t repair = FALSE;
824       apr_hash_t *final_kw = NULL;
825
826       if (fb->eol_style_val)
827         {
828           SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
829                                 eb->native_eol));
830           repair = TRUE;
831         }
832
833       if (fb->keywords_val)
834         SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
835                                           fb->revision, fb->url,
836                                           fb->repos_root_url, fb->date,
837                                           fb->author, pool));
838
839       SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
840                                             eol, repair, final_kw,
841                                             TRUE, /* expand */
842                                             fb->special,
843                                             eb->cancel_func, eb->cancel_baton,
844                                             pool));
845
846       SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
847     }
848
849   if (fb->executable_val)
850     SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
851
852   if (fb->date && (! fb->special))
853     SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
854
855   if (fb->edit_baton->notify_func)
856     {
857       svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
858                                                      svn_wc_notify_update_add,
859                                                      pool);
860       notify->kind = svn_node_file;
861       (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
862                                      pool);
863     }
864
865   return SVN_NO_ERROR;
866 }
867
868 static svn_error_t *
869 fetch_props_func(apr_hash_t **props,
870                  void *baton,
871                  const char *path,
872                  svn_revnum_t base_revision,
873                  apr_pool_t *result_pool,
874                  apr_pool_t *scratch_pool)
875 {
876   /* Always use empty props, since the node won't have pre-existing props
877      (This is an export, remember?) */
878   *props = apr_hash_make(result_pool);
879
880   return SVN_NO_ERROR;
881 }
882
883 static svn_error_t *
884 fetch_base_func(const char **filename,
885                 void *baton,
886                 const char *path,
887                 svn_revnum_t base_revision,
888                 apr_pool_t *result_pool,
889                 apr_pool_t *scratch_pool)
890 {
891   /* An export always gets text against the empty stream (i.e, full texts). */
892   *filename = NULL;
893
894   return SVN_NO_ERROR;
895 }
896
897 static svn_error_t *
898 get_editor_ev1(const svn_delta_editor_t **export_editor,
899                void **edit_baton,
900                struct edit_baton *eb,
901                svn_client_ctx_t *ctx,
902                apr_pool_t *result_pool,
903                apr_pool_t *scratch_pool)
904 {
905   svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
906
907   editor->set_target_revision = set_target_revision;
908   editor->open_root = open_root;
909   editor->add_directory = add_directory;
910   editor->add_file = add_file;
911   editor->apply_textdelta = apply_textdelta;
912   editor->close_file = close_file;
913   editor->change_file_prop = change_file_prop;
914   editor->change_dir_prop = change_dir_prop;
915
916   SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
917                                             ctx->cancel_baton,
918                                             editor,
919                                             eb,
920                                             export_editor,
921                                             edit_baton,
922                                             result_pool));
923
924   return SVN_NO_ERROR;
925 }
926
927 \f
928 /*** The Ev2 Implementation ***/
929
930 static svn_error_t *
931 add_file_ev2(void *baton,
932              const char *relpath,
933              const svn_checksum_t *checksum,
934              svn_stream_t *contents,
935              apr_hash_t *props,
936              svn_revnum_t replaces_rev,
937              apr_pool_t *scratch_pool)
938 {
939   struct edit_baton *eb = baton;
940   const char *full_path = svn_dirent_join(eb->root_path, relpath,
941                                           scratch_pool);
942   /* RELPATH is not canonicalized, i.e. it may still contain spaces etc.
943    * but EB->root_url is. */
944   const char *full_url = svn_path_url_add_component2(eb->root_url,
945                                                      relpath,
946                                                      scratch_pool);
947   const svn_string_t *val;
948   /* The four svn: properties we might actually care about. */
949   const svn_string_t *eol_style_val = NULL;
950   const svn_string_t *keywords_val = NULL;
951   const svn_string_t *executable_val = NULL;
952   svn_boolean_t special = FALSE;
953   /* Any keyword vals to be substituted */
954   const char *revision = NULL;
955   const char *author = NULL;
956   apr_time_t date = 0;
957
958   /* Look at any properties for additional information. */
959   if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
960     eol_style_val = val;
961
962   if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
963     keywords_val = val;
964
965   if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
966     executable_val = val;
967
968   /* Try to fill out the baton's keywords-structure too. */
969   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
970     revision = val->data;
971
972   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
973     SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
974
975   if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
976     author = val->data;
977
978   if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
979     special = TRUE;
980
981   if (special)
982     {
983       svn_stream_t *tmp_stream;
984
985       SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path,
986                                            scratch_pool, scratch_pool));
987       SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
988                                eb->cancel_baton, scratch_pool));
989     }
990   else
991     {
992       svn_stream_t *tmp_stream;
993       const char *tmppath;
994
995       /* Create a temporary file in the same directory as the file. We're going
996          to rename the thing into place when we're done. */
997       SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath,
998                                      svn_dirent_dirname(full_path,
999                                                         scratch_pool),
1000                                      svn_io_file_del_none,
1001                                      scratch_pool, scratch_pool));
1002
1003       /* Possibly wrap the stream to be translated, as dictated by
1004          the props. */
1005       if (eol_style_val || keywords_val)
1006         {
1007           svn_subst_eol_style_t style;
1008           const char *eol = NULL;
1009           svn_boolean_t repair = FALSE;
1010           apr_hash_t *final_kw = NULL;
1011
1012           if (eol_style_val)
1013             {
1014               SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
1015                                     eb->native_eol));
1016               repair = TRUE;
1017             }
1018
1019           if (keywords_val)
1020             SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
1021                                               revision, full_url,
1022                                               eb->repos_root_url,
1023                                               date, author, scratch_pool));
1024
1025           /* Writing through a translated stream is more efficient than
1026              reading through one, so we wrap TMP_STREAM and not CONTENTS. */
1027           tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair,
1028                                                    final_kw, TRUE, /* expand */
1029                                                    scratch_pool);
1030         }
1031
1032       SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
1033                                eb->cancel_baton, scratch_pool));
1034
1035       /* Move the file into place. */
1036       SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
1037     }
1038
1039   if (executable_val)
1040     SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
1041
1042   if (date && (! special))
1043     SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
1044
1045   if (eb->notify_func)
1046     {
1047       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1048                                                      svn_wc_notify_update_add,
1049                                                      scratch_pool);
1050       notify->kind = svn_node_file;
1051       (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1052     }
1053
1054   return SVN_NO_ERROR;
1055 }
1056
1057 static svn_error_t *
1058 add_directory_ev2(void *baton,
1059                   const char *relpath,
1060                   const apr_array_header_t *children,
1061                   apr_hash_t *props,
1062                   svn_revnum_t replaces_rev,
1063                   apr_pool_t *scratch_pool)
1064 {
1065   struct edit_baton *eb = baton;
1066   svn_node_kind_t kind;
1067   const char *full_path = svn_dirent_join(eb->root_path, relpath,
1068                                           scratch_pool);
1069   svn_string_t *val;
1070
1071   SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool));
1072   if (kind == svn_node_none)
1073     SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool));
1074   else if (kind == svn_node_file)
1075     return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1076                              _("'%s' exists and is not a directory"),
1077                              svn_dirent_local_style(full_path, scratch_pool));
1078   else if (! (kind == svn_node_dir && eb->force))
1079     return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1080                              _("'%s' already exists"),
1081                              svn_dirent_local_style(full_path, scratch_pool));
1082
1083   if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
1084     SVN_ERR(add_externals(eb->externals, full_path, val));
1085
1086   if (eb->notify_func)
1087     {
1088       svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1089                                                      svn_wc_notify_update_add,
1090                                                      scratch_pool);
1091       notify->kind = svn_node_dir;
1092       (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1093     }
1094
1095   return SVN_NO_ERROR;
1096 }
1097
1098 static svn_error_t *
1099 target_revision_func(void *baton,
1100                      svn_revnum_t target_revision,
1101                      apr_pool_t *scratch_pool)
1102 {
1103   struct edit_baton *eb = baton;
1104
1105   *eb->target_revision = target_revision;
1106
1107   return SVN_NO_ERROR;
1108 }
1109
1110 static svn_error_t *
1111 get_editor_ev2(const svn_delta_editor_t **export_editor,
1112                void **edit_baton,
1113                struct edit_baton *eb,
1114                svn_client_ctx_t *ctx,
1115                apr_pool_t *result_pool,
1116                apr_pool_t *scratch_pool)
1117 {
1118   svn_editor_t *editor;
1119   struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb));
1120   svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
1121                                               sizeof(*found_abs_paths));
1122
1123   exb->baton = eb;
1124   exb->target_revision = target_revision_func;
1125
1126   SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton,
1127                             result_pool, scratch_pool));
1128   SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2,
1129                                          scratch_pool));
1130   SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
1131
1132   *found_abs_paths = TRUE;
1133
1134   SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
1135                                        editor, NULL, NULL, found_abs_paths,
1136                                        NULL, NULL,
1137                                        fetch_props_func, eb,
1138                                        fetch_base_func, eb,
1139                                        exb, result_pool));
1140
1141   /* Create the root of the export. */
1142   SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
1143                              eb->notify_baton, scratch_pool));
1144
1145   return SVN_NO_ERROR;
1146 }
1147
1148 static svn_error_t *
1149 export_file_ev2(const char *from_path_or_url,
1150                 const char *to_path,
1151                 struct edit_baton *eb,
1152                 svn_client__pathrev_t *loc,
1153                 svn_ra_session_t *ra_session,
1154                 svn_boolean_t overwrite,
1155                 apr_pool_t *scratch_pool)
1156 {
1157   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1158   apr_hash_t *props;
1159   svn_stream_t *tmp_stream;
1160   svn_node_kind_t to_kind;
1161
1162   if (svn_path_is_empty(to_path))
1163     {
1164       if (from_is_url)
1165         to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1166       else
1167         to_path = svn_dirent_basename(from_path_or_url, NULL);
1168       eb->root_path = to_path;
1169     }
1170   else
1171     {
1172       SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1173                                      from_is_url, scratch_pool));
1174       eb->root_path = to_path;
1175     }
1176
1177   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1178
1179   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1180       ! overwrite)
1181     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1182                              _("Destination file '%s' exists, and "
1183                                "will not be overwritten unless forced"),
1184                              svn_dirent_local_style(to_path, scratch_pool));
1185   else if (to_kind == svn_node_dir)
1186     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1187                              _("Destination '%s' exists. Cannot "
1188                                "overwrite directory with non-directory"),
1189                              svn_dirent_local_style(to_path, scratch_pool));
1190
1191   tmp_stream = svn_stream_buffered(scratch_pool);
1192
1193   SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1194                           tmp_stream, NULL, &props, scratch_pool));
1195
1196   /* Since you cannot actually root an editor at a file, we manually drive
1197    * a function of our editor. */
1198   SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
1199                        scratch_pool));
1200
1201   return SVN_NO_ERROR;
1202 }
1203
1204 static svn_error_t *
1205 export_file(const char *from_path_or_url,
1206             const char *to_path,
1207             struct edit_baton *eb,
1208             svn_client__pathrev_t *loc,
1209             svn_ra_session_t *ra_session,
1210             svn_boolean_t overwrite,
1211             apr_pool_t *scratch_pool)
1212 {
1213   apr_hash_t *props;
1214   apr_hash_index_t *hi;
1215   struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
1216   svn_node_kind_t to_kind;
1217   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1218
1219   if (svn_path_is_empty(to_path))
1220     {
1221       if (from_is_url)
1222         to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1223       else
1224         to_path = svn_dirent_basename(from_path_or_url, NULL);
1225       eb->root_path = to_path;
1226     }
1227   else
1228     {
1229       SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1230                                      from_is_url, scratch_pool));
1231       eb->root_path = to_path;
1232     }
1233
1234   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1235
1236   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1237       ! overwrite)
1238     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1239                              _("Destination file '%s' exists, and "
1240                                "will not be overwritten unless forced"),
1241                              svn_dirent_local_style(to_path, scratch_pool));
1242   else if (to_kind == svn_node_dir)
1243     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1244                              _("Destination '%s' exists. Cannot "
1245                                "overwrite directory with non-directory"),
1246                              svn_dirent_local_style(to_path, scratch_pool));
1247
1248   /* Since you cannot actually root an editor at a file, we
1249    * manually drive a few functions of our editor. */
1250
1251   /* This is the equivalent of a parentless add_file(). */
1252   fb->edit_baton = eb;
1253   fb->path = eb->root_path;
1254   fb->url = eb->root_url;
1255   fb->pool = scratch_pool;
1256   fb->repos_root_url = eb->repos_root_url;
1257
1258   /* Copied from apply_textdelta(). */
1259   SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
1260                                  svn_dirent_dirname(fb->path, scratch_pool),
1261                                  svn_io_file_del_none,
1262                                  fb->pool, fb->pool));
1263
1264   /* Step outside the editor-likeness for a moment, to actually talk
1265    * to the repository. */
1266   /* ### note: the stream will not be closed */
1267   SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1268                           fb->tmp_stream,
1269                           NULL, &props, scratch_pool));
1270
1271   /* Push the props into change_file_prop(), to update the file_baton
1272    * with information. */
1273   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
1274     {
1275       const char *propname = svn__apr_hash_index_key(hi);
1276       const svn_string_t *propval = svn__apr_hash_index_val(hi);
1277
1278       SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1279     }
1280
1281   /* And now just use close_file() to do all the keyword and EOL
1282    * work, and put the file into place. */
1283   SVN_ERR(close_file(fb, NULL, scratch_pool));
1284
1285   return SVN_NO_ERROR;
1286 }
1287
1288 static svn_error_t *
1289 export_directory(const char *from_path_or_url,
1290                  const char *to_path,
1291                  struct edit_baton *eb,
1292                  svn_client__pathrev_t *loc,
1293                  svn_ra_session_t *ra_session,
1294                  svn_boolean_t overwrite,
1295                  svn_boolean_t ignore_externals,
1296                  svn_boolean_t ignore_keywords,
1297                  svn_depth_t depth,
1298                  const char *native_eol,
1299                  svn_client_ctx_t *ctx,
1300                  apr_pool_t *scratch_pool)
1301 {
1302   void *edit_baton;
1303   const svn_delta_editor_t *export_editor;
1304   const svn_ra_reporter3_t *reporter;
1305   void *report_baton;
1306   svn_node_kind_t kind;
1307
1308   if (!ENABLE_EV2_IMPL)
1309     SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1310                            scratch_pool, scratch_pool));
1311   else
1312     SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1313                            scratch_pool, scratch_pool));
1314
1315   /* Manufacture a basic 'report' to the update reporter. */
1316   SVN_ERR(svn_ra_do_update3(ra_session,
1317                             &reporter, &report_baton,
1318                             loc->rev,
1319                             "", /* no sub-target */
1320                             depth,
1321                             FALSE, /* don't want copyfrom-args */
1322                             FALSE, /* don't want ignore_ancestry */
1323                             export_editor, edit_baton,
1324                             scratch_pool, scratch_pool));
1325
1326   SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1327                              /* Depth is irrelevant, as we're
1328                                 passing start_empty=TRUE anyway. */
1329                              svn_depth_infinity,
1330                              TRUE, /* "help, my dir is empty!" */
1331                              NULL, scratch_pool));
1332
1333   SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1334
1335   /* Special case: Due to our sly export/checkout method of updating an
1336    * empty directory, no target will have been created if the exported
1337    * item is itself an empty directory (export_editor->open_root never
1338    * gets called, because there are no "changes" to make to the empty
1339    * dir we reported to the repository).
1340    *
1341    * So we just create the empty dir manually; but we do it via
1342    * open_root_internal(), in order to get proper notification.
1343    */
1344   SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
1345   if (kind == svn_node_none)
1346     SVN_ERR(open_root_internal
1347             (to_path, overwrite, ctx->notify_func2,
1348              ctx->notify_baton2, scratch_pool));
1349
1350   if (! ignore_externals && depth == svn_depth_infinity)
1351     {
1352       const char *to_abspath;
1353
1354       SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1355       SVN_ERR(svn_client__export_externals(eb->externals,
1356                                            from_path_or_url,
1357                                            to_abspath, eb->repos_root_url,
1358                                            depth, native_eol,
1359                                            ignore_keywords,
1360                                            ctx, scratch_pool));
1361     }
1362
1363   return SVN_NO_ERROR;
1364 }
1365
1366
1367 \f
1368 /*** Public Interfaces ***/
1369
1370 svn_error_t *
1371 svn_client_export5(svn_revnum_t *result_rev,
1372                    const char *from_path_or_url,
1373                    const char *to_path,
1374                    const svn_opt_revision_t *peg_revision,
1375                    const svn_opt_revision_t *revision,
1376                    svn_boolean_t overwrite,
1377                    svn_boolean_t ignore_externals,
1378                    svn_boolean_t ignore_keywords,
1379                    svn_depth_t depth,
1380                    const char *native_eol,
1381                    svn_client_ctx_t *ctx,
1382                    apr_pool_t *pool)
1383 {
1384   svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1385   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1386
1387   SVN_ERR_ASSERT(peg_revision != NULL);
1388   SVN_ERR_ASSERT(revision != NULL);
1389
1390   if (svn_path_is_url(to_path))
1391     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1392                              _("'%s' is not a local path"), to_path);
1393
1394   peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1395                                                         from_path_or_url);
1396   revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1397
1398   if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1399     {
1400       svn_client__pathrev_t *loc;
1401       svn_ra_session_t *ra_session;
1402       svn_node_kind_t kind;
1403       struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1404
1405       /* Get the RA connection. */
1406       SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1407                                                 from_path_or_url, NULL,
1408                                                 peg_revision,
1409                                                 revision, ctx, pool));
1410
1411       SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
1412       eb->root_path = to_path;
1413       eb->root_url = loc->url;
1414       eb->force = overwrite;
1415       eb->target_revision = &edit_revision;
1416       eb->externals = apr_hash_make(pool);
1417       eb->native_eol = native_eol;
1418       eb->ignore_keywords = ignore_keywords;
1419       eb->cancel_func = ctx->cancel_func;
1420       eb->cancel_baton = ctx->cancel_baton;
1421       eb->notify_func = ctx->notify_func2;
1422       eb->notify_baton = ctx->notify_baton2;
1423
1424       SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1425
1426       if (kind == svn_node_file)
1427         {
1428           if (!ENABLE_EV2_IMPL)
1429             SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
1430                                 overwrite, pool));
1431           else
1432             SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
1433                                     ra_session, overwrite, pool));
1434         }
1435       else if (kind == svn_node_dir)
1436         {
1437           SVN_ERR(export_directory(from_path_or_url, to_path,
1438                                    eb, loc, ra_session, overwrite,
1439                                    ignore_externals, ignore_keywords, depth,
1440                                    native_eol, ctx, pool));
1441         }
1442       else if (kind == svn_node_none)
1443         {
1444           return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1445                                    _("URL '%s' doesn't exist"),
1446                                    from_path_or_url);
1447         }
1448       /* kind == svn_node_unknown not handled */
1449     }
1450   else
1451     {
1452       struct export_info_baton eib;
1453       svn_node_kind_t kind;
1454       apr_hash_t *externals = NULL;
1455
1456       /* This is a working copy export. */
1457       /* just copy the contents of the working copy into the target path. */
1458       SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
1459                                       pool));
1460
1461       SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1462
1463       SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1464
1465       /* ### [JAF] If something already exists on disk at the destination path,
1466        * the behaviour depends on the node kinds of the source and destination
1467        * and on the FORCE flag.  The intention (I guess) is to follow the
1468        * semantics of svn_client_export5(), semantics that are not fully
1469        * documented but would be something like:
1470        *
1471        * -----------+---------------------------------------------------------
1472        *        Src | DIR                 FILE                SPECIAL
1473        * Dst (disk) +---------------------------------------------------------
1474        * NONE       | simple copy         simple copy         (as src=file?)
1475        * DIR        | merge if forced [2] inside if root [1]  (as src=file?)
1476        * FILE       | err                 overwr if forced[3] (as src=file?)
1477        * SPECIAL    | ???                 ???                 ???
1478        * -----------+---------------------------------------------------------
1479        *
1480        * [1] FILE onto DIR case: If this file is the root of the copy and thus
1481        *     the only node to be copied, then copy it as a child of the
1482        *     directory TO, applying these same rules again except that if this
1483        *     case occurs again (the child path is already a directory) then
1484        *     error out.  If this file is not the root of the copy (it is
1485        *     reached by recursion), then error out.
1486        *
1487        * [2] DIR onto DIR case.  If the 'FORCE' flag is true then copy the
1488        *     source's children inside the target dir, else error out.  When
1489        *     copying the children, apply the same set of rules, except in the
1490        *     FILE onto DIR case error out like in note [1].
1491        *
1492        * [3] If the 'FORCE' flag is true then overwrite the destination file
1493        *     else error out.
1494        *
1495        * The reality (apparently, looking at the code) is somewhat different.
1496        * For a start, to detect the source kind, it looks at what is on disk
1497        * rather than the versioned working or base node.
1498        */
1499       if (kind == svn_node_file)
1500         SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1501                                        pool));
1502
1503       eib.to_path = to_path;
1504       eib.revision = revision;
1505       eib.overwrite = overwrite;
1506       eib.ignore_keywords = ignore_keywords;
1507       eib.wc_ctx = ctx->wc_ctx;
1508       eib.native_eol = native_eol;
1509       eib.notify_func = ctx->notify_func2;;
1510       eib.notify_baton = ctx->notify_baton2;
1511       eib.origin_abspath = from_path_or_url;
1512       eib.exported = FALSE;
1513
1514       SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1515                                  TRUE /* get_all */,
1516                                  TRUE /* no_ignore */,
1517                                  FALSE /* ignore_text_mods */,
1518                                  NULL,
1519                                  export_node, &eib,
1520                                  ctx->cancel_func, ctx->cancel_baton,
1521                                  pool));
1522
1523       if (!eib.exported)
1524         return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1525                                  _("The node '%s' was not found."),
1526                                  svn_dirent_local_style(from_path_or_url,
1527                                                         pool));
1528
1529       if (!ignore_externals)
1530         SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1531                                                 from_path_or_url,
1532                                                 pool, pool));
1533
1534       if (externals && apr_hash_count(externals))
1535         {
1536           apr_pool_t *iterpool = svn_pool_create(pool);
1537           apr_hash_index_t *hi;
1538
1539           for (hi = apr_hash_first(pool, externals);
1540                hi;
1541                hi = apr_hash_next(hi))
1542             {
1543               const char *external_abspath = svn__apr_hash_index_key(hi);
1544               const char *relpath;
1545               const char *target_abspath;
1546
1547               svn_pool_clear(iterpool);
1548
1549               relpath = svn_dirent_skip_ancestor(from_path_or_url,
1550                                                  external_abspath);
1551
1552               target_abspath = svn_dirent_join(to_path, relpath,
1553                                                          iterpool);
1554
1555               /* Ensure that the parent directory exists */
1556               SVN_ERR(svn_io_make_dir_recursively(
1557                             svn_dirent_dirname(target_abspath, iterpool),
1558                             iterpool));
1559
1560               SVN_ERR(svn_client_export5(NULL,
1561                                          svn_dirent_join(from_path_or_url,
1562                                                          relpath,
1563                                                          iterpool),
1564                                          target_abspath,
1565                                          peg_revision, revision,
1566                                          TRUE, ignore_externals,
1567                                          ignore_keywords, depth, native_eol,
1568                                          ctx, iterpool));
1569             }
1570
1571           svn_pool_destroy(iterpool);
1572         }
1573     }
1574
1575
1576   if (ctx->notify_func2)
1577     {
1578       svn_wc_notify_t *notify
1579         = svn_wc_create_notify(to_path,
1580                                svn_wc_notify_update_completed, pool);
1581       notify->revision = edit_revision;
1582       (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1583     }
1584
1585   if (result_rev)
1586     *result_rev = edit_revision;
1587
1588   return SVN_NO_ERROR;
1589 }