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