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.  */
59static svn_error_t *
60add_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. */
83static svn_error_t *
84get_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 */
113static svn_error_t *
114append_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 */
162struct 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 */
177static svn_error_t *
178export_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 */
459static svn_error_t *
460open_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
499struct 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
517struct dir_baton
518{
519  struct edit_baton *edit_baton;
520  const char *path;
521};
522
523
524struct 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
557struct 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
566static svn_error_t *
567set_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. */
581static svn_error_t *
582open_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. */
603static svn_error_t *
604add_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. */
648static svn_error_t *
649add_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
678static svn_error_t *
679window_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 */
699static svn_error_t *
700apply_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
733static svn_error_t *
734change_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
772static svn_error_t *
773change_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. */
789static svn_error_t *
790close_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
870static svn_error_t *
871fetch_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
885static svn_error_t *
886fetch_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
899static svn_error_t *
900get_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
932static svn_error_t *
933add_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
1059static svn_error_t *
1060add_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
1100static svn_error_t *
1101target_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
1112static svn_error_t *
1113get_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
1150static svn_error_t *
1151export_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
1203static svn_error_t *
1204export_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
1284static svn_error_t *
1285export_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
1367svn_error_t *
1368svn_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