2
* export.c: export a tree.
4
* ====================================================================
5
* Copyright (c) 2000-2004 CollabNet. All rights reserved.
7
* This software is licensed as described in the file COPYING, which
8
* you should have received as part of this distribution. The terms
9
* are also available at http://subversion.tigris.org/license-1.html.
10
* If newer versions of this license are posted there, you may use a
11
* newer version instead, at your option.
13
* This software consists of voluntary contributions made by many
14
* individuals. For exact contribution history, see the revision
15
* history and logs, available at http://subversion.tigris.org/.
16
* ====================================================================
19
/* ==================================================================== */
25
#include <apr_file_io.h>
27
#include "svn_types.h"
29
#include "svn_client.h"
30
#include "svn_string.h"
31
#include "svn_error.h"
33
#include "svn_pools.h"
34
#include "svn_subst.h"
37
#include "svn_props.h"
40
#include "svn_private_config.h"
45
/* Add EXTERNALS_PROP_VAL for the export destination path PATH to
48
add_externals (apr_hash_t *externals,
50
const svn_string_t *externals_prop_val)
52
apr_pool_t *pool = apr_hash_pool_get (externals);
54
if (! externals_prop_val)
57
apr_hash_set (externals,
58
apr_pstrdup (pool, path),
60
apr_pstrmemdup (pool, externals_prop_val->data,
61
externals_prop_val->len));
64
/* Helper function that gets the eol style and optionally overrides the
65
EOL marker for files marked as native with the EOL marker matching
66
the string specified in requested_value which is of the same format
67
as the svn:eol-style property values. */
69
get_eol_style (svn_subst_eol_style_t *style,
72
const char *requested_value)
74
svn_subst_eol_style_from_value (style, eol, value);
75
if (requested_value && *style == svn_subst_eol_style_native)
77
svn_subst_eol_style_t requested_style;
78
const char *requested_eol;
80
svn_subst_eol_style_from_value (&requested_style, &requested_eol,
83
if (requested_style == svn_subst_eol_style_fixed)
86
return svn_error_createf (SVN_ERR_IO_UNKNOWN_EOL, NULL,
87
_("'%s' is not a valid EOL value"),
94
copy_one_versioned_file (const char *from,
96
svn_wc_adm_access_t *adm_access,
97
svn_opt_revision_t *revision,
98
const char *native_eol,
101
const svn_wc_entry_t *entry;
102
svn_subst_keywords_t kw = { 0 };
103
svn_subst_eol_style_t style;
106
svn_string_t *eol_style, *keywords, *executable, *externals, *special;
107
const char *eol = NULL;
108
svn_boolean_t local_mod = FALSE;
111
SVN_ERR (svn_wc_entry (&entry, from, adm_access, FALSE, pool));
113
/* Only export 'added' files when the revision is WORKING.
114
Otherwise, skip the 'added' files, since they didn't exist
115
in the BASE revision and don't have an associated text-base.
117
Don't export 'deleted' files and directories unless it's a
118
revision other than WORKING. These files and directories
119
don't really exists in WORKING. */
120
if ((revision->kind != svn_opt_revision_working &&
121
entry->schedule == svn_wc_schedule_add) ||
122
(revision->kind == svn_opt_revision_working &&
123
entry->schedule == svn_wc_schedule_delete))
126
if (revision->kind != svn_opt_revision_working)
128
SVN_ERR (svn_wc_get_pristine_copy_path (from, &base,
130
SVN_ERR (svn_wc_get_prop_diffs (NULL, &props, from,
135
svn_wc_status2_t *status;
138
SVN_ERR (svn_wc_prop_list (&props, from,
140
SVN_ERR (svn_wc_status2 (&status, from,
142
if (status->text_status != svn_wc_status_normal)
146
eol_style = apr_hash_get (props, SVN_PROP_EOL_STYLE,
147
APR_HASH_KEY_STRING);
148
keywords = apr_hash_get (props, SVN_PROP_KEYWORDS,
149
APR_HASH_KEY_STRING);
150
executable = apr_hash_get (props, SVN_PROP_EXECUTABLE,
151
APR_HASH_KEY_STRING);
152
externals = apr_hash_get (props, SVN_PROP_EXTERNALS,
153
APR_HASH_KEY_STRING);
154
special = apr_hash_get (props, SVN_PROP_SPECIAL,
155
APR_HASH_KEY_STRING);
158
SVN_ERR (get_eol_style (&style, &eol, eol_style->data, native_eol));
160
if (local_mod && (! special))
162
/* Use the modified time from the working copy if
164
SVN_ERR (svn_io_file_affected_time (&tm, from, pool));
168
tm = entry->cmt_date;
178
/* For locally modified files, we'll append an 'M'
179
to the revision number, and set the author to
180
"(local)" since we can't always determine the
181
current user's username */
183
author = _("(local)");
188
author = entry->cmt_author;
191
SVN_ERR (svn_subst_build_keywords
192
(&kw, keywords->data,
193
apr_psprintf (pool, fmt, entry->cmt_rev),
194
entry->url, tm, author, pool));
197
SVN_ERR (svn_subst_copy_and_translate2 (base, to, eol, FALSE,
199
special ? TRUE : FALSE,
202
SVN_ERR (svn_io_set_file_executable (to, TRUE,
206
SVN_ERR (svn_io_set_file_affected_time (tm, to, pool));
212
copy_versioned_files (const char *from,
214
svn_opt_revision_t *revision,
216
svn_boolean_t recurse,
217
const char *native_eol,
218
svn_client_ctx_t *ctx,
221
svn_wc_adm_access_t *adm_access;
222
const svn_wc_entry_t *entry;
224
apr_pool_t *iterpool;
226
apr_hash_index_t *hi;
229
SVN_ERR (svn_wc_adm_probe_open3 (&adm_access, NULL, from, FALSE,
230
0, ctx->cancel_func, ctx->cancel_baton,
233
SVN_ERR (svn_wc_entry (&entry, from, adm_access, FALSE, pool));
235
/* Bail if we're trying to export something that doesn't exist,
236
or isn't under version control. */
239
SVN_ERR (svn_wc_adm_close (adm_access));
240
return svn_error_createf (SVN_ERR_UNVERSIONED_RESOURCE, NULL,
241
_("'%s' is not under version control "
243
svn_path_local_style (from, pool));
246
/* Only export 'added' files when the revision is WORKING.
247
Otherwise, skip the 'added' files, since they didn't exist
248
in the BASE revision and don't have an associated text-base.
250
Don't export 'deleted' files and directories unless it's a
251
revision other than WORKING. These files and directories
252
don't really exist in WORKING. */
253
if ((revision->kind != svn_opt_revision_working &&
254
entry->schedule == svn_wc_schedule_add) ||
255
(revision->kind == svn_opt_revision_working &&
256
entry->schedule == svn_wc_schedule_delete))
259
if (entry->kind == svn_node_dir)
261
/* Try to make the new directory. If this fails because the
262
directory already exists, check our FORCE flag to see if we
264
SVN_ERR (svn_io_stat (&finfo, from, APR_FINFO_PROT, pool));
265
err = svn_io_dir_make (to, finfo.protection, pool);
268
if (! APR_STATUS_IS_EEXIST (err->apr_err))
271
SVN_ERR_W (err, _("Destination directory exists, and will not be "
272
"overwritten unless forced"));
274
svn_error_clear (err);
277
SVN_ERR (svn_wc_entries_read (&entries, adm_access, FALSE, pool));
279
iterpool = svn_pool_create (pool);
280
for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
286
svn_pool_clear (iterpool);
288
apr_hash_this (hi, &key, NULL, &val);
293
if (ctx->cancel_func)
294
SVN_ERR (ctx->cancel_func (ctx->cancel_baton));
296
/* ### We could also invoke ctx->notify_func somewhere in
297
### here... Is it called for, though? Not sure. */
299
if (entry->kind == svn_node_dir)
301
if (strcmp (item, SVN_WC_ENTRY_THIS_DIR) == 0)
303
; /* skip this, it's the current directory that we're
310
const char *new_from = svn_path_join (from, item,
312
const char *new_to = svn_path_join (to, item, iterpool);
314
SVN_ERR (copy_versioned_files (new_from, new_to,
315
revision, force, recurse,
321
else if (entry->kind == svn_node_file)
323
const char *new_from = svn_path_join (from, item, iterpool);
324
const char *new_to = svn_path_join (to, item, iterpool);
326
SVN_ERR (copy_one_versioned_file (new_from, new_to, adm_access,
327
revision, native_eol,
331
svn_pool_destroy (iterpool);
333
else if (entry->kind == svn_node_file)
335
SVN_ERR (copy_one_versioned_file (from, to, adm_access, revision,
339
SVN_ERR (svn_wc_adm_close (adm_access));
344
/* Abstraction of open_root.
346
* Create PATH if it does not exist and is not obstructed, and invoke
347
* NOTIFY_FUNC with NOTIFY_BATON on PATH.
349
* If PATH exists but is a file, then error with SVN_ERR_WC_NOT_DIRECTORY.
351
* If PATH is a already a directory, then error with
352
* SVN_ERR_WC_OBSTRUCTED_UPDATE, unless FORCE, in which case just
353
* export into PATH with no error.
356
open_root_internal (const char *path,
358
svn_wc_notify_func2_t notify_func,
362
svn_node_kind_t kind;
364
SVN_ERR (svn_io_check_path (path, &kind, pool));
365
if (kind == svn_node_none)
366
SVN_ERR (svn_io_dir_make (path, APR_OS_DEFAULT, pool));
367
else if (kind == svn_node_file)
368
return svn_error_createf (SVN_ERR_WC_NOT_DIRECTORY, NULL,
369
_("'%s' exists and is not a directory"),
370
svn_path_local_style (path, pool));
371
else if ((kind != svn_node_dir) || (! force))
372
return svn_error_createf (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
373
_("'%s' already exists"),
374
svn_path_local_style (path, pool));
378
svn_wc_notify_t *notify = svn_wc_create_notify (path,
379
svn_wc_notify_update_add,
381
notify->kind = svn_node_dir;
382
(*notify_func) (notify_baton, notify, pool);
389
/* ---------------------------------------------------------------------- */
391
/*** A dedicated 'export' editor, which does no .svn/ accounting. ***/
396
const char *root_path;
397
const char *root_url;
399
svn_revnum_t *target_revision;
400
apr_hash_t *externals;
401
const char *native_eol;
403
svn_wc_notify_func2_t notify_func;
410
struct edit_baton *edit_baton;
417
struct edit_baton *edit_baton;
422
/* We need to keep this around so we can explicitly close it in close_file,
423
thus flushing its output to disk so we can copy and translate it. */
424
apr_file_t *tmp_file;
426
/* The MD5 digest of the file's fulltext. This is all zeros until
427
the last textdelta window handler call returns. */
428
unsigned char text_digest[APR_MD5_DIGESTSIZE];
430
/* The three svn: properties we might actually care about. */
431
const svn_string_t *eol_style_val;
432
const svn_string_t *keywords_val;
433
const svn_string_t *executable_val;
434
svn_boolean_t special;
436
/* Any keyword vals to be substituted */
437
const char *revision;
442
/* Pool associated with this baton. */
449
svn_txdelta_window_handler_t apply_handler;
457
set_target_revision (void *edit_baton,
458
svn_revnum_t target_revision,
461
struct edit_baton *eb = edit_baton;
463
/* Stashing a target_revision in the baton */
464
*(eb->target_revision) = target_revision;
470
/* Just ensure that the main export directory exists. */
472
open_root (void *edit_baton,
473
svn_revnum_t base_revision,
477
struct edit_baton *eb = edit_baton;
478
struct dir_baton *db = apr_pcalloc (pool, sizeof (*db));
480
SVN_ERR (open_root_internal (eb->root_path, eb->force,
481
eb->notify_func, eb->notify_baton, pool));
483
/* Build our dir baton. */
484
db->path = eb->root_path;
492
/* Ensure the directory exists, and send feedback. */
494
add_directory (const char *path,
496
const char *copyfrom_path,
497
svn_revnum_t copyfrom_revision,
501
struct dir_baton *pb = parent_baton;
502
struct dir_baton *db = apr_pcalloc (pool, sizeof (*db));
503
struct edit_baton *eb = pb->edit_baton;
504
const char *full_path = svn_path_join (eb->root_path, path, pool);
505
svn_node_kind_t kind;
507
SVN_ERR (svn_io_check_path (full_path, &kind, pool));
508
if (kind == svn_node_none)
509
SVN_ERR (svn_io_dir_make (full_path, APR_OS_DEFAULT, pool));
510
else if (kind == svn_node_file)
511
return svn_error_createf (SVN_ERR_WC_NOT_DIRECTORY, NULL,
512
_("'%s' exists and is not a directory"),
513
svn_path_local_style (full_path, pool));
514
else if (! (kind == svn_node_dir && eb->force))
515
return svn_error_createf (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
516
_("'%s' already exists"),
517
svn_path_local_style (full_path, pool));
521
svn_wc_notify_t *notify = svn_wc_create_notify (full_path,
522
svn_wc_notify_update_add,
524
notify->kind = svn_node_dir;
525
(*eb->notify_func) (eb->notify_baton, notify, pool);
528
/* Build our dir baton. */
529
db->path = full_path;
537
/* Build a file baton. */
539
add_file (const char *path,
541
const char *copyfrom_path,
542
svn_revnum_t copyfrom_revision,
546
struct dir_baton *pb = parent_baton;
547
struct edit_baton *eb = pb->edit_baton;
548
struct file_baton *fb = apr_pcalloc (pool, sizeof(*fb));
549
const char *full_path = svn_path_join (eb->root_path, path, pool);
550
const char *full_url = svn_path_join (eb->root_url, path, pool);
553
fb->path = full_path;
563
window_handler (svn_txdelta_window_t *window, void *baton)
565
struct handler_baton *hb = baton;
568
err = hb->apply_handler (window, hb->apply_baton);
571
/* We failed to apply the patch; clean up the temporary file. */
572
apr_file_remove (hb->tmppath, hb->pool);
580
/* Write incoming data into the tmpfile stream */
582
apply_textdelta (void *file_baton,
583
const char *base_checksum,
585
svn_txdelta_window_handler_t *handler,
586
void **handler_baton)
588
struct file_baton *fb = file_baton;
589
struct handler_baton *hb = apr_palloc (pool, sizeof (*hb));
591
SVN_ERR (svn_io_open_unique_file (&fb->tmp_file, &(fb->tmppath),
592
fb->path, ".tmp", FALSE, fb->pool));
595
hb->tmppath = fb->tmppath;
597
svn_txdelta_apply (svn_stream_empty (pool),
598
svn_stream_from_aprfile (fb->tmp_file, pool),
599
fb->text_digest, NULL, pool,
600
&hb->apply_handler, &hb->apply_baton);
603
*handler = window_handler;
609
change_file_prop (void *file_baton,
611
const svn_string_t *value,
614
struct file_baton *fb = file_baton;
619
/* Store only the magic three properties. */
620
if (strcmp (name, SVN_PROP_EOL_STYLE) == 0)
621
fb->eol_style_val = svn_string_dup (value, fb->pool);
623
else if (strcmp (name, SVN_PROP_KEYWORDS) == 0)
624
fb->keywords_val = svn_string_dup (value, fb->pool);
626
else if (strcmp (name, SVN_PROP_EXECUTABLE) == 0)
627
fb->executable_val = svn_string_dup (value, fb->pool);
629
/* Try to fill out the baton's keywords-structure too. */
630
else if (strcmp (name, SVN_PROP_ENTRY_COMMITTED_REV) == 0)
631
fb->revision = apr_pstrdup (fb->pool, value->data);
633
else if (strcmp (name, SVN_PROP_ENTRY_COMMITTED_DATE) == 0)
634
SVN_ERR (svn_time_from_cstring (&fb->date, value->data, fb->pool));
636
else if (strcmp (name, SVN_PROP_ENTRY_LAST_AUTHOR) == 0)
637
fb->author = apr_pstrdup (fb->pool, value->data);
639
else if (strcmp (name, SVN_PROP_SPECIAL) == 0)
647
change_dir_prop (void *dir_baton,
649
const svn_string_t *value,
652
struct dir_baton *db = dir_baton;
653
struct edit_baton *eb = db->edit_baton;
655
if (value && (strcmp (name, SVN_PROP_EXTERNALS) == 0))
656
add_externals (eb->externals, db->path, value);
662
/* Move the tmpfile to file, and send feedback. */
664
close_file (void *file_baton,
665
const char *text_checksum,
668
struct file_baton *fb = file_baton;
669
struct edit_baton *eb = fb->edit_baton;
671
/* Was a txdelta even sent? */
675
SVN_ERR (svn_io_file_close (fb->tmp_file, fb->pool));
679
const char *actual_checksum
680
= svn_md5_digest_to_cstring (fb->text_digest, pool);
682
if (actual_checksum && (strcmp (text_checksum, actual_checksum) != 0))
684
return svn_error_createf
685
(SVN_ERR_CHECKSUM_MISMATCH, NULL,
686
_("Checksum mismatch for '%s'; expected: '%s', actual: '%s'"),
687
svn_path_local_style (fb->path, pool),
688
text_checksum, actual_checksum);
692
if ((! fb->eol_style_val) && (! fb->keywords_val) && (! fb->special))
694
SVN_ERR (svn_io_file_rename (fb->tmppath, fb->path, pool));
698
svn_subst_eol_style_t style;
700
svn_subst_keywords_t final_kw = {0};
702
if (fb->eol_style_val)
703
SVN_ERR (get_eol_style (&style, &eol, fb->eol_style_val->data,
706
if (fb->keywords_val)
707
SVN_ERR (svn_subst_build_keywords (&final_kw, fb->keywords_val->data,
708
fb->revision, fb->url, fb->date,
711
SVN_ERR (svn_subst_copy_and_translate2
712
(fb->tmppath, fb->path,
713
fb->eol_style_val ? eol : NULL,
714
fb->eol_style_val ? TRUE : FALSE, /* repair */
715
fb->keywords_val ? &final_kw : NULL,
720
SVN_ERR (svn_io_remove_file (fb->tmppath, pool));
723
if (fb->executable_val)
724
SVN_ERR (svn_io_set_file_executable (fb->path, TRUE, FALSE, pool));
726
if (fb->date && (! fb->special))
727
SVN_ERR (svn_io_set_file_affected_time (fb->date, fb->path, pool));
729
if (fb->edit_baton->notify_func)
731
svn_wc_notify_t *notify = svn_wc_create_notify (fb->path,
732
svn_wc_notify_update_add,
734
notify->kind = svn_node_file;
735
(*fb->edit_baton->notify_func) (fb->edit_baton->notify_baton, notify,
744
/*** Public Interfaces ***/
747
svn_client_export3 (svn_revnum_t *result_rev,
750
const svn_opt_revision_t *peg_revision,
751
const svn_opt_revision_t *revision,
752
svn_boolean_t overwrite,
753
svn_boolean_t ignore_externals,
754
svn_boolean_t recurse,
755
const char *native_eol,
756
svn_client_ctx_t *ctx,
759
svn_revnum_t edit_revision = SVN_INVALID_REVNUM;
762
if (svn_path_is_url (from) ||
763
! (revision->kind == svn_opt_revision_base ||
764
revision->kind == svn_opt_revision_committed ||
765
revision->kind == svn_opt_revision_working ||
766
revision->kind == svn_opt_revision_unspecified))
769
svn_ra_session_t *ra_session;
770
svn_node_kind_t kind;
771
struct edit_baton *eb = apr_pcalloc (pool, sizeof (*eb));
773
/* Get the RA connection. */
774
SVN_ERR (svn_client__ra_session_from_path (&ra_session, &revnum,
775
&url, from, peg_revision,
776
revision, ctx, pool));
780
eb->force = overwrite;
781
eb->target_revision = &edit_revision;
782
eb->notify_func = ctx->notify_func2;
783
eb->notify_baton = ctx->notify_baton2;
784
eb->externals = apr_hash_make (pool);
785
eb->native_eol = native_eol;
787
SVN_ERR (svn_ra_check_path (ra_session, "", revnum, &kind, pool));
789
if (kind == svn_node_file)
792
apr_hash_index_t *hi;
793
struct file_baton *fb = apr_pcalloc (pool, sizeof(*fb));
795
/* Since you cannot actually root an editor at a file, we
796
* manually drive a few functions of our editor. */
798
/* This is the equivalent of a parentless add_file(). */
800
fb->path = eb->root_path;
801
fb->url = eb->root_url;
804
/* Copied from apply_textdelta(). */
805
SVN_ERR (svn_io_open_unique_file (&fb->tmp_file, &(fb->tmppath),
806
fb->path, ".tmp", FALSE,
809
/* Step outside the editor-likeness for a moment, to actually talk
810
* to the repository. */
811
SVN_ERR (svn_ra_get_file (ra_session, "", revnum,
812
svn_stream_from_aprfile (fb->tmp_file,
814
NULL, &props, pool));
816
/* Push the props into change_file_prop(), to update the file_baton
817
* with information. */
818
for (hi = apr_hash_first (pool, props); hi; hi = apr_hash_next (hi))
822
apr_hash_this (hi, &key, NULL, &val);
823
SVN_ERR (change_file_prop (fb, key, val, pool));
826
/* And now just use close_file() to do all the keyword and EOL
827
* work, and put the file into place. */
828
SVN_ERR (close_file (fb, NULL, pool));
830
else if (kind == svn_node_dir)
833
const svn_delta_editor_t *export_editor;
834
const svn_ra_reporter2_t *reporter;
836
svn_delta_editor_t *editor = svn_delta_default_editor (pool);
837
svn_boolean_t use_sleep = FALSE;
839
editor->set_target_revision = set_target_revision;
840
editor->open_root = open_root;
841
editor->add_directory = add_directory;
842
editor->add_file = add_file;
843
editor->apply_textdelta = apply_textdelta;
844
editor->close_file = close_file;
845
editor->change_file_prop = change_file_prop;
846
editor->change_dir_prop = change_dir_prop;
848
SVN_ERR (svn_delta_get_cancellation_editor (ctx->cancel_func,
857
/* Manufacture a basic 'report' to the update reporter. */
858
SVN_ERR (svn_ra_do_update (ra_session,
859
&reporter, &report_baton,
861
"", /* no sub-target */
863
export_editor, edit_baton, pool));
865
SVN_ERR (reporter->set_path (report_baton, "", revnum,
866
TRUE, /* "help, my dir is empty!" */
869
SVN_ERR (reporter->finish_report (report_baton, pool));
871
/* Special case: Due to our sly export/checkout method of
872
* updating an empty directory, no target will have been created
873
* if the exported item is itself an empty directory
874
* (export_editor->open_root never gets called, because there
875
* are no "changes" to make to the empty dir we reported to the
878
* So we just create the empty dir manually; but we do it via
879
* open_root_internal(), in order to get proper notification.
881
SVN_ERR (svn_io_check_path (to, &kind, pool));
882
if (kind == svn_node_none)
883
SVN_ERR (open_root_internal
884
(to, overwrite, ctx->notify_func2,
885
ctx->notify_baton2, pool));
887
if (! ignore_externals && recurse)
888
SVN_ERR (svn_client__fetch_externals (eb->externals, TRUE,
889
&use_sleep, ctx, pool));
894
svn_opt_revision_t working_revision = *revision;
895
/* This is a working copy export. */
896
if (working_revision.kind == svn_opt_revision_unspecified)
898
/* Default to WORKING in the case that we have
899
been given a working copy path */
900
working_revision.kind = svn_opt_revision_working;
903
/* just copy the contents of the working copy into the target path. */
904
SVN_ERR (copy_versioned_files (from, to, &working_revision, overwrite,
905
recurse, native_eol, ctx, pool));
909
if (ctx->notify_func2)
911
svn_wc_notify_t *notify
912
= svn_wc_create_notify (to,
913
svn_wc_notify_update_completed, pool);
914
notify->revision = edit_revision;
915
(*ctx->notify_func2) (ctx->notify_baton2, notify, pool);
919
*result_rev = edit_revision;
926
svn_client_export2 (svn_revnum_t *result_rev,
929
svn_opt_revision_t *revision,
931
const char *native_eol,
932
svn_client_ctx_t *ctx,
935
svn_opt_revision_t peg_revision;
937
peg_revision.kind = svn_opt_revision_unspecified;
939
return svn_client_export3 (result_rev, from, to, &peg_revision,
940
revision, force, FALSE, TRUE,
941
native_eol, ctx, pool);
946
svn_client_export (svn_revnum_t *result_rev,
949
svn_opt_revision_t *revision,
951
svn_client_ctx_t *ctx,
954
return svn_client_export2 (result_rev, from, to, revision, force, NULL, ctx,