1251881Speter/*
2251881Speter * export.c:  export a tree.
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <apr_file_io.h>
31251881Speter#include <apr_md5.h>
32251881Speter#include "svn_types.h"
33251881Speter#include "svn_client.h"
34251881Speter#include "svn_string.h"
35251881Speter#include "svn_error.h"
36251881Speter#include "svn_dirent_uri.h"
37251881Speter#include "svn_hash.h"
38251881Speter#include "svn_path.h"
39251881Speter#include "svn_pools.h"
40251881Speter#include "svn_subst.h"
41251881Speter#include "svn_time.h"
42251881Speter#include "svn_props.h"
43251881Speter#include "client.h"
44251881Speter
45251881Speter#include "svn_private_config.h"
46251881Speter#include "private/svn_subr_private.h"
47251881Speter#include "private/svn_delta_private.h"
48251881Speter#include "private/svn_wc_private.h"
49251881Speter
50251881Speter#ifndef ENABLE_EV2_IMPL
51251881Speter#define ENABLE_EV2_IMPL 0
52251881Speter#endif
53251881Speter
54251881Speter
55251881Speter/*** Code. ***/
56251881Speter
57251881Speter/* Add EXTERNALS_PROP_VAL for the export destination path PATH to
58251881Speter   TRAVERSAL_INFO.  */
59251881Speterstatic svn_error_t *
60251881Speteradd_externals(apr_hash_t *externals,
61251881Speter              const char *path,
62251881Speter              const svn_string_t *externals_prop_val)
63251881Speter{
64251881Speter  apr_pool_t *pool = apr_hash_pool_get(externals);
65251881Speter  const char *local_abspath;
66251881Speter
67251881Speter  if (! externals_prop_val)
68251881Speter    return SVN_NO_ERROR;
69251881Speter
70251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
71251881Speter
72251881Speter  svn_hash_sets(externals, local_abspath,
73251881Speter                apr_pstrmemdup(pool, externals_prop_val->data,
74251881Speter                               externals_prop_val->len));
75251881Speter
76251881Speter  return SVN_NO_ERROR;
77251881Speter}
78251881Speter
79251881Speter/* Helper function that gets the eol style and optionally overrides the
80251881Speter   EOL marker for files marked as native with the EOL marker matching
81251881Speter   the string specified in requested_value which is of the same format
82251881Speter   as the svn:eol-style property values. */
83251881Speterstatic svn_error_t *
84251881Speterget_eol_style(svn_subst_eol_style_t *style,
85251881Speter              const char **eol,
86251881Speter              const char *value,
87251881Speter              const char *requested_value)
88251881Speter{
89251881Speter  svn_subst_eol_style_from_value(style, eol, value);
90251881Speter  if (requested_value && *style == svn_subst_eol_style_native)
91251881Speter    {
92251881Speter      svn_subst_eol_style_t requested_style;
93251881Speter      const char *requested_eol;
94251881Speter
95251881Speter      svn_subst_eol_style_from_value(&requested_style, &requested_eol,
96251881Speter                                     requested_value);
97251881Speter
98251881Speter      if (requested_style == svn_subst_eol_style_fixed)
99251881Speter        *eol = requested_eol;
100251881Speter      else
101251881Speter        return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
102251881Speter                                 _("'%s' is not a valid EOL value"),
103251881Speter                                 requested_value);
104251881Speter    }
105251881Speter  return SVN_NO_ERROR;
106251881Speter}
107251881Speter
108251881Speter/* If *APPENDABLE_DIRENT_P represents an existing directory, then append
109251881Speter * to it the basename of BASENAME_OF and return the result in
110251881Speter * *APPENDABLE_DIRENT_P.  The kind of BASENAME_OF is either dirent or uri,
111251881Speter * as given by IS_URI.
112251881Speter */
113251881Speterstatic svn_error_t *
114251881Speterappend_basename_if_dir(const char **appendable_dirent_p,
115251881Speter                       const char *basename_of,
116251881Speter                       svn_boolean_t is_uri,
117251881Speter                       apr_pool_t *pool)
118251881Speter{
119251881Speter  svn_node_kind_t local_kind;
120251881Speter  SVN_ERR(svn_io_check_resolved_path(*appendable_dirent_p, &local_kind, pool));
121251881Speter  if (local_kind == svn_node_dir)
122251881Speter    {
123251881Speter      const char *base_name;
124251881Speter
125251881Speter      if (is_uri)
126251881Speter        base_name = svn_uri_basename(basename_of, pool);
127251881Speter      else
128251881Speter        base_name = svn_dirent_basename(basename_of, NULL);
129251881Speter
130251881Speter      *appendable_dirent_p = svn_dirent_join(*appendable_dirent_p,
131251881Speter                                             base_name, pool);
132251881Speter    }
133251881Speter
134251881Speter  return SVN_NO_ERROR;
135251881Speter}
136251881Speter
137251881Speter/* Make an unversioned copy of the versioned file at FROM_ABSPATH.  Copy it
138251881Speter * to the destination path TO_ABSPATH.
139251881Speter *
140251881Speter * If REVISION is svn_opt_revision_working, copy the working version,
141251881Speter * otherwise copy the base version.
142251881Speter *
143251881Speter * Expand the file's keywords according to the source file's 'svn:keywords'
144251881Speter * property, if present.  If copying a locally modified working version,
145251881Speter * append 'M' to the revision number and use '(local)' for the author.
146251881Speter *
147251881Speter * Translate the file's line endings according to the source file's
148251881Speter * 'svn:eol-style' property, if present.  If NATIVE_EOL is not NULL, use it
149251881Speter * in place of the native EOL style.  Throw an error if the source file has
150251881Speter * inconsistent line endings and EOL translation is attempted.
151251881Speter *
152251881Speter * Set the destination file's modification time to the source file's
153251881Speter * modification time if copying the working version and the working version
154251881Speter * is locally modified; otherwise set it to the versioned file's last
155251881Speter * changed time.
156251881Speter *
157251881Speter * Set the destination file's 'executable' flag according to the source
158251881Speter * file's 'svn:executable' property.
159251881Speter */
160251881Speter
161251881Speter/* baton for export_node */
162251881Speterstruct export_info_baton
163251881Speter{
164251881Speter  const char *to_path;
165251881Speter  const svn_opt_revision_t *revision;
166251881Speter  svn_boolean_t ignore_keywords;
167251881Speter  svn_boolean_t overwrite;
168251881Speter  svn_wc_context_t *wc_ctx;
169251881Speter  const char *native_eol;
170251881Speter  svn_wc_notify_func2_t notify_func;
171251881Speter  void *notify_baton;
172251881Speter  const char *origin_abspath;
173251881Speter  svn_boolean_t exported;
174251881Speter};
175251881Speter
176251881Speter/* Export a file or directory. Implements svn_wc_status_func4_t */
177251881Speterstatic svn_error_t *
178251881Speterexport_node(void *baton,
179251881Speter            const char *local_abspath,
180251881Speter            const svn_wc_status3_t *status,
181251881Speter            apr_pool_t *scratch_pool)
182251881Speter{
183251881Speter  struct export_info_baton *eib = baton;
184251881Speter  svn_wc_context_t *wc_ctx = eib->wc_ctx;
185251881Speter  apr_hash_t *kw = NULL;
186251881Speter  svn_subst_eol_style_t style;
187251881Speter  apr_hash_t *props;
188251881Speter  svn_string_t *eol_style, *keywords, *executable, *special;
189251881Speter  const char *eol = NULL;
190251881Speter  svn_boolean_t local_mod = FALSE;
191251881Speter  apr_time_t tm;
192251881Speter  svn_stream_t *source;
193251881Speter  svn_stream_t *dst_stream;
194251881Speter  const char *dst_tmp;
195251881Speter  svn_error_t *err;
196251881Speter
197251881Speter  const char *to_abspath = svn_dirent_join(
198251881Speter                                eib->to_path,
199251881Speter                                svn_dirent_skip_ancestor(eib->origin_abspath,
200251881Speter                                                         local_abspath),
201251881Speter                                scratch_pool);
202251881Speter
203251881Speter  eib->exported = TRUE;
204251881Speter
205251881Speter  /* Don't export 'deleted' files and directories unless it's a
206251881Speter     revision other than WORKING.  These files and directories
207251881Speter     don't really exist in WORKING. */
208251881Speter  if (eib->revision->kind == svn_opt_revision_working
209251881Speter      && status->node_status == svn_wc_status_deleted)
210251881Speter    return SVN_NO_ERROR;
211251881Speter
212251881Speter  if (status->kind == svn_node_dir)
213251881Speter    {
214251881Speter      apr_fileperms_t perm = APR_OS_DEFAULT;
215251881Speter
216251881Speter      /* Try to make the new directory.  If this fails because the
217251881Speter         directory already exists, check our FORCE flag to see if we
218251881Speter         care. */
219251881Speter
220251881Speter      /* Keep the source directory's permissions if applicable.
221251881Speter         Skip retrieving the umask on windows. Apr does not implement setting
222251881Speter         filesystem privileges on Windows.
223251881Speter         Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER
224251881Speter         is documented to be 'incredibly expensive' */
225251881Speter#ifndef WIN32
226251881Speter      if (eib->revision->kind == svn_opt_revision_working)
227251881Speter        {
228251881Speter          apr_finfo_t finfo;
229251881Speter          SVN_ERR(svn_io_stat(&finfo, local_abspath, APR_FINFO_PROT,
230251881Speter                              scratch_pool));
231251881Speter          perm = finfo.protection;
232251881Speter        }
233251881Speter#endif
234251881Speter      err = svn_io_dir_make(to_abspath, perm, scratch_pool);
235251881Speter      if (err)
236251881Speter        {
237251881Speter          if (! APR_STATUS_IS_EEXIST(err->apr_err))
238251881Speter            return svn_error_trace(err);
239251881Speter          if (! eib->overwrite)
240251881Speter            SVN_ERR_W(err, _("Destination directory exists, and will not be "
241251881Speter                             "overwritten unless forced"));
242251881Speter          else
243251881Speter            svn_error_clear(err);
244251881Speter        }
245251881Speter
246251881Speter      if (eib->notify_func
247251881Speter          && (strcmp(eib->origin_abspath, local_abspath) != 0))
248251881Speter        {
249251881Speter          svn_wc_notify_t *notify =
250251881Speter              svn_wc_create_notify(to_abspath,
251251881Speter                                   svn_wc_notify_update_add, scratch_pool);
252251881Speter
253251881Speter          notify->kind = svn_node_dir;
254251881Speter          (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
255251881Speter        }
256251881Speter
257251881Speter      return SVN_NO_ERROR;
258251881Speter    }
259251881Speter  else if (status->kind != svn_node_file)
260251881Speter    {
261251881Speter      if (strcmp(eib->origin_abspath, local_abspath) != 0)
262251881Speter        return SVN_NO_ERROR;
263251881Speter
264251881Speter      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
265251881Speter                               _("The node '%s' was not found."),
266251881Speter                               svn_dirent_local_style(local_abspath,
267251881Speter                                                      scratch_pool));
268251881Speter    }
269251881Speter
270269847Speter  /* Skip file externals if they are a descendant of the export,
271269847Speter     BUT NOT if we are explictly exporting the file external. */
272269847Speter  if (status->file_external && strcmp(eib->origin_abspath, local_abspath) != 0)
273251881Speter    return SVN_NO_ERROR;
274251881Speter
275251881Speter  /* Produce overwrite errors for the export root */
276251881Speter  if (strcmp(local_abspath, eib->origin_abspath) == 0)
277251881Speter    {
278251881Speter      svn_node_kind_t to_kind;
279251881Speter
280251881Speter      SVN_ERR(svn_io_check_path(to_abspath, &to_kind, scratch_pool));
281251881Speter
282251881Speter      if ((to_kind == svn_node_file || to_kind == svn_node_unknown)
283251881Speter          && !eib->overwrite)
284251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
285251881Speter                                 _("Destination file '%s' exists, and "
286251881Speter                                   "will not be overwritten unless forced"),
287251881Speter                                 svn_dirent_local_style(to_abspath,
288251881Speter                                                        scratch_pool));
289251881Speter      else if (to_kind == svn_node_dir)
290251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
291251881Speter                                 _("Destination '%s' exists. Cannot "
292251881Speter                                   "overwrite directory with non-directory"),
293251881Speter                                 svn_dirent_local_style(to_abspath,
294251881Speter                                                        scratch_pool));
295251881Speter    }
296251881Speter
297251881Speter  if (eib->revision->kind != svn_opt_revision_working)
298251881Speter    {
299251881Speter      /* Only export 'added' files when the revision is WORKING. This is not
300251881Speter         WORKING, so skip the 'added' files, since they didn't exist
301251881Speter         in the BASE revision and don't have an associated text-base.
302251881Speter
303251881Speter         'replaced' files are technically the same as 'added' files.
304251881Speter         ### TODO: Handle replaced nodes properly.
305251881Speter         ###       svn_opt_revision_base refers to the "new"
306251881Speter         ###       base of the node. That means, if a node is locally
307251881Speter         ###       replaced, export skips this node, as if it was locally
308251881Speter         ###       added, because svn_opt_revision_base refers to the base
309251881Speter         ###       of the added node, not to the node that was deleted.
310251881Speter         ###       In contrast, when the node is copied-here or moved-here,
311251881Speter         ###       the copy/move source's content will be exported.
312251881Speter         ###       It is currently not possible to export the revert-base
313251881Speter         ###       when a node is locally replaced. We need a new
314251881Speter         ###       svn_opt_revision_ enum value for proper distinction
315251881Speter         ###       between revert-base and commit-base.
316251881Speter
317251881Speter         Copied-/moved-here nodes have a base, so export both added and
318251881Speter         replaced files when they involve a copy-/move-here.
319251881Speter
320251881Speter         We get all this for free from evaluating SOURCE == NULL:
321251881Speter       */
322251881Speter      SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, local_abspath,
323251881Speter                                            scratch_pool, scratch_pool));
324251881Speter      if (source == NULL)
325251881Speter        return SVN_NO_ERROR;
326251881Speter
327251881Speter      SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, local_abspath,
328251881Speter                                        scratch_pool, scratch_pool));
329251881Speter    }
330251881Speter  else
331251881Speter    {
332251881Speter      /* ### hmm. this isn't always a specialfile. this will simply open
333251881Speter         ### the file readonly if it is a regular file. */
334251881Speter      SVN_ERR(svn_subst_read_specialfile(&source, local_abspath, scratch_pool,
335251881Speter                                         scratch_pool));
336251881Speter
337251881Speter      SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, local_abspath, scratch_pool,
338251881Speter                                scratch_pool));
339251881Speter      if (status->node_status != svn_wc_status_normal)
340251881Speter        local_mod = TRUE;
341251881Speter    }
342251881Speter
343251881Speter  /* We can early-exit if we're creating a special file. */
344251881Speter  special = svn_hash_gets(props, SVN_PROP_SPECIAL);
345251881Speter  if (special != NULL)
346251881Speter    {
347251881Speter      /* Create the destination as a special file, and copy the source
348251881Speter         details into the destination stream. */
349251881Speter      /* ### And forget the notification */
350251881Speter      SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath,
351251881Speter                                           scratch_pool, scratch_pool));
352251881Speter      return svn_error_trace(
353251881Speter        svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool));
354251881Speter    }
355251881Speter
356251881Speter
357251881Speter  eol_style = svn_hash_gets(props, SVN_PROP_EOL_STYLE);
358251881Speter  keywords = svn_hash_gets(props, SVN_PROP_KEYWORDS);
359251881Speter  executable = svn_hash_gets(props, SVN_PROP_EXECUTABLE);
360251881Speter
361251881Speter  if (eol_style)
362251881Speter    SVN_ERR(get_eol_style(&style, &eol, eol_style->data, eib->native_eol));
363251881Speter
364251881Speter  if (local_mod)
365251881Speter    {
366251881Speter      /* Use the modified time from the working copy of
367251881Speter         the file */
368251881Speter      SVN_ERR(svn_io_file_affected_time(&tm, local_abspath, scratch_pool));
369251881Speter    }
370251881Speter  else
371251881Speter    {
372251881Speter      tm = status->changed_date;
373251881Speter    }
374251881Speter
375251881Speter  if (keywords)
376251881Speter    {
377251881Speter      svn_revnum_t changed_rev = status->changed_rev;
378251881Speter      const char *suffix;
379251881Speter      const char *url = svn_path_url_add_component2(status->repos_root_url,
380251881Speter                                                    status->repos_relpath,
381251881Speter                                                    scratch_pool);
382251881Speter      const char *author = status->changed_author;
383251881Speter      if (local_mod)
384251881Speter        {
385251881Speter          /* For locally modified files, we'll append an 'M'
386251881Speter             to the revision number, and set the author to
387251881Speter             "(local)" since we can't always determine the
388251881Speter             current user's username */
389251881Speter          suffix = "M";
390251881Speter          author = _("(local)");
391251881Speter        }
392251881Speter      else
393251881Speter        {
394251881Speter          suffix = "";
395251881Speter        }
396251881Speter
397251881Speter      SVN_ERR(svn_subst_build_keywords3(&kw, keywords->data,
398251881Speter                                        apr_psprintf(scratch_pool, "%ld%s",
399251881Speter                                                     changed_rev, suffix),
400251881Speter                                        url, status->repos_root_url, tm,
401251881Speter                                        author, scratch_pool));
402251881Speter    }
403251881Speter
404251881Speter  /* For atomicity, we translate to a tmp file and then rename the tmp file
405251881Speter     over the real destination. */
406251881Speter  SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp,
407251881Speter                                 svn_dirent_dirname(to_abspath, scratch_pool),
408251881Speter                                 svn_io_file_del_none, scratch_pool,
409251881Speter                                 scratch_pool));
410251881Speter
411251881Speter  /* If some translation is needed, then wrap the output stream (this is
412251881Speter     more efficient than wrapping the input). */
413251881Speter  if (eol || (kw && (apr_hash_count(kw) > 0)))
414251881Speter    dst_stream = svn_subst_stream_translated(dst_stream,
415251881Speter                                             eol,
416251881Speter                                             FALSE /* repair */,
417251881Speter                                             kw,
418251881Speter                                             ! eib->ignore_keywords /* expand */,
419251881Speter                                             scratch_pool);
420251881Speter
421251881Speter  /* ###: use cancel func/baton in place of NULL/NULL below. */
422251881Speter  err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool);
423251881Speter
424251881Speter  if (!err && executable)
425251881Speter    err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool);
426251881Speter
427251881Speter  if (!err)
428251881Speter    err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool);
429251881Speter
430251881Speter  if (err)
431251881Speter    return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE,
432251881Speter                                                             scratch_pool));
433251881Speter
434251881Speter  /* Now that dst_tmp contains the translated data, do the atomic rename. */
435251881Speter  SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool));
436251881Speter
437251881Speter  if (eib->notify_func)
438251881Speter    {
439251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath,
440251881Speter                                      svn_wc_notify_update_add, scratch_pool);
441251881Speter      notify->kind = svn_node_file;
442251881Speter      (eib->notify_func)(eib->notify_baton, notify, scratch_pool);
443251881Speter    }
444251881Speter
445251881Speter  return SVN_NO_ERROR;
446251881Speter}
447251881Speter
448251881Speter/* Abstraction of open_root.
449251881Speter *
450251881Speter * Create PATH if it does not exist and is not obstructed, and invoke
451251881Speter * NOTIFY_FUNC with NOTIFY_BATON on PATH.
452251881Speter *
453251881Speter * If PATH exists but is a file, then error with SVN_ERR_WC_NOT_WORKING_COPY.
454251881Speter *
455251881Speter * If PATH is a already a directory, then error with
456251881Speter * SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
457251881Speter * export into PATH with no error.
458251881Speter */
459251881Speterstatic svn_error_t *
460251881Speteropen_root_internal(const char *path,
461251881Speter                   svn_boolean_t force,
462251881Speter                   svn_wc_notify_func2_t notify_func,
463251881Speter                   void *notify_baton,
464251881Speter                   apr_pool_t *pool)
465251881Speter{
466251881Speter  svn_node_kind_t kind;
467251881Speter
468251881Speter  SVN_ERR(svn_io_check_path(path, &kind, pool));
469251881Speter  if (kind == svn_node_none)
470251881Speter    SVN_ERR(svn_io_make_dir_recursively(path, pool));
471251881Speter  else if (kind == svn_node_file)
472251881Speter    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
473251881Speter                             _("'%s' exists and is not a directory"),
474251881Speter                             svn_dirent_local_style(path, pool));
475251881Speter  else if ((kind != svn_node_dir) || (! force))
476251881Speter    return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
477251881Speter                             _("'%s' already exists"),
478251881Speter                             svn_dirent_local_style(path, pool));
479251881Speter
480251881Speter  if (notify_func)
481251881Speter    {
482251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(path,
483251881Speter                                                     svn_wc_notify_update_add,
484251881Speter                                                     pool);
485251881Speter      notify->kind = svn_node_dir;
486251881Speter      (*notify_func)(notify_baton, notify, pool);
487251881Speter    }
488251881Speter
489251881Speter  return SVN_NO_ERROR;
490251881Speter}
491251881Speter
492251881Speter
493251881Speter/* ---------------------------------------------------------------------- */
494251881Speter
495251881Speter
496251881Speter/*** A dedicated 'export' editor, which does no .svn/ accounting.  ***/
497251881Speter
498251881Speter
499251881Speterstruct edit_baton
500251881Speter{
501251881Speter  const char *repos_root_url;
502251881Speter  const char *root_path;
503251881Speter  const char *root_url;
504251881Speter  svn_boolean_t force;
505251881Speter  svn_revnum_t *target_revision;
506251881Speter  apr_hash_t *externals;
507251881Speter  const char *native_eol;
508251881Speter  svn_boolean_t ignore_keywords;
509251881Speter
510251881Speter  svn_cancel_func_t cancel_func;
511251881Speter  void *cancel_baton;
512251881Speter  svn_wc_notify_func2_t notify_func;
513251881Speter  void *notify_baton;
514251881Speter};
515251881Speter
516251881Speter
517251881Speterstruct dir_baton
518251881Speter{
519251881Speter  struct edit_baton *edit_baton;
520251881Speter  const char *path;
521251881Speter};
522251881Speter
523251881Speter
524251881Speterstruct file_baton
525251881Speter{
526251881Speter  struct edit_baton *edit_baton;
527251881Speter
528251881Speter  const char *path;
529251881Speter  const char *tmppath;
530251881Speter
531251881Speter  /* We need to keep this around so we can explicitly close it in close_file,
532251881Speter     thus flushing its output to disk so we can copy and translate it. */
533251881Speter  svn_stream_t *tmp_stream;
534251881Speter
535251881Speter  /* The MD5 digest of the file's fulltext.  This is all zeros until
536251881Speter     the last textdelta window handler call returns. */
537251881Speter  unsigned char text_digest[APR_MD5_DIGESTSIZE];
538251881Speter
539251881Speter  /* The three svn: properties we might actually care about. */
540251881Speter  const svn_string_t *eol_style_val;
541251881Speter  const svn_string_t *keywords_val;
542251881Speter  const svn_string_t *executable_val;
543251881Speter  svn_boolean_t special;
544251881Speter
545251881Speter  /* Any keyword vals to be substituted */
546251881Speter  const char *revision;
547251881Speter  const char *url;
548251881Speter  const char *repos_root_url;
549251881Speter  const char *author;
550251881Speter  apr_time_t date;
551251881Speter
552251881Speter  /* Pool associated with this baton. */
553251881Speter  apr_pool_t *pool;
554251881Speter};
555251881Speter
556251881Speter
557251881Speterstruct handler_baton
558251881Speter{
559251881Speter  svn_txdelta_window_handler_t apply_handler;
560251881Speter  void *apply_baton;
561251881Speter  apr_pool_t *pool;
562251881Speter  const char *tmppath;
563251881Speter};
564251881Speter
565251881Speter
566251881Speterstatic svn_error_t *
567251881Speterset_target_revision(void *edit_baton,
568251881Speter                    svn_revnum_t target_revision,
569251881Speter                    apr_pool_t *pool)
570251881Speter{
571251881Speter  struct edit_baton *eb = edit_baton;
572251881Speter
573251881Speter  /* Stashing a target_revision in the baton */
574251881Speter  *(eb->target_revision) = target_revision;
575251881Speter  return SVN_NO_ERROR;
576251881Speter}
577251881Speter
578251881Speter
579251881Speter
580251881Speter/* Just ensure that the main export directory exists. */
581251881Speterstatic svn_error_t *
582251881Speteropen_root(void *edit_baton,
583251881Speter          svn_revnum_t base_revision,
584251881Speter          apr_pool_t *pool,
585251881Speter          void **root_baton)
586251881Speter{
587251881Speter  struct edit_baton *eb = edit_baton;
588251881Speter  struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
589251881Speter
590251881Speter  SVN_ERR(open_root_internal(eb->root_path, eb->force,
591251881Speter                             eb->notify_func, eb->notify_baton, pool));
592251881Speter
593251881Speter  /* Build our dir baton. */
594251881Speter  db->path = eb->root_path;
595251881Speter  db->edit_baton = eb;
596251881Speter  *root_baton = db;
597251881Speter
598251881Speter  return SVN_NO_ERROR;
599251881Speter}
600251881Speter
601251881Speter
602251881Speter/* Ensure the directory exists, and send feedback. */
603251881Speterstatic svn_error_t *
604251881Speteradd_directory(const char *path,
605251881Speter              void *parent_baton,
606251881Speter              const char *copyfrom_path,
607251881Speter              svn_revnum_t copyfrom_revision,
608251881Speter              apr_pool_t *pool,
609251881Speter              void **baton)
610251881Speter{
611251881Speter  struct dir_baton *pb = parent_baton;
612251881Speter  struct dir_baton *db = apr_pcalloc(pool, sizeof(*db));
613251881Speter  struct edit_baton *eb = pb->edit_baton;
614251881Speter  const char *full_path = svn_dirent_join(eb->root_path, path, pool);
615251881Speter  svn_node_kind_t kind;
616251881Speter
617251881Speter  SVN_ERR(svn_io_check_path(full_path, &kind, pool));
618251881Speter  if (kind == svn_node_none)
619251881Speter    SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, pool));
620251881Speter  else if (kind == svn_node_file)
621251881Speter    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
622251881Speter                             _("'%s' exists and is not a directory"),
623251881Speter                             svn_dirent_local_style(full_path, pool));
624251881Speter  else if (! (kind == svn_node_dir && eb->force))
625251881Speter    return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
626251881Speter                             _("'%s' already exists"),
627251881Speter                             svn_dirent_local_style(full_path, pool));
628251881Speter
629251881Speter  if (eb->notify_func)
630251881Speter    {
631251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
632251881Speter                                                     svn_wc_notify_update_add,
633251881Speter                                                     pool);
634251881Speter      notify->kind = svn_node_dir;
635251881Speter      (*eb->notify_func)(eb->notify_baton, notify, pool);
636251881Speter    }
637251881Speter
638251881Speter  /* Build our dir baton. */
639251881Speter  db->path = full_path;
640251881Speter  db->edit_baton = eb;
641251881Speter  *baton = db;
642251881Speter
643251881Speter  return SVN_NO_ERROR;
644251881Speter}
645251881Speter
646251881Speter
647251881Speter/* Build a file baton. */
648251881Speterstatic svn_error_t *
649251881Speteradd_file(const char *path,
650251881Speter         void *parent_baton,
651251881Speter         const char *copyfrom_path,
652251881Speter         svn_revnum_t copyfrom_revision,
653251881Speter         apr_pool_t *pool,
654251881Speter         void **baton)
655251881Speter{
656251881Speter  struct dir_baton *pb = parent_baton;
657251881Speter  struct edit_baton *eb = pb->edit_baton;
658251881Speter  struct file_baton *fb = apr_pcalloc(pool, sizeof(*fb));
659251881Speter  const char *full_path = svn_dirent_join(eb->root_path, path, pool);
660251881Speter
661251881Speter  /* PATH is not canonicalized, i.e. it may still contain spaces etc.
662251881Speter   * but EB->root_url is. */
663251881Speter  const char *full_url = svn_path_url_add_component2(eb->root_url,
664251881Speter                                                     path,
665251881Speter                                                     pool);
666251881Speter
667251881Speter  fb->edit_baton = eb;
668251881Speter  fb->path = full_path;
669251881Speter  fb->url = full_url;
670251881Speter  fb->repos_root_url = eb->repos_root_url;
671251881Speter  fb->pool = pool;
672251881Speter
673251881Speter  *baton = fb;
674251881Speter  return SVN_NO_ERROR;
675251881Speter}
676251881Speter
677251881Speter
678251881Speterstatic svn_error_t *
679251881Speterwindow_handler(svn_txdelta_window_t *window, void *baton)
680251881Speter{
681251881Speter  struct handler_baton *hb = baton;
682251881Speter  svn_error_t *err;
683251881Speter
684251881Speter  err = hb->apply_handler(window, hb->apply_baton);
685251881Speter  if (err)
686251881Speter    {
687251881Speter      /* We failed to apply the patch; clean up the temporary file.  */
688251881Speter      err = svn_error_compose_create(
689251881Speter                    err,
690251881Speter                    svn_io_remove_file2(hb->tmppath, TRUE, hb->pool));
691251881Speter    }
692251881Speter
693251881Speter  return svn_error_trace(err);
694251881Speter}
695251881Speter
696251881Speter
697251881Speter
698251881Speter/* Write incoming data into the tmpfile stream */
699251881Speterstatic svn_error_t *
700251881Speterapply_textdelta(void *file_baton,
701251881Speter                const char *base_checksum,
702251881Speter                apr_pool_t *pool,
703251881Speter                svn_txdelta_window_handler_t *handler,
704251881Speter                void **handler_baton)
705251881Speter{
706251881Speter  struct file_baton *fb = file_baton;
707251881Speter  struct handler_baton *hb = apr_palloc(pool, sizeof(*hb));
708251881Speter
709251881Speter  /* Create a temporary file in the same directory as the file. We're going
710251881Speter     to rename the thing into place when we're done. */
711251881Speter  SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
712251881Speter                                 svn_dirent_dirname(fb->path, pool),
713251881Speter                                 svn_io_file_del_none, fb->pool, fb->pool));
714251881Speter
715251881Speter  hb->pool = pool;
716251881Speter  hb->tmppath = fb->tmppath;
717251881Speter
718251881Speter  /* svn_txdelta_apply() closes the stream, but we want to close it in the
719251881Speter     close_file() function, so disown it here. */
720251881Speter  /* ### contrast to when we call svn_ra_get_file() which does NOT close the
721251881Speter     ### tmp_stream. we *should* be much more consistent! */
722251881Speter  svn_txdelta_apply(svn_stream_empty(pool),
723251881Speter                    svn_stream_disown(fb->tmp_stream, pool),
724251881Speter                    fb->text_digest, NULL, pool,
725251881Speter                    &hb->apply_handler, &hb->apply_baton);
726251881Speter
727251881Speter  *handler_baton = hb;
728251881Speter  *handler = window_handler;
729251881Speter  return SVN_NO_ERROR;
730251881Speter}
731251881Speter
732251881Speter
733251881Speterstatic svn_error_t *
734251881Speterchange_file_prop(void *file_baton,
735251881Speter                 const char *name,
736251881Speter                 const svn_string_t *value,
737251881Speter                 apr_pool_t *pool)
738251881Speter{
739251881Speter  struct file_baton *fb = file_baton;
740251881Speter
741251881Speter  if (! value)
742251881Speter    return SVN_NO_ERROR;
743251881Speter
744251881Speter  /* Store only the magic three properties. */
745251881Speter  if (strcmp(name, SVN_PROP_EOL_STYLE) == 0)
746251881Speter    fb->eol_style_val = svn_string_dup(value, fb->pool);
747251881Speter
748251881Speter  else if (! fb->edit_baton->ignore_keywords &&
749251881Speter           strcmp(name, SVN_PROP_KEYWORDS) == 0)
750251881Speter    fb->keywords_val = svn_string_dup(value, fb->pool);
751251881Speter
752251881Speter  else if (strcmp(name, SVN_PROP_EXECUTABLE) == 0)
753251881Speter    fb->executable_val = svn_string_dup(value, fb->pool);
754251881Speter
755251881Speter  /* Try to fill out the baton's keywords-structure too. */
756251881Speter  else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
757251881Speter    fb->revision = apr_pstrdup(fb->pool, value->data);
758251881Speter
759251881Speter  else if (strcmp(name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
760251881Speter    SVN_ERR(svn_time_from_cstring(&fb->date, value->data, fb->pool));
761251881Speter
762251881Speter  else if (strcmp(name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
763251881Speter    fb->author = apr_pstrdup(fb->pool, value->data);
764251881Speter
765251881Speter  else if (strcmp(name, SVN_PROP_SPECIAL) == 0)
766251881Speter    fb->special = TRUE;
767251881Speter
768251881Speter  return SVN_NO_ERROR;
769251881Speter}
770251881Speter
771251881Speter
772251881Speterstatic svn_error_t *
773251881Speterchange_dir_prop(void *dir_baton,
774251881Speter                const char *name,
775251881Speter                const svn_string_t *value,
776251881Speter                apr_pool_t *pool)
777251881Speter{
778251881Speter  struct dir_baton *db = dir_baton;
779251881Speter  struct edit_baton *eb = db->edit_baton;
780251881Speter
781251881Speter  if (value && (strcmp(name, SVN_PROP_EXTERNALS) == 0))
782251881Speter    SVN_ERR(add_externals(eb->externals, db->path, value));
783251881Speter
784251881Speter  return SVN_NO_ERROR;
785251881Speter}
786251881Speter
787251881Speter
788251881Speter/* Move the tmpfile to file, and send feedback. */
789251881Speterstatic svn_error_t *
790251881Speterclose_file(void *file_baton,
791251881Speter           const char *text_digest,
792251881Speter           apr_pool_t *pool)
793251881Speter{
794251881Speter  struct file_baton *fb = file_baton;
795251881Speter  struct edit_baton *eb = fb->edit_baton;
796251881Speter  svn_checksum_t *text_checksum;
797251881Speter  svn_checksum_t *actual_checksum;
798251881Speter
799251881Speter  /* Was a txdelta even sent? */
800251881Speter  if (! fb->tmppath)
801251881Speter    return SVN_NO_ERROR;
802251881Speter
803251881Speter  SVN_ERR(svn_stream_close(fb->tmp_stream));
804251881Speter
805251881Speter  SVN_ERR(svn_checksum_parse_hex(&text_checksum, svn_checksum_md5, text_digest,
806251881Speter                                 pool));
807251881Speter  actual_checksum = svn_checksum__from_digest_md5(fb->text_digest, pool);
808251881Speter
809251881Speter  /* Note that text_digest can be NULL when talking to certain repositories.
810251881Speter     In that case text_checksum will be NULL and the following match code
811251881Speter     will note that the checksums match */
812251881Speter  if (!svn_checksum_match(text_checksum, actual_checksum))
813251881Speter    return svn_checksum_mismatch_err(text_checksum, actual_checksum, pool,
814251881Speter                                     _("Checksum mismatch for '%s'"),
815251881Speter                                     svn_dirent_local_style(fb->path, pool));
816251881Speter
817251881Speter  if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
818251881Speter    {
819251881Speter      SVN_ERR(svn_io_file_rename(fb->tmppath, fb->path, pool));
820251881Speter    }
821251881Speter  else
822251881Speter    {
823251881Speter      svn_subst_eol_style_t style;
824251881Speter      const char *eol = NULL;
825251881Speter      svn_boolean_t repair = FALSE;
826251881Speter      apr_hash_t *final_kw = NULL;
827251881Speter
828251881Speter      if (fb->eol_style_val)
829251881Speter        {
830251881Speter          SVN_ERR(get_eol_style(&style, &eol, fb->eol_style_val->data,
831251881Speter                                eb->native_eol));
832251881Speter          repair = TRUE;
833251881Speter        }
834251881Speter
835251881Speter      if (fb->keywords_val)
836251881Speter        SVN_ERR(svn_subst_build_keywords3(&final_kw, fb->keywords_val->data,
837251881Speter                                          fb->revision, fb->url,
838251881Speter                                          fb->repos_root_url, fb->date,
839251881Speter                                          fb->author, pool));
840251881Speter
841251881Speter      SVN_ERR(svn_subst_copy_and_translate4(fb->tmppath, fb->path,
842251881Speter                                            eol, repair, final_kw,
843251881Speter                                            TRUE, /* expand */
844251881Speter                                            fb->special,
845251881Speter                                            eb->cancel_func, eb->cancel_baton,
846251881Speter                                            pool));
847251881Speter
848251881Speter      SVN_ERR(svn_io_remove_file2(fb->tmppath, FALSE, pool));
849251881Speter    }
850251881Speter
851251881Speter  if (fb->executable_val)
852251881Speter    SVN_ERR(svn_io_set_file_executable(fb->path, TRUE, FALSE, pool));
853251881Speter
854251881Speter  if (fb->date && (! fb->special))
855251881Speter    SVN_ERR(svn_io_set_file_affected_time(fb->date, fb->path, pool));
856251881Speter
857251881Speter  if (fb->edit_baton->notify_func)
858251881Speter    {
859251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(fb->path,
860251881Speter                                                     svn_wc_notify_update_add,
861251881Speter                                                     pool);
862251881Speter      notify->kind = svn_node_file;
863251881Speter      (*fb->edit_baton->notify_func)(fb->edit_baton->notify_baton, notify,
864251881Speter                                     pool);
865251881Speter    }
866251881Speter
867251881Speter  return SVN_NO_ERROR;
868251881Speter}
869251881Speter
870251881Speterstatic svn_error_t *
871251881Speterfetch_props_func(apr_hash_t **props,
872251881Speter                 void *baton,
873251881Speter                 const char *path,
874251881Speter                 svn_revnum_t base_revision,
875251881Speter                 apr_pool_t *result_pool,
876251881Speter                 apr_pool_t *scratch_pool)
877251881Speter{
878251881Speter  /* Always use empty props, since the node won't have pre-existing props
879251881Speter     (This is an export, remember?) */
880251881Speter  *props = apr_hash_make(result_pool);
881251881Speter
882251881Speter  return SVN_NO_ERROR;
883251881Speter}
884251881Speter
885251881Speterstatic svn_error_t *
886251881Speterfetch_base_func(const char **filename,
887251881Speter                void *baton,
888251881Speter                const char *path,
889251881Speter                svn_revnum_t base_revision,
890251881Speter                apr_pool_t *result_pool,
891251881Speter                apr_pool_t *scratch_pool)
892251881Speter{
893251881Speter  /* An export always gets text against the empty stream (i.e, full texts). */
894251881Speter  *filename = NULL;
895251881Speter
896251881Speter  return SVN_NO_ERROR;
897251881Speter}
898251881Speter
899251881Speterstatic svn_error_t *
900251881Speterget_editor_ev1(const svn_delta_editor_t **export_editor,
901251881Speter               void **edit_baton,
902251881Speter               struct edit_baton *eb,
903251881Speter               svn_client_ctx_t *ctx,
904251881Speter               apr_pool_t *result_pool,
905251881Speter               apr_pool_t *scratch_pool)
906251881Speter{
907251881Speter  svn_delta_editor_t *editor = svn_delta_default_editor(result_pool);
908251881Speter
909251881Speter  editor->set_target_revision = set_target_revision;
910251881Speter  editor->open_root = open_root;
911251881Speter  editor->add_directory = add_directory;
912251881Speter  editor->add_file = add_file;
913251881Speter  editor->apply_textdelta = apply_textdelta;
914251881Speter  editor->close_file = close_file;
915251881Speter  editor->change_file_prop = change_file_prop;
916251881Speter  editor->change_dir_prop = change_dir_prop;
917251881Speter
918251881Speter  SVN_ERR(svn_delta_get_cancellation_editor(ctx->cancel_func,
919251881Speter                                            ctx->cancel_baton,
920251881Speter                                            editor,
921251881Speter                                            eb,
922251881Speter                                            export_editor,
923251881Speter                                            edit_baton,
924251881Speter                                            result_pool));
925251881Speter
926251881Speter  return SVN_NO_ERROR;
927251881Speter}
928251881Speter
929251881Speter
930251881Speter/*** The Ev2 Implementation ***/
931251881Speter
932251881Speterstatic svn_error_t *
933251881Speteradd_file_ev2(void *baton,
934251881Speter             const char *relpath,
935251881Speter             const svn_checksum_t *checksum,
936251881Speter             svn_stream_t *contents,
937251881Speter             apr_hash_t *props,
938251881Speter             svn_revnum_t replaces_rev,
939251881Speter             apr_pool_t *scratch_pool)
940251881Speter{
941251881Speter  struct edit_baton *eb = baton;
942251881Speter  const char *full_path = svn_dirent_join(eb->root_path, relpath,
943251881Speter                                          scratch_pool);
944251881Speter  /* RELPATH is not canonicalized, i.e. it may still contain spaces etc.
945251881Speter   * but EB->root_url is. */
946251881Speter  const char *full_url = svn_path_url_add_component2(eb->root_url,
947251881Speter                                                     relpath,
948251881Speter                                                     scratch_pool);
949251881Speter  const svn_string_t *val;
950251881Speter  /* The four svn: properties we might actually care about. */
951251881Speter  const svn_string_t *eol_style_val = NULL;
952251881Speter  const svn_string_t *keywords_val = NULL;
953251881Speter  const svn_string_t *executable_val = NULL;
954251881Speter  svn_boolean_t special = FALSE;
955251881Speter  /* Any keyword vals to be substituted */
956251881Speter  const char *revision = NULL;
957251881Speter  const char *author = NULL;
958251881Speter  apr_time_t date = 0;
959251881Speter
960251881Speter  /* Look at any properties for additional information. */
961251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_EOL_STYLE)) )
962251881Speter    eol_style_val = val;
963251881Speter
964251881Speter  if ( !eb->ignore_keywords && (val = svn_hash_gets(props, SVN_PROP_KEYWORDS)) )
965251881Speter    keywords_val = val;
966251881Speter
967251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_EXECUTABLE)) )
968251881Speter    executable_val = val;
969251881Speter
970251881Speter  /* Try to fill out the baton's keywords-structure too. */
971251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_REV)) )
972251881Speter    revision = val->data;
973251881Speter
974251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_COMMITTED_DATE)) )
975251881Speter    SVN_ERR(svn_time_from_cstring(&date, val->data, scratch_pool));
976251881Speter
977251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_ENTRY_LAST_AUTHOR)) )
978251881Speter    author = val->data;
979251881Speter
980251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_SPECIAL)) )
981251881Speter    special = TRUE;
982251881Speter
983251881Speter  if (special)
984251881Speter    {
985251881Speter      svn_stream_t *tmp_stream;
986251881Speter
987251881Speter      SVN_ERR(svn_subst_create_specialfile(&tmp_stream, full_path,
988251881Speter                                           scratch_pool, scratch_pool));
989251881Speter      SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
990251881Speter                               eb->cancel_baton, scratch_pool));
991251881Speter    }
992251881Speter  else
993251881Speter    {
994251881Speter      svn_stream_t *tmp_stream;
995251881Speter      const char *tmppath;
996251881Speter
997251881Speter      /* Create a temporary file in the same directory as the file. We're going
998251881Speter         to rename the thing into place when we're done. */
999251881Speter      SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmppath,
1000251881Speter                                     svn_dirent_dirname(full_path,
1001251881Speter                                                        scratch_pool),
1002251881Speter                                     svn_io_file_del_none,
1003251881Speter                                     scratch_pool, scratch_pool));
1004251881Speter
1005251881Speter      /* Possibly wrap the stream to be translated, as dictated by
1006251881Speter         the props. */
1007251881Speter      if (eol_style_val || keywords_val)
1008251881Speter        {
1009251881Speter          svn_subst_eol_style_t style;
1010251881Speter          const char *eol = NULL;
1011251881Speter          svn_boolean_t repair = FALSE;
1012251881Speter          apr_hash_t *final_kw = NULL;
1013251881Speter
1014251881Speter          if (eol_style_val)
1015251881Speter            {
1016251881Speter              SVN_ERR(get_eol_style(&style, &eol, eol_style_val->data,
1017251881Speter                                    eb->native_eol));
1018251881Speter              repair = TRUE;
1019251881Speter            }
1020251881Speter
1021251881Speter          if (keywords_val)
1022251881Speter            SVN_ERR(svn_subst_build_keywords3(&final_kw, keywords_val->data,
1023251881Speter                                              revision, full_url,
1024251881Speter                                              eb->repos_root_url,
1025251881Speter                                              date, author, scratch_pool));
1026251881Speter
1027251881Speter          /* Writing through a translated stream is more efficient than
1028251881Speter             reading through one, so we wrap TMP_STREAM and not CONTENTS. */
1029251881Speter          tmp_stream = svn_subst_stream_translated(tmp_stream, eol, repair,
1030251881Speter                                                   final_kw, TRUE, /* expand */
1031251881Speter                                                   scratch_pool);
1032251881Speter        }
1033251881Speter
1034251881Speter      SVN_ERR(svn_stream_copy3(contents, tmp_stream, eb->cancel_func,
1035251881Speter                               eb->cancel_baton, scratch_pool));
1036251881Speter
1037251881Speter      /* Move the file into place. */
1038251881Speter      SVN_ERR(svn_io_file_rename(tmppath, full_path, scratch_pool));
1039251881Speter    }
1040251881Speter
1041251881Speter  if (executable_val)
1042251881Speter    SVN_ERR(svn_io_set_file_executable(full_path, TRUE, FALSE, scratch_pool));
1043251881Speter
1044251881Speter  if (date && (! special))
1045251881Speter    SVN_ERR(svn_io_set_file_affected_time(date, full_path, scratch_pool));
1046251881Speter
1047251881Speter  if (eb->notify_func)
1048251881Speter    {
1049251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1050251881Speter                                                     svn_wc_notify_update_add,
1051251881Speter                                                     scratch_pool);
1052251881Speter      notify->kind = svn_node_file;
1053251881Speter      (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1054251881Speter    }
1055251881Speter
1056251881Speter  return SVN_NO_ERROR;
1057251881Speter}
1058251881Speter
1059251881Speterstatic svn_error_t *
1060251881Speteradd_directory_ev2(void *baton,
1061251881Speter                  const char *relpath,
1062251881Speter                  const apr_array_header_t *children,
1063251881Speter                  apr_hash_t *props,
1064251881Speter                  svn_revnum_t replaces_rev,
1065251881Speter                  apr_pool_t *scratch_pool)
1066251881Speter{
1067251881Speter  struct edit_baton *eb = baton;
1068251881Speter  svn_node_kind_t kind;
1069251881Speter  const char *full_path = svn_dirent_join(eb->root_path, relpath,
1070251881Speter                                          scratch_pool);
1071251881Speter  svn_string_t *val;
1072251881Speter
1073251881Speter  SVN_ERR(svn_io_check_path(full_path, &kind, scratch_pool));
1074251881Speter  if (kind == svn_node_none)
1075251881Speter    SVN_ERR(svn_io_dir_make(full_path, APR_OS_DEFAULT, scratch_pool));
1076251881Speter  else if (kind == svn_node_file)
1077251881Speter    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1078251881Speter                             _("'%s' exists and is not a directory"),
1079251881Speter                             svn_dirent_local_style(full_path, scratch_pool));
1080251881Speter  else if (! (kind == svn_node_dir && eb->force))
1081251881Speter    return svn_error_createf(SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1082251881Speter                             _("'%s' already exists"),
1083251881Speter                             svn_dirent_local_style(full_path, scratch_pool));
1084251881Speter
1085251881Speter  if ( (val = svn_hash_gets(props, SVN_PROP_EXTERNALS)) )
1086251881Speter    SVN_ERR(add_externals(eb->externals, full_path, val));
1087251881Speter
1088251881Speter  if (eb->notify_func)
1089251881Speter    {
1090251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(full_path,
1091251881Speter                                                     svn_wc_notify_update_add,
1092251881Speter                                                     scratch_pool);
1093251881Speter      notify->kind = svn_node_dir;
1094251881Speter      (*eb->notify_func)(eb->notify_baton, notify, scratch_pool);
1095251881Speter    }
1096251881Speter
1097251881Speter  return SVN_NO_ERROR;
1098251881Speter}
1099251881Speter
1100251881Speterstatic svn_error_t *
1101251881Spetertarget_revision_func(void *baton,
1102251881Speter                     svn_revnum_t target_revision,
1103251881Speter                     apr_pool_t *scratch_pool)
1104251881Speter{
1105251881Speter  struct edit_baton *eb = baton;
1106251881Speter
1107251881Speter  *eb->target_revision = target_revision;
1108251881Speter
1109251881Speter  return SVN_NO_ERROR;
1110251881Speter}
1111251881Speter
1112251881Speterstatic svn_error_t *
1113251881Speterget_editor_ev2(const svn_delta_editor_t **export_editor,
1114251881Speter               void **edit_baton,
1115251881Speter               struct edit_baton *eb,
1116251881Speter               svn_client_ctx_t *ctx,
1117251881Speter               apr_pool_t *result_pool,
1118251881Speter               apr_pool_t *scratch_pool)
1119251881Speter{
1120251881Speter  svn_editor_t *editor;
1121251881Speter  struct svn_delta__extra_baton *exb = apr_pcalloc(result_pool, sizeof(*exb));
1122251881Speter  svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
1123251881Speter                                              sizeof(*found_abs_paths));
1124251881Speter
1125251881Speter  exb->baton = eb;
1126251881Speter  exb->target_revision = target_revision_func;
1127251881Speter
1128251881Speter  SVN_ERR(svn_editor_create(&editor, eb, ctx->cancel_func, ctx->cancel_baton,
1129251881Speter                            result_pool, scratch_pool));
1130251881Speter  SVN_ERR(svn_editor_setcb_add_directory(editor, add_directory_ev2,
1131251881Speter                                         scratch_pool));
1132251881Speter  SVN_ERR(svn_editor_setcb_add_file(editor, add_file_ev2, scratch_pool));
1133251881Speter
1134251881Speter  *found_abs_paths = TRUE;
1135251881Speter
1136251881Speter  SVN_ERR(svn_delta__delta_from_editor(export_editor, edit_baton,
1137251881Speter                                       editor, NULL, NULL, found_abs_paths,
1138251881Speter                                       NULL, NULL,
1139251881Speter                                       fetch_props_func, eb,
1140251881Speter                                       fetch_base_func, eb,
1141251881Speter                                       exb, result_pool));
1142251881Speter
1143251881Speter  /* Create the root of the export. */
1144251881Speter  SVN_ERR(open_root_internal(eb->root_path, eb->force, eb->notify_func,
1145251881Speter                             eb->notify_baton, scratch_pool));
1146251881Speter
1147251881Speter  return SVN_NO_ERROR;
1148251881Speter}
1149251881Speter
1150251881Speterstatic svn_error_t *
1151251881Speterexport_file_ev2(const char *from_path_or_url,
1152251881Speter                const char *to_path,
1153251881Speter                struct edit_baton *eb,
1154251881Speter                svn_client__pathrev_t *loc,
1155251881Speter                svn_ra_session_t *ra_session,
1156251881Speter                svn_boolean_t overwrite,
1157251881Speter                apr_pool_t *scratch_pool)
1158251881Speter{
1159251881Speter  svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1160251881Speter  apr_hash_t *props;
1161251881Speter  svn_stream_t *tmp_stream;
1162251881Speter  svn_node_kind_t to_kind;
1163251881Speter
1164251881Speter  if (svn_path_is_empty(to_path))
1165251881Speter    {
1166251881Speter      if (from_is_url)
1167251881Speter        to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1168251881Speter      else
1169251881Speter        to_path = svn_dirent_basename(from_path_or_url, NULL);
1170251881Speter      eb->root_path = to_path;
1171251881Speter    }
1172251881Speter  else
1173251881Speter    {
1174251881Speter      SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1175251881Speter                                     from_is_url, scratch_pool));
1176251881Speter      eb->root_path = to_path;
1177251881Speter    }
1178251881Speter
1179251881Speter  SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1180251881Speter
1181251881Speter  if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1182251881Speter      ! overwrite)
1183251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1184251881Speter                             _("Destination file '%s' exists, and "
1185251881Speter                               "will not be overwritten unless forced"),
1186251881Speter                             svn_dirent_local_style(to_path, scratch_pool));
1187251881Speter  else if (to_kind == svn_node_dir)
1188251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1189251881Speter                             _("Destination '%s' exists. Cannot "
1190251881Speter                               "overwrite directory with non-directory"),
1191251881Speter                             svn_dirent_local_style(to_path, scratch_pool));
1192251881Speter
1193251881Speter  tmp_stream = svn_stream_buffered(scratch_pool);
1194251881Speter
1195251881Speter  SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1196251881Speter                          tmp_stream, NULL, &props, scratch_pool));
1197251881Speter
1198251881Speter  /* Since you cannot actually root an editor at a file, we manually drive
1199251881Speter   * a function of our editor. */
1200251881Speter  SVN_ERR(add_file_ev2(eb, "", NULL, tmp_stream, props, SVN_INVALID_REVNUM,
1201251881Speter                       scratch_pool));
1202251881Speter
1203251881Speter  return SVN_NO_ERROR;
1204251881Speter}
1205251881Speter
1206251881Speterstatic svn_error_t *
1207251881Speterexport_file(const char *from_path_or_url,
1208251881Speter            const char *to_path,
1209251881Speter            struct edit_baton *eb,
1210251881Speter            svn_client__pathrev_t *loc,
1211251881Speter            svn_ra_session_t *ra_session,
1212251881Speter            svn_boolean_t overwrite,
1213251881Speter            apr_pool_t *scratch_pool)
1214251881Speter{
1215251881Speter  apr_hash_t *props;
1216251881Speter  apr_hash_index_t *hi;
1217251881Speter  struct file_baton *fb = apr_pcalloc(scratch_pool, sizeof(*fb));
1218251881Speter  svn_node_kind_t to_kind;
1219251881Speter  svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1220251881Speter
1221251881Speter  if (svn_path_is_empty(to_path))
1222251881Speter    {
1223251881Speter      if (from_is_url)
1224251881Speter        to_path = svn_uri_basename(from_path_or_url, scratch_pool);
1225251881Speter      else
1226251881Speter        to_path = svn_dirent_basename(from_path_or_url, NULL);
1227251881Speter      eb->root_path = to_path;
1228251881Speter    }
1229251881Speter  else
1230251881Speter    {
1231251881Speter      SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url,
1232251881Speter                                     from_is_url, scratch_pool));
1233251881Speter      eb->root_path = to_path;
1234251881Speter    }
1235251881Speter
1236251881Speter  SVN_ERR(svn_io_check_path(to_path, &to_kind, scratch_pool));
1237251881Speter
1238251881Speter  if ((to_kind == svn_node_file || to_kind == svn_node_unknown) &&
1239251881Speter      ! overwrite)
1240251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1241251881Speter                             _("Destination file '%s' exists, and "
1242251881Speter                               "will not be overwritten unless forced"),
1243251881Speter                             svn_dirent_local_style(to_path, scratch_pool));
1244251881Speter  else if (to_kind == svn_node_dir)
1245251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1246251881Speter                             _("Destination '%s' exists. Cannot "
1247251881Speter                               "overwrite directory with non-directory"),
1248251881Speter                             svn_dirent_local_style(to_path, scratch_pool));
1249251881Speter
1250251881Speter  /* Since you cannot actually root an editor at a file, we
1251251881Speter   * manually drive a few functions of our editor. */
1252251881Speter
1253251881Speter  /* This is the equivalent of a parentless add_file(). */
1254251881Speter  fb->edit_baton = eb;
1255251881Speter  fb->path = eb->root_path;
1256251881Speter  fb->url = eb->root_url;
1257251881Speter  fb->pool = scratch_pool;
1258251881Speter  fb->repos_root_url = eb->repos_root_url;
1259251881Speter
1260251881Speter  /* Copied from apply_textdelta(). */
1261251881Speter  SVN_ERR(svn_stream_open_unique(&fb->tmp_stream, &fb->tmppath,
1262251881Speter                                 svn_dirent_dirname(fb->path, scratch_pool),
1263251881Speter                                 svn_io_file_del_none,
1264251881Speter                                 fb->pool, fb->pool));
1265251881Speter
1266251881Speter  /* Step outside the editor-likeness for a moment, to actually talk
1267251881Speter   * to the repository. */
1268251881Speter  /* ### note: the stream will not be closed */
1269251881Speter  SVN_ERR(svn_ra_get_file(ra_session, "", loc->rev,
1270251881Speter                          fb->tmp_stream,
1271251881Speter                          NULL, &props, scratch_pool));
1272251881Speter
1273251881Speter  /* Push the props into change_file_prop(), to update the file_baton
1274251881Speter   * with information. */
1275251881Speter  for (hi = apr_hash_first(scratch_pool, props); hi; hi = apr_hash_next(hi))
1276251881Speter    {
1277251881Speter      const char *propname = svn__apr_hash_index_key(hi);
1278251881Speter      const svn_string_t *propval = svn__apr_hash_index_val(hi);
1279251881Speter
1280251881Speter      SVN_ERR(change_file_prop(fb, propname, propval, scratch_pool));
1281251881Speter    }
1282251881Speter
1283251881Speter  /* And now just use close_file() to do all the keyword and EOL
1284251881Speter   * work, and put the file into place. */
1285251881Speter  SVN_ERR(close_file(fb, NULL, scratch_pool));
1286251881Speter
1287251881Speter  return SVN_NO_ERROR;
1288251881Speter}
1289251881Speter
1290251881Speterstatic svn_error_t *
1291251881Speterexport_directory(const char *from_path_or_url,
1292251881Speter                 const char *to_path,
1293251881Speter                 struct edit_baton *eb,
1294251881Speter                 svn_client__pathrev_t *loc,
1295251881Speter                 svn_ra_session_t *ra_session,
1296251881Speter                 svn_boolean_t overwrite,
1297251881Speter                 svn_boolean_t ignore_externals,
1298251881Speter                 svn_boolean_t ignore_keywords,
1299251881Speter                 svn_depth_t depth,
1300251881Speter                 const char *native_eol,
1301251881Speter                 svn_client_ctx_t *ctx,
1302251881Speter                 apr_pool_t *scratch_pool)
1303251881Speter{
1304251881Speter  void *edit_baton;
1305251881Speter  const svn_delta_editor_t *export_editor;
1306251881Speter  const svn_ra_reporter3_t *reporter;
1307251881Speter  void *report_baton;
1308251881Speter  svn_node_kind_t kind;
1309251881Speter
1310251881Speter  if (!ENABLE_EV2_IMPL)
1311251881Speter    SVN_ERR(get_editor_ev1(&export_editor, &edit_baton, eb, ctx,
1312251881Speter                           scratch_pool, scratch_pool));
1313251881Speter  else
1314251881Speter    SVN_ERR(get_editor_ev2(&export_editor, &edit_baton, eb, ctx,
1315251881Speter                           scratch_pool, scratch_pool));
1316251881Speter
1317251881Speter  /* Manufacture a basic 'report' to the update reporter. */
1318251881Speter  SVN_ERR(svn_ra_do_update3(ra_session,
1319251881Speter                            &reporter, &report_baton,
1320251881Speter                            loc->rev,
1321251881Speter                            "", /* no sub-target */
1322251881Speter                            depth,
1323251881Speter                            FALSE, /* don't want copyfrom-args */
1324251881Speter                            FALSE, /* don't want ignore_ancestry */
1325251881Speter                            export_editor, edit_baton,
1326251881Speter                            scratch_pool, scratch_pool));
1327251881Speter
1328251881Speter  SVN_ERR(reporter->set_path(report_baton, "", loc->rev,
1329251881Speter                             /* Depth is irrelevant, as we're
1330251881Speter                                passing start_empty=TRUE anyway. */
1331251881Speter                             svn_depth_infinity,
1332251881Speter                             TRUE, /* "help, my dir is empty!" */
1333251881Speter                             NULL, scratch_pool));
1334251881Speter
1335251881Speter  SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1336251881Speter
1337251881Speter  /* Special case: Due to our sly export/checkout method of updating an
1338251881Speter   * empty directory, no target will have been created if the exported
1339251881Speter   * item is itself an empty directory (export_editor->open_root never
1340251881Speter   * gets called, because there are no "changes" to make to the empty
1341251881Speter   * dir we reported to the repository).
1342251881Speter   *
1343251881Speter   * So we just create the empty dir manually; but we do it via
1344251881Speter   * open_root_internal(), in order to get proper notification.
1345251881Speter   */
1346251881Speter  SVN_ERR(svn_io_check_path(to_path, &kind, scratch_pool));
1347251881Speter  if (kind == svn_node_none)
1348251881Speter    SVN_ERR(open_root_internal
1349251881Speter            (to_path, overwrite, ctx->notify_func2,
1350251881Speter             ctx->notify_baton2, scratch_pool));
1351251881Speter
1352251881Speter  if (! ignore_externals && depth == svn_depth_infinity)
1353251881Speter    {
1354251881Speter      const char *to_abspath;
1355251881Speter
1356251881Speter      SVN_ERR(svn_dirent_get_absolute(&to_abspath, to_path, scratch_pool));
1357251881Speter      SVN_ERR(svn_client__export_externals(eb->externals,
1358251881Speter                                           from_path_or_url,
1359251881Speter                                           to_abspath, eb->repos_root_url,
1360251881Speter                                           depth, native_eol,
1361251881Speter                                           ignore_keywords,
1362251881Speter                                           ctx, scratch_pool));
1363251881Speter    }
1364251881Speter
1365251881Speter  return SVN_NO_ERROR;
1366251881Speter}
1367251881Speter
1368251881Speter
1369251881Speter
1370251881Speter/*** Public Interfaces ***/
1371251881Speter
1372251881Spetersvn_error_t *
1373251881Spetersvn_client_export5(svn_revnum_t *result_rev,
1374251881Speter                   const char *from_path_or_url,
1375251881Speter                   const char *to_path,
1376251881Speter                   const svn_opt_revision_t *peg_revision,
1377251881Speter                   const svn_opt_revision_t *revision,
1378251881Speter                   svn_boolean_t overwrite,
1379251881Speter                   svn_boolean_t ignore_externals,
1380251881Speter                   svn_boolean_t ignore_keywords,
1381251881Speter                   svn_depth_t depth,
1382251881Speter                   const char *native_eol,
1383251881Speter                   svn_client_ctx_t *ctx,
1384251881Speter                   apr_pool_t *pool)
1385251881Speter{
1386251881Speter  svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
1387251881Speter  svn_boolean_t from_is_url = svn_path_is_url(from_path_or_url);
1388251881Speter
1389251881Speter  SVN_ERR_ASSERT(peg_revision != NULL);
1390251881Speter  SVN_ERR_ASSERT(revision != NULL);
1391251881Speter
1392251881Speter  if (svn_path_is_url(to_path))
1393251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1394251881Speter                             _("'%s' is not a local path"), to_path);
1395251881Speter
1396251881Speter  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1397251881Speter                                                        from_path_or_url);
1398251881Speter  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1399251881Speter
1400251881Speter  if (from_is_url || ! SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind))
1401251881Speter    {
1402251881Speter      svn_client__pathrev_t *loc;
1403251881Speter      svn_ra_session_t *ra_session;
1404251881Speter      svn_node_kind_t kind;
1405251881Speter      struct edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
1406251881Speter
1407251881Speter      /* Get the RA connection. */
1408251881Speter      SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1409251881Speter                                                from_path_or_url, NULL,
1410251881Speter                                                peg_revision,
1411251881Speter                                                revision, ctx, pool));
1412251881Speter
1413251881Speter      SVN_ERR(svn_ra_get_repos_root2(ra_session, &eb->repos_root_url, pool));
1414251881Speter      eb->root_path = to_path;
1415251881Speter      eb->root_url = loc->url;
1416251881Speter      eb->force = overwrite;
1417251881Speter      eb->target_revision = &edit_revision;
1418251881Speter      eb->externals = apr_hash_make(pool);
1419251881Speter      eb->native_eol = native_eol;
1420251881Speter      eb->ignore_keywords = ignore_keywords;
1421251881Speter      eb->cancel_func = ctx->cancel_func;
1422251881Speter      eb->cancel_baton = ctx->cancel_baton;
1423251881Speter      eb->notify_func = ctx->notify_func2;
1424251881Speter      eb->notify_baton = ctx->notify_baton2;
1425251881Speter
1426251881Speter      SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, pool));
1427251881Speter
1428251881Speter      if (kind == svn_node_file)
1429251881Speter        {
1430251881Speter          if (!ENABLE_EV2_IMPL)
1431251881Speter            SVN_ERR(export_file(from_path_or_url, to_path, eb, loc, ra_session,
1432251881Speter                                overwrite, pool));
1433251881Speter          else
1434251881Speter            SVN_ERR(export_file_ev2(from_path_or_url, to_path, eb, loc,
1435251881Speter                                    ra_session, overwrite, pool));
1436251881Speter        }
1437251881Speter      else if (kind == svn_node_dir)
1438251881Speter        {
1439251881Speter          SVN_ERR(export_directory(from_path_or_url, to_path,
1440251881Speter                                   eb, loc, ra_session, overwrite,
1441251881Speter                                   ignore_externals, ignore_keywords, depth,
1442251881Speter                                   native_eol, ctx, pool));
1443251881Speter        }
1444251881Speter      else if (kind == svn_node_none)
1445251881Speter        {
1446251881Speter          return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
1447251881Speter                                   _("URL '%s' doesn't exist"),
1448251881Speter                                   from_path_or_url);
1449251881Speter        }
1450251881Speter      /* kind == svn_node_unknown not handled */
1451251881Speter    }
1452251881Speter  else
1453251881Speter    {
1454251881Speter      struct export_info_baton eib;
1455251881Speter      svn_node_kind_t kind;
1456251881Speter      apr_hash_t *externals = NULL;
1457251881Speter
1458251881Speter      /* This is a working copy export. */
1459251881Speter      /* just copy the contents of the working copy into the target path. */
1460251881Speter      SVN_ERR(svn_dirent_get_absolute(&from_path_or_url, from_path_or_url,
1461251881Speter                                      pool));
1462251881Speter
1463251881Speter      SVN_ERR(svn_dirent_get_absolute(&to_path, to_path, pool));
1464251881Speter
1465251881Speter      SVN_ERR(svn_io_check_path(from_path_or_url, &kind, pool));
1466251881Speter
1467251881Speter      /* ### [JAF] If something already exists on disk at the destination path,
1468251881Speter       * the behaviour depends on the node kinds of the source and destination
1469251881Speter       * and on the FORCE flag.  The intention (I guess) is to follow the
1470251881Speter       * semantics of svn_client_export5(), semantics that are not fully
1471251881Speter       * documented but would be something like:
1472251881Speter       *
1473251881Speter       * -----------+---------------------------------------------------------
1474251881Speter       *        Src | DIR                 FILE                SPECIAL
1475251881Speter       * Dst (disk) +---------------------------------------------------------
1476251881Speter       * NONE       | simple copy         simple copy         (as src=file?)
1477251881Speter       * DIR        | merge if forced [2] inside if root [1]  (as src=file?)
1478251881Speter       * FILE       | err                 overwr if forced[3] (as src=file?)
1479251881Speter       * SPECIAL    | ???                 ???                 ???
1480251881Speter       * -----------+---------------------------------------------------------
1481251881Speter       *
1482251881Speter       * [1] FILE onto DIR case: If this file is the root of the copy and thus
1483251881Speter       *     the only node to be copied, then copy it as a child of the
1484251881Speter       *     directory TO, applying these same rules again except that if this
1485251881Speter       *     case occurs again (the child path is already a directory) then
1486251881Speter       *     error out.  If this file is not the root of the copy (it is
1487251881Speter       *     reached by recursion), then error out.
1488251881Speter       *
1489251881Speter       * [2] DIR onto DIR case.  If the 'FORCE' flag is true then copy the
1490251881Speter       *     source's children inside the target dir, else error out.  When
1491251881Speter       *     copying the children, apply the same set of rules, except in the
1492251881Speter       *     FILE onto DIR case error out like in note [1].
1493251881Speter       *
1494251881Speter       * [3] If the 'FORCE' flag is true then overwrite the destination file
1495251881Speter       *     else error out.
1496251881Speter       *
1497251881Speter       * The reality (apparently, looking at the code) is somewhat different.
1498251881Speter       * For a start, to detect the source kind, it looks at what is on disk
1499251881Speter       * rather than the versioned working or base node.
1500251881Speter       */
1501251881Speter      if (kind == svn_node_file)
1502251881Speter        SVN_ERR(append_basename_if_dir(&to_path, from_path_or_url, FALSE,
1503251881Speter                                       pool));
1504251881Speter
1505251881Speter      eib.to_path = to_path;
1506251881Speter      eib.revision = revision;
1507251881Speter      eib.overwrite = overwrite;
1508251881Speter      eib.ignore_keywords = ignore_keywords;
1509251881Speter      eib.wc_ctx = ctx->wc_ctx;
1510251881Speter      eib.native_eol = native_eol;
1511251881Speter      eib.notify_func = ctx->notify_func2;;
1512251881Speter      eib.notify_baton = ctx->notify_baton2;
1513251881Speter      eib.origin_abspath = from_path_or_url;
1514251881Speter      eib.exported = FALSE;
1515251881Speter
1516251881Speter      SVN_ERR(svn_wc_walk_status(ctx->wc_ctx, from_path_or_url, depth,
1517251881Speter                                 TRUE /* get_all */,
1518251881Speter                                 TRUE /* no_ignore */,
1519251881Speter                                 FALSE /* ignore_text_mods */,
1520251881Speter                                 NULL,
1521251881Speter                                 export_node, &eib,
1522251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
1523251881Speter                                 pool));
1524251881Speter
1525251881Speter      if (!eib.exported)
1526251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1527251881Speter                                 _("The node '%s' was not found."),
1528251881Speter                                 svn_dirent_local_style(from_path_or_url,
1529251881Speter                                                        pool));
1530251881Speter
1531251881Speter      if (!ignore_externals)
1532251881Speter        SVN_ERR(svn_wc__externals_defined_below(&externals, ctx->wc_ctx,
1533251881Speter                                                from_path_or_url,
1534251881Speter                                                pool, pool));
1535251881Speter
1536251881Speter      if (externals && apr_hash_count(externals))
1537251881Speter        {
1538251881Speter          apr_pool_t *iterpool = svn_pool_create(pool);
1539251881Speter          apr_hash_index_t *hi;
1540251881Speter
1541251881Speter          for (hi = apr_hash_first(pool, externals);
1542251881Speter               hi;
1543251881Speter               hi = apr_hash_next(hi))
1544251881Speter            {
1545251881Speter              const char *external_abspath = svn__apr_hash_index_key(hi);
1546251881Speter              const char *relpath;
1547251881Speter              const char *target_abspath;
1548251881Speter
1549251881Speter              svn_pool_clear(iterpool);
1550251881Speter
1551251881Speter              relpath = svn_dirent_skip_ancestor(from_path_or_url,
1552251881Speter                                                 external_abspath);
1553251881Speter
1554251881Speter              target_abspath = svn_dirent_join(to_path, relpath,
1555251881Speter                                                         iterpool);
1556251881Speter
1557251881Speter              /* Ensure that the parent directory exists */
1558251881Speter              SVN_ERR(svn_io_make_dir_recursively(
1559251881Speter                            svn_dirent_dirname(target_abspath, iterpool),
1560251881Speter                            iterpool));
1561251881Speter
1562251881Speter              SVN_ERR(svn_client_export5(NULL,
1563251881Speter                                         svn_dirent_join(from_path_or_url,
1564251881Speter                                                         relpath,
1565251881Speter                                                         iterpool),
1566251881Speter                                         target_abspath,
1567251881Speter                                         peg_revision, revision,
1568251881Speter                                         TRUE, ignore_externals,
1569251881Speter                                         ignore_keywords, depth, native_eol,
1570251881Speter                                         ctx, iterpool));
1571251881Speter            }
1572251881Speter
1573251881Speter          svn_pool_destroy(iterpool);
1574251881Speter        }
1575251881Speter    }
1576251881Speter
1577251881Speter
1578251881Speter  if (ctx->notify_func2)
1579251881Speter    {
1580251881Speter      svn_wc_notify_t *notify
1581251881Speter        = svn_wc_create_notify(to_path,
1582251881Speter                               svn_wc_notify_update_completed, pool);
1583251881Speter      notify->revision = edit_revision;
1584251881Speter      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1585251881Speter    }
1586251881Speter
1587251881Speter  if (result_rev)
1588251881Speter    *result_rev = edit_revision;
1589251881Speter
1590251881Speter  return SVN_NO_ERROR;
1591251881Speter}
1592269847Speter
1593