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 
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 
55 /*** Code. ***/
56 
57 /* Add EXTERNALS_PROP_VAL for the export destination path PATH to
58    TRAVERSAL_INFO.  */
59 static svn_error_t *
add_externals(apr_hash_t * externals,const char * path,const svn_string_t * externals_prop_val)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 *
get_eol_style(svn_subst_eol_style_t * style,const char ** eol,const char * value,const char * requested_value)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 *
append_basename_if_dir(const char ** appendable_dirent_p,const char * basename_of,svn_boolean_t is_uri,apr_pool_t * pool)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 *
export_node(void * baton,const char * local_abspath,const svn_wc_status3_t * status,apr_pool_t * scratch_pool)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_rename2(dst_tmp, to_abspath, FALSE, 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 OVERWRITE, in which case just
457  * export into PATH with no error.
458  */
459 static svn_error_t *
open_root_internal(const char * path,svn_boolean_t overwrite,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * pool)460 open_root_internal(const char *path,
461                    svn_boolean_t overwrite,
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) || (! overwrite))
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 
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 overwrite;
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 *
set_target_revision(void * edit_baton,svn_revnum_t target_revision,apr_pool_t * pool)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 *
open_root(void * edit_baton,svn_revnum_t base_revision,apr_pool_t * pool,void ** root_baton)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->overwrite,
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 *
add_directory(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** baton)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->overwrite))
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 *
add_file(const char * path,void * parent_baton,const char * copyfrom_path,svn_revnum_t copyfrom_revision,apr_pool_t * pool,void ** baton)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 *
window_handler(svn_txdelta_window_t * window,void * baton)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 *
apply_textdelta(void * file_baton,const char * base_checksum,apr_pool_t * pool,svn_txdelta_window_handler_t * handler,void ** handler_baton)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 *
change_file_prop(void * file_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)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 *
change_dir_prop(void * dir_baton,const char * name,const svn_string_t * value,apr_pool_t * pool)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 *
close_file(void * file_baton,const char * text_digest,apr_pool_t * pool)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_rename2(fb->tmppath, fb->path, FALSE, 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 *
fetch_props_func(apr_hash_t ** props,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
fetch_base_func(const char ** filename,void * baton,const char * path,svn_revnum_t base_revision,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 *
get_editor_ev1(const svn_delta_editor_t ** export_editor,void ** edit_baton,struct edit_baton * eb,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 
930 /*** The Ev2 Implementation ***/
931 
932 static svn_error_t *
add_file_ev2(void * baton,const char * relpath,const svn_checksum_t * checksum,svn_stream_t * contents,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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_rename2(tmppath, full_path, FALSE, 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 *
add_directory_ev2(void * baton,const char * relpath,const apr_array_header_t * children,apr_hash_t * props,svn_revnum_t replaces_rev,apr_pool_t * scratch_pool)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->overwrite))
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 *
target_revision_func(void * baton,svn_revnum_t target_revision,apr_pool_t * scratch_pool)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 *
get_editor_ev2(const svn_delta_editor_t ** export_editor,void ** edit_baton,struct edit_baton * eb,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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->overwrite, eb->notify_func,
1145                              eb->notify_baton, scratch_pool));
1146 
1147   return SVN_NO_ERROR;
1148 }
1149 
1150 static svn_error_t *
export_file_ev2(const char * from_url,const char * to_path,struct edit_baton * eb,svn_client__pathrev_t * loc,svn_ra_session_t * ra_session,apr_pool_t * scratch_pool)1151 export_file_ev2(const char *from_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                 apr_pool_t *scratch_pool)
1157 {
1158   apr_hash_t *props;
1159   svn_stream_t *tmp_stream;
1160   svn_node_kind_t to_kind;
1161 
1162   SVN_ERR_ASSERT(svn_path_is_url(from_url));
1163 
1164   if (svn_path_is_empty(to_path))
1165     {
1166       to_path = svn_uri_basename(from_url, scratch_pool);
1167       eb->root_path = to_path;
1168     }
1169   else
1170     {
1171       SVN_ERR(append_basename_if_dir(&to_path, from_url,
1172                                      TRUE, scratch_pool));
1173       eb->root_path = to_path;
1174     }
1175 
1176   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1177 
1178   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1179       ! eb->overwrite)
1180     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1181                              _("Destination file '%s' exists, and "
1182                                "will not be overwritten unless forced"),
1183                              svn_dirent_local_style(to_path, scratch_pool));
1184   else if (to_kind == svn_node_dir)
1185     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1186                              _("Destination '%s' exists. Cannot "
1187                                "overwrite directory with non-directory"),
1188                              svn_dirent_local_style(to_path, scratch_pool));
1189 
1190   tmp_stream = svn_stream_buffered(scratch_pool);
1191 
1192   SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1193                           tmp_stream, NULL, &props, scratch_pool));
1194 
1195   /* Since you cannot actually root an editor at a file, we manually drive
1196    * a function of our editor. */
1197   SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
1198                        scratch_pool));
1199 
1200   return SVN_NO_ERROR;
1201 }
1202 
1203 static svn_error_t *
export_file(const char * from_url,const char * to_path,struct edit_baton * eb,svn_client__pathrev_t * loc,svn_ra_session_t * ra_session,apr_pool_t * scratch_pool)1204 export_file(const char *from_url,
1205             const char *to_path,
1206             struct edit_baton *eb,
1207             svn_client__pathrev_t *loc,
1208             svn_ra_session_t *ra_session,
1209             apr_pool_t *scratch_pool)
1210 {
1211   apr_hash_t *props;
1212   apr_hash_index_t *hi;
1213   struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
1214   svn_node_kind_t to_kind;
1215 
1216   SVN_ERR_ASSERT(svn_path_is_url(from_url));
1217 
1218   if (svn_path_is_empty(to_path))
1219     {
1220       to_path = svn_uri_basename(from_url, scratch_pool);
1221       eb->root_path = to_path;
1222     }
1223   else
1224     {
1225       SVN_ERR(append_basename_if_dir(&to_path, from_url,
1226                                      TRUE, scratch_pool));
1227       eb->root_path = to_path;
1228     }
1229 
1230   SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1231 
1232   if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1233       ! eb->overwrite)
1234     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1235                              _("Destination file '%s' exists, and "
1236                                "will not be overwritten unless forced"),
1237                              svn_dirent_local_style(to_path, scratch_pool));
1238   else if (to_kind == svn_node_dir)
1239     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1240                              _("Destination '%s' exists. Cannot "
1241                                "overwrite directory with non-directory"),
1242                              svn_dirent_local_style(to_path, scratch_pool));
1243 
1244   /* Since you cannot actually root an editor at a file, we
1245    * manually drive a few functions of our editor. */
1246 
1247   /* This is the equivalent of a parentless add_file(). */
1248   fb->edit_baton = eb;
1249   fb->path = eb->root_path;
1250   fb->url = eb->root_url;
1251   fb->pool = scratch_pool;
1252   fb->repos_root_url = eb->repos_root_url;
1253 
1254   /* Copied from apply_textdelta(). */
1255   SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
1256                                  svn_dirent_dirname(fb->path, scratch_pool),
1257                                  svn_io_file_del_none,
1258                                  fb->pool, fb->pool));
1259 
1260   /* Step outside the editor-likeness for a moment, to actually talk
1261    * to the repository. */
1262   /* ### note: the stream will not be closed */
1263   SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1264                           fb->tmp_stream,
1265                           NULL, &props, scratch_pool));
1266 
1267   /* Push the props into change_file_prop(), to update the file_baton
1268    * with information. */
1269   for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
1270     {
1271       const char *propname = apr_hash_this_key(hi);
1272       const svn_string_t *propval = apr_hash_this_val(hi);
1273 
1274       SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1275     }
1276 
1277   /* And now just use close_file() to do all the keyword and EOL
1278    * work, and put the file into place. */
1279   SVN_ERR(close_file(fb, NULL, scratch_pool));
1280 
1281   return SVN_NO_ERROR;
1282 }
1283 
1284 static svn_error_t *
export_directory(const char * from_url,const char * to_path,struct edit_baton * eb,svn_client__pathrev_t * loc,svn_ra_session_t * ra_session,svn_boolean_t ignore_externals,svn_boolean_t ignore_keywords,svn_depth_t depth,const char * native_eol,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1285 export_directory(const char *from_url,
1286                  const char *to_path,
1287                  struct edit_baton *eb,
1288                  svn_client__pathrev_t *loc,
1289                  svn_ra_session_t *ra_session,
1290                  svn_boolean_t ignore_externals,
1291                  svn_boolean_t ignore_keywords,
1292                  svn_depth_t depth,
1293                  const char *native_eol,
1294                  svn_client_ctx_t *ctx,
1295                  apr_pool_t *scratch_pool)
1296 {
1297   void *edit_baton;
1298   const svn_delta_editor_t *export_editor;
1299   const svn_ra_reporter3_t *reporter;
1300   void *report_baton;
1301   svn_node_kind_t kind;
1302 
1303   SVN_ERR_ASSERT(svn_path_is_url(from_url));
1304 
1305   if (!ENABLE_EV2_IMPL)
1306     SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1307                            scratch_pool, scratch_pool));
1308   else
1309     SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1310                            scratch_pool, scratch_pool));
1311 
1312   /* Manufacture a basic 'report' to the update reporter. */
1313   SVN_ERR(svn_ra_do_update3(ra_session,
1314                             &reporter, &report_baton,
1315                             loc->rev,
1316                             "", /* no sub-target */
1317                             depth,
1318                             FALSE, /* don't want copyfrom-args */
1319                             FALSE, /* don't want ignore_ancestry */
1320                             export_editor, edit_baton,
1321                             scratch_pool, scratch_pool));
1322 
1323   SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1324                              /* Depth is irrelevant, as we're
1325                                 passing start_empty=TRUE anyway. */
1326                              svn_depth_infinity,
1327                              TRUE, /* "help, my dir is empty!" */
1328                              NULL, scratch_pool));
1329 
1330   SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1331 
1332   /* Special case: Due to our sly export/checkout method of updating an
1333    * empty directory, no target will have been created if the exported
1334    * item is itself an empty directory (export_editor->open_root never
1335    * gets called, because there are no "changes" to make to the empty
1336    * dir we reported to the repository).
1337    *
1338    * So we just create the empty dir manually; but we do it via
1339    * open_root_internal(), in order to get proper notification.
1340    */
1341   SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
1342   if (kind == svn_node_none)
1343     SVN_ERR(open_root_internal
1344             (to_path, eb->overwrite, ctx->notify_func2,
1345              ctx->notify_baton2, scratch_pool));
1346 
1347   if (! ignore_externals && depth == svn_depth_infinity)
1348     {
1349       const char *to_abspath;
1350 
1351       SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1352       SVN_ERR(svn_client__export_externals(eb->externals,
1353                                            from_url,
1354                                            to_abspath, eb->repos_root_url,
1355                                            depth, native_eol,
1356                                            ignore_keywords,
1357                                            ctx, scratch_pool));
1358     }
1359 
1360   return SVN_NO_ERROR;
1361 }
1362 
1363 
1364 
1365 /*** Public Interfaces ***/
1366 
1367 svn_error_t *
svn_client_export5(svn_revnum_t * result_rev,const char * from_path_or_url,const char * to_path,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,svn_boolean_t overwrite,svn_boolean_t ignore_externals,svn_boolean_t ignore_keywords,svn_depth_t depth,const char * native_eol,svn_client_ctx_t * ctx,apr_pool_t * pool)1368 svn_client_export5(svn_revnum_t *result_rev,
1369                    const char *from_path_or_url,
1370                    const char *to_path,
1371                    const svn_opt_revision_t *peg_revision,
1372                    const svn_opt_revision_t *revision,
1373                    svn_boolean_t overwrite,
1374                    svn_boolean_t ignore_externals,
1375                    svn_boolean_t ignore_keywords,
1376                    svn_depth_t depth,
1377                    const char *native_eol,
1378                    svn_client_ctx_t *ctx,
1379                    apr_pool_t *pool)
1380 {
1381   svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1382   svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1383 
1384   SVN_ERR_ASSERT(peg_revision != NULL);
1385   SVN_ERR_ASSERT(revision != NULL);
1386 
1387   if (svn_path_is_url(to_path))
1388     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1389                              _("'%s' is not a local path"), to_path);
1390 
1391   peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1392                                                         from_path_or_url);
1393   revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1394 
1395   if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1396     {
1397       svn_client__pathrev_t *loc;
1398       svn_ra_session_t *ra_session;
1399       svn_node_kind_t kind;
1400       const char *from_url;
1401       struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1402 
1403       SVN_ERR(svn_client_url_from_path2(&from_url, from_path_or_url,
1404                                         ctx, pool, pool));
1405 
1406       /* Get the RA connection. */
1407       SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1408                                                 from_path_or_url, NULL,
1409                                                 peg_revision,
1410                                                 revision, ctx, pool));
1411 
1412       SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
1413       eb->root_path = to_path;
1414       eb->root_url = loc->url;
1415       eb->overwrite = overwrite;
1416       eb->target_revision = &edit_revision;
1417       eb->externals = apr_hash_make(pool);
1418       eb->native_eol = native_eol;
1419       eb->ignore_keywords = ignore_keywords;
1420       eb->cancel_func = ctx->cancel_func;
1421       eb->cancel_baton = ctx->cancel_baton;
1422       eb->notify_func = ctx->notify_func2;
1423       eb->notify_baton = ctx->notify_baton2;
1424 
1425       SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1426 
1427       if (kind == svn_node_file)
1428         {
1429           if (!ENABLE_EV2_IMPL)
1430             SVN_ERR(export_file(from_url, to_path, eb, loc, ra_session,
1431                                 pool));
1432           else
1433             SVN_ERR(export_file_ev2(from_url, to_path, eb, loc,
1434                                     ra_session, pool));
1435         }
1436       else if (kind == svn_node_dir)
1437         {
1438           SVN_ERR(export_directory(from_url, to_path,
1439                                    eb, loc, ra_session,
1440                                    ignore_externals, ignore_keywords, depth,
1441                                    native_eol, ctx, pool));
1442         }
1443       else if (kind == svn_node_none)
1444         {
1445           return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1446                                    _("URL '%s' doesn't exist"),
1447                                    from_path_or_url);
1448         }
1449       /* kind == svn_node_unknown not handled */
1450     }
1451   else
1452     {
1453       struct export_info_baton eib;
1454       svn_node_kind_t kind;
1455       apr_hash_t *externals = NULL;
1456 
1457       /* This is a working copy export. */
1458       /* just copy the contents of the working copy into the target path. */
1459       SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
1460                                       pool));
1461 
1462       SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1463 
1464       SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1465 
1466       /* ### [JAF] If something already exists on disk at the destination path,
1467        * the behaviour depends on the node kinds of the source and destination
1468        * and on the FORCE flag.  The intention (I guess) is to follow the
1469        * semantics of svn_client_export5(), semantics that are not fully
1470        * documented but would be something like:
1471        *
1472        * -----------+---------------------------------------------------------
1473        *        Src | DIR                 FILE                SPECIAL
1474        * Dst (disk) +---------------------------------------------------------
1475        * NONE       | simple copy         simple copy         (as src=file?)
1476        * DIR        | merge if forced [2] inside if root [1]  (as src=file?)
1477        * FILE       | err                 overwr if forced[3] (as src=file?)
1478        * SPECIAL    | ???                 ???                 ???
1479        * -----------+---------------------------------------------------------
1480        *
1481        * [1] FILE onto DIR case: If this file is the root of the copy and thus
1482        *     the only node to be copied, then copy it as a child of the
1483        *     directory TO, applying these same rules again except that if this
1484        *     case occurs again (the child path is already a directory) then
1485        *     error out.  If this file is not the root of the copy (it is
1486        *     reached by recursion), then error out.
1487        *
1488        * [2] DIR onto DIR case.  If the 'FORCE' flag is true then copy the
1489        *     source's children inside the target dir, else error out.  When
1490        *     copying the children, apply the same set of rules, except in the
1491        *     FILE onto DIR case error out like in note [1].
1492        *
1493        * [3] If the 'FORCE' flag is true then overwrite the destination file
1494        *     else error out.
1495        *
1496        * The reality (apparently, looking at the code) is somewhat different.
1497        * For a start, to detect the source kind, it looks at what is on disk
1498        * rather than the versioned working or base node.
1499        */
1500       if (kind == svn_node_file)
1501         SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1502                                        pool));
1503 
1504       eib.to_path = to_path;
1505       eib.revision = revision;
1506       eib.overwrite = overwrite;
1507       eib.ignore_keywords = ignore_keywords;
1508       eib.wc_ctx = ctx->wc_ctx;
1509       eib.native_eol = native_eol;
1510       eib.notify_func = ctx->notify_func2;
1511       eib.notify_baton = ctx->notify_baton2;
1512       eib.origin_abspath = from_path_or_url;
1513       eib.exported = FALSE;
1514 
1515       SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1516                                  TRUE /* get_all */,
1517                                  TRUE /* no_ignore */,
1518                                  FALSE /* ignore_text_mods */,
1519                                  NULL,
1520                                  export_node, &eib,
1521                                  ctx->cancel_func, ctx->cancel_baton,
1522                                  pool));
1523 
1524       if (!eib.exported)
1525         return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1526                                  _("The node '%s' was not found."),
1527                                  svn_dirent_local_style(from_path_or_url,
1528                                                         pool));
1529 
1530       if (!ignore_externals)
1531         SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1532                                                 from_path_or_url,
1533                                                 pool, pool));
1534 
1535       if (externals && apr_hash_count(externals))
1536         {
1537           apr_pool_t *iterpool = svn_pool_create(pool);
1538           apr_hash_index_t *hi;
1539 
1540           for (hi = apr_hash_first(pool, externals);
1541                hi;
1542                hi = apr_hash_next(hi))
1543             {
1544               const char *external_abspath = apr_hash_this_key(hi);
1545               const char *relpath;
1546               const char *target_abspath;
1547 
1548               svn_pool_clear(iterpool);
1549 
1550               relpath = svn_dirent_skip_ancestor(from_path_or_url,
1551                                                  external_abspath);
1552 
1553               target_abspath = svn_dirent_join(to_path, relpath,
1554                                                          iterpool);
1555 
1556               /* Ensure that the parent directory exists */
1557               SVN_ERR(svn_io_make_dir_recursively(
1558                             svn_dirent_dirname(target_abspath, iterpool),
1559                             iterpool));
1560 
1561               SVN_ERR(svn_client_export5(NULL,
1562                                          svn_dirent_join(from_path_or_url,
1563                                                          relpath,
1564                                                          iterpool),
1565                                          target_abspath,
1566                                          peg_revision, revision,
1567                                          TRUE, ignore_externals,
1568                                          ignore_keywords, depth, native_eol,
1569                                          ctx, iterpool));
1570             }
1571 
1572           svn_pool_destroy(iterpool);
1573         }
1574     }
1575 
1576 
1577   if (ctx->notify_func2)
1578     {
1579       svn_wc_notify_t *notify
1580         = svn_wc_create_notify(to_path,
1581                                svn_wc_notify_update_completed, pool);
1582       notify->revision = edit_revision;
1583       ctx->notify_func2(ctx->notify_baton2, notify, pool);
1584     }
1585 
1586   if (result_rev)
1587     *result_rev = edit_revision;
1588 
1589   return SVN_NO_ERROR;
1590 }
1591 
1592