2 * externals.c : routines dealing with (file) externals in the working copy
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
29 #include <apr_pools.h>
31 #include <apr_tables.h>
32 #include <apr_general.h>
35 #include "svn_dirent_uri.h"
37 #include "svn_error.h"
40 #include "svn_pools.h"
41 #include "svn_props.h"
42 #include "svn_string.h"
44 #include "svn_types.h"
47 #include "private/svn_skel.h"
48 #include "private/svn_subr_private.h"
51 #include "adm_files.h"
53 #include "translate.h"
54 #include "workqueue.h"
55 #include "conflicts.h"
57 #include "svn_private_config.h"
67 * in the LINE_PARTS array and update the revision field in ITEM with
68 * the revision if the revision is found. Set REV_IDX to the index in
69 * LINE_PARTS where the revision specification starts. Remove from
70 * LINE_PARTS the element(s) that specify the revision.
71 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
74 * If this function returns successfully, then LINE_PARTS will have
75 * only two elements in it.
78 find_and_remove_externals_revision(int *rev_idx,
79 const char **line_parts,
81 svn_wc_external_item2_t *item,
82 const char *parent_directory_display,
88 for (i = 0; i < 2; ++i)
90 const char *token = line_parts[i];
92 if (token[0] == '-' && token[1] == 'r')
94 svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
95 const char *digits_ptr;
101 if (token[2] == '\0')
103 /* There must be a total of four elements in the line if
105 if (num_line_parts != 4)
109 digits_ptr = line_parts[i+1];
113 /* There must be a total of three elements in the line
115 if (num_line_parts != 3)
119 digits_ptr = token+2;
122 if (svn_opt_parse_revision(&item->revision,
124 digits_ptr, pool) != 0)
126 /* We want a single revision, not a range. */
127 if (end_revision.kind != svn_opt_revision_unspecified)
129 /* Allow only numbers and dates, not keywords. */
130 if (item->revision.kind != svn_opt_revision_number
131 && item->revision.kind != svn_opt_revision_date)
134 /* Shift any line elements past the revision specification
135 down over the revision specification. */
136 for (j = i; j < num_line_parts-shift_count; ++j)
137 line_parts[j] = line_parts[j+shift_count];
138 line_parts[num_line_parts-shift_count] = NULL;
140 /* Found the revision, so leave the function immediately, do
141 * not continue looking for additional revisions. */
146 /* No revision was found, so there must be exactly two items in the
148 if (num_line_parts == 2)
152 return svn_error_createf
153 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
154 _("Error parsing %s property on '%s': '%s'"),
156 parent_directory_display,
161 svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
162 const char *parent_directory,
164 svn_boolean_t canonicalize_url,
168 apr_array_header_t *externals = NULL;
169 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
170 const char *parent_directory_display = svn_path_is_url(parent_directory) ?
171 parent_directory : svn_dirent_local_style(parent_directory, pool);
173 /* If an error occurs halfway through parsing, *externals_p should stay
174 * untouched. So, store the list in a local var first. */
176 externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
178 for (i = 0; i < lines->nelts; i++)
180 const char *line = APR_ARRAY_IDX(lines, i, const char *);
184 svn_wc_external_item2_t *item;
187 svn_boolean_t token0_is_url;
188 svn_boolean_t token1_is_url;
190 /* Index into line_parts where the revision specification
194 if ((! line) || (line[0] == '#'))
199 status = apr_tokenize_to_argv(line, &line_parts, pool);
201 return svn_error_wrap_apr(status,
202 _("Can't split line into components: '%s'"),
204 /* Count the number of tokens. */
205 for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
208 SVN_ERR(svn_wc_external_item2_create(&item, pool));
209 item->revision.kind = svn_opt_revision_unspecified;
210 item->peg_revision.kind = svn_opt_revision_unspecified;
213 * There are six different formats of externals:
222 * The last three allow peg revisions in the URL.
224 * With relative URLs and no '-rN' or '-r N', there is no way to
225 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
226 * relative URL like /svn/repos/trunk, so this case is taken as
229 if (num_line_parts < 2 || num_line_parts > 4)
230 return svn_error_createf
231 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
232 _("Error parsing %s property on '%s': '%s'"),
234 parent_directory_display,
237 /* To make it easy to check for the forms, find and remove -r N
238 or -rN from the line item array. If it is found, rev_idx
239 contains the index into line_parts where '-r' was found and
240 set item->revision to the parsed revision. */
241 /* ### ugh. stupid cast. */
242 SVN_ERR(find_and_remove_externals_revision(&rev_idx,
243 (const char **)line_parts,
244 num_line_parts, item,
245 parent_directory_display,
248 token0 = line_parts[0];
249 token1 = line_parts[1];
251 token0_is_url = svn_path_is_url(token0);
252 token1_is_url = svn_path_is_url(token1);
254 if (token0_is_url && token1_is_url)
255 return svn_error_createf
256 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
257 _("Invalid %s property on '%s': "
258 "cannot use two absolute URLs ('%s' and '%s') in an external; "
259 "one must be a path where an absolute or relative URL is "
261 SVN_PROP_EXTERNALS, parent_directory_display, token0, token1);
263 if (0 == rev_idx && token1_is_url)
264 return svn_error_createf
265 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
266 _("Invalid %s property on '%s': "
267 "cannot use a URL '%s' as the target directory for an external "
269 SVN_PROP_EXTERNALS, parent_directory_display, token1);
271 if (1 == rev_idx && token0_is_url)
272 return svn_error_createf
273 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
274 _("Invalid %s property on '%s': "
275 "cannot use a URL '%s' as the target directory for an external "
277 SVN_PROP_EXTERNALS, parent_directory_display, token0);
279 /* The appearence of -r N or -rN forces the type of external.
280 If -r is at the beginning of the line or the first token is
281 an absolute URL or if the second token is not an absolute
282 URL, then the URL supports peg revisions. */
284 (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
286 /* The URL is passed to svn_opt_parse_path in
287 uncanonicalized form so that the scheme relative URL
288 //hostname/foo is not collapsed to a server root relative
289 URL /hostname/foo. */
290 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
292 item->target_dir = token1;
296 item->target_dir = token0;
298 item->peg_revision = item->revision;
301 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
302 &item->revision, TRUE, FALSE,
305 item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
307 if (item->target_dir[0] == '\0'
308 || svn_dirent_is_absolute(item->target_dir)
309 || svn_path_is_backpath_present(item->target_dir)
310 || !svn_dirent_skip_ancestor("dummy",
311 svn_dirent_join("dummy",
314 return svn_error_createf
315 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
316 _("Invalid %s property on '%s': "
317 "target '%s' is an absolute path or involves '..'"),
319 parent_directory_display,
322 if (canonicalize_url)
324 /* Uh... this is stupid. But it's consistent with what our
325 code did before we split up the relpath/dirent/uri APIs.
326 Still, given this, it's no wonder that our own libraries
327 don't ask this function to canonicalize the results. */
328 if (svn_path_is_url(item->url))
329 item->url = svn_uri_canonicalize(item->url, pool);
331 item->url = svn_dirent_canonicalize(item->url, pool);
335 APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
339 *externals_p = externals;
345 svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
346 apr_array_header_t *externals,
348 apr_pool_t *scratch_pool)
354 apr_hash_t *targets = apr_hash_make(scratch_pool);
355 apr_hash_t *targets2 = NULL;
356 *duplicate_targets = NULL;
358 for (i = 0; i < externals->nelts; i++)
360 target = APR_ARRAY_IDX(externals, i,
361 svn_wc_external_item2_t*)->target_dir;
362 len = apr_hash_count(targets);
363 svn_hash_sets(targets, target, "");
364 if (len == apr_hash_count(targets))
366 /* Hashtable length is unchanged. This must be a duplicate. */
368 /* Collapse multiple duplicates of the same target by using a second
371 targets2 = apr_hash_make(scratch_pool);
372 len2 = apr_hash_count(targets2);
373 svn_hash_sets(targets2, target, "");
374 if (len2 < apr_hash_count(targets2))
376 /* The second hash list just got bigger, i.e. this target has
377 * not been counted as duplicate before. */
378 if (! *duplicate_targets)
380 *duplicate_targets = apr_array_make(
381 pool, 1, sizeof(svn_wc_external_item2_t*));
383 APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
385 /* Else, this same target has already been recorded as a duplicate,
386 * don't count it again. */
397 /* We explicitly use wri_abspath and local_abspath here, because we
398 might want to install file externals in an obstructing working copy */
399 const char *wri_abspath; /* The working defining the file external */
400 const char *local_abspath; /* The file external itself */
401 const char *name; /* The basename of the file external itself */
403 /* Information from the caller */
404 svn_boolean_t use_commit_times;
405 const apr_array_header_t *ext_patterns;
406 const char *diff3cmd;
408 const char *repos_root_url;
409 const char *repos_uuid;
410 const char *old_repos_relpath;
411 const char *new_repos_relpath;
413 const char *record_ancestor_abspath;
414 const char *recorded_repos_relpath;
415 svn_revnum_t recorded_peg_revision;
416 svn_revnum_t recorded_revision;
418 /* Introducing a new file external */
421 svn_wc_conflict_resolver_func2_t conflict_func;
422 void *conflict_baton;
423 svn_cancel_func_t cancel_func;
425 svn_wc_notify_func2_t notify_func;
428 svn_revnum_t *target_revision;
430 /* What was there before the update */
431 svn_revnum_t original_revision;
432 const svn_checksum_t *original_checksum;
434 /* What we are installing now */
435 const char *new_pristine_abspath;
436 svn_checksum_t *new_sha1_checksum;
437 svn_checksum_t *new_md5_checksum;
439 /* List of incoming propchanges */
440 apr_array_header_t *propchanges;
442 /* Array of svn_prop_inherited_item_t * structures representing the
443 properties inherited by the base node at LOCAL_ABSPATH. */
444 apr_array_header_t *iprops;
446 /* The last change information */
447 svn_revnum_t changed_rev;
448 apr_time_t changed_date;
449 const char *changed_author;
451 svn_boolean_t had_props;
453 svn_boolean_t file_closed;
456 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
458 set_target_revision(void *edit_baton,
459 svn_revnum_t target_revision,
462 struct edit_baton *eb = edit_baton;
464 *eb->target_revision = target_revision;
469 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
471 open_root(void *edit_baton,
472 svn_revnum_t base_revision,
473 apr_pool_t *dir_pool,
476 *root_baton = edit_baton;
480 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
482 add_file(const char *path,
484 const char *copyfrom_path,
485 svn_revnum_t copyfrom_revision,
486 apr_pool_t *file_pool,
489 struct edit_baton *eb = parent_baton;
490 if (strcmp(path, eb->name))
491 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
492 _("This editor can only update '%s'"),
493 svn_dirent_local_style(eb->local_abspath,
497 eb->original_revision = SVN_INVALID_REVNUM;
503 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
505 open_file(const char *path,
507 svn_revnum_t base_revision,
508 apr_pool_t *file_pool,
511 struct edit_baton *eb = parent_baton;
512 svn_node_kind_t kind;
513 if (strcmp(path, eb->name))
514 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
515 _("This editor can only update '%s'"),
516 svn_dirent_local_style(eb->local_abspath,
520 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
521 &eb->old_repos_relpath, NULL, NULL,
523 &eb->changed_date, &eb->changed_author,
524 NULL, &eb->original_checksum, NULL, NULL,
525 &eb->had_props, NULL, NULL,
526 eb->db, eb->local_abspath,
527 eb->pool, file_pool));
529 if (kind != svn_node_file)
530 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
531 _("Node '%s' is no existing file external"),
532 svn_dirent_local_style(eb->local_abspath,
537 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
539 apply_textdelta(void *file_baton,
540 const char *base_checksum_digest,
542 svn_txdelta_window_handler_t *handler,
543 void **handler_baton)
545 struct edit_baton *eb = file_baton;
546 svn_stream_t *src_stream;
547 svn_stream_t *dest_stream;
549 if (eb->original_checksum)
551 if (base_checksum_digest)
553 svn_checksum_t *expected_checksum;
554 const svn_checksum_t *original_md5;
556 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
557 base_checksum_digest, pool));
559 if (eb->original_checksum->kind != svn_checksum_md5)
560 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
561 eb->db, eb->wri_abspath,
562 eb->original_checksum,
565 original_md5 = eb->original_checksum;
567 if (!svn_checksum_match(expected_checksum, original_md5))
568 return svn_error_trace(svn_checksum_mismatch_err(
572 _("Base checksum mismatch for '%s'"),
573 svn_dirent_local_style(eb->local_abspath,
577 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
578 eb->wri_abspath, eb->original_checksum,
582 src_stream = svn_stream_empty(pool);
584 SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
585 &eb->new_md5_checksum,
586 &eb->new_sha1_checksum,
587 eb->db, eb->wri_abspath,
590 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
591 handler, handler_baton);
596 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
598 change_file_prop(void *file_baton,
600 const svn_string_t *value,
603 struct edit_baton *eb = file_baton;
604 svn_prop_t *propchange;
606 propchange = apr_array_push(eb->propchanges);
607 propchange->name = apr_pstrdup(eb->pool, name);
608 propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
613 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
615 close_file(void *file_baton,
616 const char *expected_md5_digest,
619 struct edit_baton *eb = file_baton;
620 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
621 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
622 svn_boolean_t obstructed = FALSE;
624 eb->file_closed = TRUE; /* We bump the revision here */
626 /* Check the checksum, if provided */
627 if (expected_md5_digest)
629 svn_checksum_t *expected_md5_checksum;
630 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
632 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
633 expected_md5_digest, pool));
635 if (actual_md5_checksum == NULL)
637 actual_md5_checksum = eb->original_checksum;
639 if (actual_md5_checksum != NULL
640 && actual_md5_checksum->kind != svn_checksum_md5)
642 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
643 eb->db, eb->wri_abspath,
649 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
650 return svn_checksum_mismatch_err(
651 expected_md5_checksum,
652 actual_md5_checksum, pool,
653 _("Checksum mismatch for '%s'"),
654 svn_dirent_local_style(eb->local_abspath, pool));
657 /* First move the file in the pristine store; this hands over the cleanup
658 behavior to the pristine store. */
659 if (eb->new_sha1_checksum)
661 SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
662 eb->new_sha1_checksum,
663 eb->new_md5_checksum, pool));
665 eb->new_pristine_abspath = NULL;
668 /* Merge the changes */
670 svn_skel_t *all_work_items = NULL;
671 svn_skel_t *conflict_skel = NULL;
672 svn_skel_t *work_item;
673 apr_hash_t *base_props = NULL;
674 apr_hash_t *actual_props = NULL;
675 apr_hash_t *new_pristine_props = NULL;
676 apr_hash_t *new_actual_props = NULL;
677 apr_hash_t *new_dav_props = NULL;
678 const svn_checksum_t *new_checksum = NULL;
679 const svn_checksum_t *original_checksum = NULL;
681 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
685 new_checksum = eb->original_checksum;
688 SVN_ERR(svn_wc__db_base_get_props(
689 &base_props, eb->db, eb->local_abspath, pool, pool));
691 SVN_ERR(svn_wc__db_read_props(
692 &actual_props, eb->db, eb->local_abspath, pool, pool));
696 base_props = apr_hash_make(pool);
699 actual_props = apr_hash_make(pool);
701 if (eb->new_sha1_checksum)
702 new_checksum = eb->new_sha1_checksum;
704 /* Merge the properties */
706 apr_array_header_t *entry_prop_changes;
707 apr_array_header_t *dav_prop_changes;
708 apr_array_header_t *regular_prop_changes;
711 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
712 &dav_prop_changes, ®ular_prop_changes,
715 /* Read the entry-prop changes to update the last-changed info. */
716 for (i = 0; i < entry_prop_changes->nelts; i++)
718 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
722 continue; /* authz or something */
724 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
725 eb->changed_author = apr_pstrdup(pool, prop->value->data);
726 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
729 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
730 eb->changed_rev = (svn_revnum_t)rev;
732 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
733 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
737 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
738 * of changes as a list of new props, but we only use this when
739 * adding a new file and it's equivalent in that case.) */
740 if (dav_prop_changes->nelts > 0)
741 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
743 /* Merge the regular prop changes. */
744 if (regular_prop_changes->nelts > 0)
746 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
748 SVN_ERR(svn_wc__merge_props(&conflict_skel,
751 eb->db, eb->local_abspath,
752 NULL /* server_baseprops*/,
755 regular_prop_changes,
760 new_pristine_props = base_props;
761 new_actual_props = actual_props;
766 if (eb->new_sha1_checksum)
768 svn_node_kind_t disk_kind;
769 svn_boolean_t install_pristine = FALSE;
770 const char *install_from = NULL;
772 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
774 if (disk_kind == svn_node_none)
776 /* Just install the file */
777 install_pristine = TRUE;
778 content_state = svn_wc_notify_state_changed;
780 else if (disk_kind != svn_node_file
781 || (eb->added && disk_kind == svn_node_file))
783 /* The node is obstructed; we just change the DB */
785 content_state = svn_wc_notify_state_unchanged;
789 svn_boolean_t is_mod;
790 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
791 eb->db, eb->local_abspath,
796 install_pristine = TRUE;
797 content_state = svn_wc_notify_state_changed;
801 svn_boolean_t found_text_conflict;
803 /* Ok, we have to do some work to merge a local change */
804 SVN_ERR(svn_wc__perform_file_merge(&work_item,
806 &found_text_conflict,
814 eb->original_revision,
815 *eb->target_revision,
822 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
825 if (found_text_conflict)
826 content_state = svn_wc_notify_state_conflicted;
828 content_state = svn_wc_notify_state_merged;
831 if (install_pristine)
833 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
836 eb->use_commit_times, TRUE,
839 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
844 content_state = svn_wc_notify_state_unchanged;
845 /* ### Retranslate on magic property changes, etc. */
848 /* Generate a conflict description, if needed */
851 SVN_ERR(svn_wc__conflict_skel_set_op_switch(
853 svn_wc_conflict_version_create2(
856 eb->old_repos_relpath,
857 eb->original_revision,
860 svn_wc_conflict_version_create2(
863 eb->new_repos_relpath,
864 *eb->target_revision,
868 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
869 eb->db, eb->local_abspath,
872 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
876 /* Install the file in the DB */
877 SVN_ERR(svn_wc__db_external_add_file(
881 eb->new_repos_relpath,
884 *eb->target_revision,
892 eb->record_ancestor_abspath,
893 eb->recorded_repos_relpath,
894 eb->recorded_peg_revision,
895 eb->recorded_revision,
896 TRUE, new_actual_props,
897 FALSE /* keep_recorded_info */,
902 /* close_edit may also update iprops for switched files, catching
903 those for which close_file is never called (e.g. an update of a
904 file external with no changes). So as a minor optimization we
905 clear the iprops so as not to set them again in close_edit. */
908 /* Run the work queue to complete the installation */
909 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
910 eb->cancel_func, eb->cancel_baton, pool));
916 svn_wc_notify_action_t action;
917 svn_wc_notify_t *notify;
920 action = obstructed ? svn_wc_notify_update_shadowed_update
921 : svn_wc_notify_update_update;
923 action = obstructed ? svn_wc_notify_update_shadowed_add
924 : svn_wc_notify_update_add;
926 notify = svn_wc_create_notify(eb->local_abspath, action, pool);
927 notify->kind = svn_node_file;
929 notify->revision = *eb->target_revision;
930 notify->prop_state = prop_state;
931 notify->content_state = content_state;
933 notify->old_revision = eb->original_revision;
935 eb->notify_func(eb->notify_baton, notify, pool);
941 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
943 close_edit(void *edit_baton,
946 struct edit_baton *eb = edit_baton;
948 if (!eb->file_closed)
950 apr_hash_t *wcroot_iprops = NULL;
951 /* The file wasn't updated, but its url or revision might have...
952 e.g. switch between branches for relative externals.
954 Just bump the information as that is just as expensive as
955 investigating when we should and shouldn't update it...
956 and avoid hard to debug edge cases */
960 wcroot_iprops = apr_hash_make(pool);
961 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
964 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
967 eb->new_repos_relpath,
970 *eb->target_revision,
972 /* exclude_relpaths */,
983 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
985 svn_revnum_t *target_revision,
986 svn_wc_context_t *wc_ctx,
987 const char *local_abspath,
988 const char *wri_abspath,
990 const char *repos_root_url,
991 const char *repos_uuid,
992 apr_array_header_t *iprops,
993 svn_boolean_t use_commit_times,
994 const char *diff3_cmd,
995 const apr_array_header_t *preserved_exts,
996 const char *record_ancestor_abspath,
997 const char *recorded_url,
998 const svn_opt_revision_t *recorded_peg_rev,
999 const svn_opt_revision_t *recorded_rev,
1000 svn_wc_conflict_resolver_func2_t conflict_func,
1001 void *conflict_baton,
1002 svn_cancel_func_t cancel_func,
1004 svn_wc_notify_func2_t notify_func,
1006 apr_pool_t *result_pool,
1007 apr_pool_t *scratch_pool)
1009 svn_wc__db_t *db = wc_ctx->db;
1010 apr_pool_t *edit_pool = result_pool;
1011 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1012 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1014 eb->pool = edit_pool;
1016 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1018 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1020 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1021 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1022 eb->target_revision = target_revision;
1024 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1025 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1026 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1027 eb->old_repos_relpath = eb->new_repos_relpath;
1029 eb->original_revision = SVN_INVALID_REVNUM;
1031 eb->iprops = iprops;
1033 eb->use_commit_times = use_commit_times;
1034 eb->ext_patterns = preserved_exts;
1035 eb->diff3cmd = diff3_cmd;
1037 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1038 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1041 eb->changed_rev = SVN_INVALID_REVNUM;
1043 if (recorded_peg_rev->kind == svn_opt_revision_number)
1044 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1046 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1048 if (recorded_rev->kind == svn_opt_revision_number)
1049 eb->recorded_revision = recorded_rev->value.number;
1051 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1053 eb->conflict_func = conflict_func;
1054 eb->conflict_baton = conflict_baton;
1055 eb->cancel_func = cancel_func;
1056 eb->cancel_baton = cancel_baton;
1057 eb->notify_func = notify_func;
1058 eb->notify_baton = notify_baton;
1060 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1062 tree_editor->open_root = open_root;
1063 tree_editor->set_target_revision = set_target_revision;
1064 tree_editor->add_file = add_file;
1065 tree_editor->open_file = open_file;
1066 tree_editor->apply_textdelta = apply_textdelta;
1067 tree_editor->change_file_prop = change_file_prop;
1068 tree_editor->close_file = close_file;
1069 tree_editor->close_edit = close_edit;
1071 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1078 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1079 const char *local_abspath,
1080 const svn_ra_reporter3_t *reporter,
1082 svn_boolean_t restore_files,
1083 svn_boolean_t use_commit_times,
1084 svn_cancel_func_t cancel_func,
1086 svn_wc_notify_func2_t notify_func,
1088 apr_pool_t *scratch_pool)
1090 svn_wc__db_t *db = wc_ctx->db;
1092 svn_node_kind_t kind;
1093 svn_wc__db_lock_t *lock;
1094 svn_revnum_t revision;
1095 const char *repos_root_url;
1096 const char *repos_relpath;
1097 svn_boolean_t update_root;
1099 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1100 &repos_relpath, &repos_root_url, NULL, NULL,
1101 NULL, NULL, NULL, NULL, NULL, &lock,
1102 NULL, NULL, &update_root,
1104 scratch_pool, scratch_pool);
1107 || kind == svn_node_dir
1110 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1111 return svn_error_trace(err);
1113 svn_error_clear(err);
1115 /* We don't know about this node, so all we have to do is tell
1116 the reporter that we don't know this node.
1118 But first we have to start the report by sending some basic
1119 information for the root. */
1121 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1122 FALSE, NULL, scratch_pool));
1123 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1125 /* Finish the report, which causes the update editor to be
1127 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1129 return SVN_NO_ERROR;
1135 svn_node_kind_t disk_kind;
1136 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1138 if (disk_kind == svn_node_none)
1140 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1145 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1146 return svn_error_trace(err);
1148 svn_error_clear(err);
1153 /* Report that we know the path */
1154 SVN_ERR(reporter->set_path(report_baton, "", revision,
1155 svn_depth_infinity, FALSE, NULL,
1158 /* For compatibility with the normal update editor report we report
1159 the target as switched.
1161 ### We can probably report a parent url and unswitched later */
1162 SVN_ERR(reporter->link_path(report_baton, "",
1163 svn_path_url_add_component2(repos_root_url,
1168 FALSE /* start_empty*/,
1169 lock ? lock->token : NULL,
1173 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1177 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1178 const char **defining_abspath,
1179 const char **defining_url,
1180 svn_revnum_t *defining_operational_revision,
1181 svn_revnum_t *defining_revision,
1182 svn_wc_context_t *wc_ctx,
1183 const char *wri_abspath,
1184 const char *local_abspath,
1185 svn_boolean_t ignore_enoent,
1186 apr_pool_t *result_pool,
1187 apr_pool_t *scratch_pool)
1189 const char *repos_root_url;
1190 svn_wc__db_status_t status;
1191 svn_node_kind_t kind;
1194 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1195 defining_url ? &repos_root_url : NULL, NULL,
1196 defining_url, defining_operational_revision,
1198 wc_ctx->db, local_abspath, wri_abspath,
1199 result_pool, scratch_pool);
1203 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1204 return svn_error_trace(err);
1206 svn_error_clear(err);
1209 *external_kind = svn_node_none;
1211 if (defining_abspath)
1212 *defining_abspath = NULL;
1215 *defining_url = NULL;
1217 if (defining_operational_revision)
1218 *defining_operational_revision = SVN_INVALID_REVNUM;
1220 if (defining_revision)
1221 *defining_revision = SVN_INVALID_REVNUM;
1223 return SVN_NO_ERROR;
1228 if (status != svn_wc__db_status_normal)
1229 *external_kind = svn_node_unknown;
1234 case svn_node_symlink:
1235 *external_kind = svn_node_file;
1238 *external_kind = svn_node_dir;
1241 *external_kind = svn_node_none;
1245 if (defining_url && *defining_url)
1246 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1249 return SVN_NO_ERROR;
1252 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1253 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1254 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1255 static svn_error_t *
1256 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1257 svn_wc_context_t *wc_ctx,
1258 svn_wc__committable_external_info_t *xinfo,
1259 apr_pool_t *scratch_pool)
1261 const char *repos_relpath;
1262 const char *repos_root_url;
1265 *is_rolled_out = FALSE;
1267 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1268 &repos_root_url, NULL, NULL, NULL, NULL,
1269 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1270 wc_ctx->db, xinfo->local_abspath,
1271 scratch_pool, scratch_pool);
1275 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1277 svn_error_clear(err);
1278 return SVN_NO_ERROR;
1283 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1284 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1285 return SVN_NO_ERROR;
1289 svn_wc__committable_externals_below(apr_array_header_t **externals,
1290 svn_wc_context_t *wc_ctx,
1291 const char *local_abspath,
1293 apr_pool_t *result_pool,
1294 apr_pool_t *scratch_pool)
1296 apr_array_header_t *orig_externals;
1298 apr_pool_t *iterpool;
1300 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1301 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1304 depth != svn_depth_infinity,
1305 result_pool, scratch_pool));
1307 if (orig_externals == NULL)
1308 return SVN_NO_ERROR;
1310 iterpool = svn_pool_create(scratch_pool);
1312 for (i = 0; i < orig_externals->nelts; i++)
1314 svn_boolean_t is_rolled_out;
1316 svn_wc__committable_external_info_t *xinfo =
1317 APR_ARRAY_IDX(orig_externals, i,
1318 svn_wc__committable_external_info_t *);
1320 /* Discard dirs for svn_depth_files (s.a.). */
1321 if (depth == svn_depth_files
1322 && xinfo->kind == svn_node_dir)
1325 svn_pool_clear(iterpool);
1327 /* Discard those externals that are not currently checked out. */
1328 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1330 if (! is_rolled_out)
1333 if (*externals == NULL)
1334 *externals = apr_array_make(
1336 sizeof(svn_wc__committable_external_info_t *));
1338 APR_ARRAY_PUSH(*externals,
1339 svn_wc__committable_external_info_t *) = xinfo;
1341 if (depth != svn_depth_infinity)
1344 /* Are there any nested externals? */
1345 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1346 xinfo->local_abspath,
1348 result_pool, iterpool));
1351 return SVN_NO_ERROR;
1355 svn_wc__externals_defined_below(apr_hash_t **externals,
1356 svn_wc_context_t *wc_ctx,
1357 const char *local_abspath,
1358 apr_pool_t *result_pool,
1359 apr_pool_t *scratch_pool)
1361 return svn_error_trace(
1362 svn_wc__db_externals_defined_below(externals,
1363 wc_ctx->db, local_abspath,
1364 result_pool, scratch_pool));
1368 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1369 const char *defining_abspath,
1370 const char *local_abspath,
1371 svn_node_kind_t kind,
1372 const char *repos_root_url,
1373 const char *repos_uuid,
1374 const char *repos_relpath,
1375 svn_revnum_t operational_revision,
1376 svn_revnum_t revision,
1377 apr_pool_t *scratch_pool)
1379 SVN_ERR_ASSERT(kind == svn_node_dir);
1380 return svn_error_trace(
1381 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1387 operational_revision,
1394 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1395 const char *wri_abspath,
1396 const char *local_abspath,
1397 svn_boolean_t declaration_only,
1398 svn_cancel_func_t cancel_func,
1400 apr_pool_t *scratch_pool)
1402 svn_wc__db_status_t status;
1403 svn_node_kind_t kind;
1405 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1407 wc_ctx->db, local_abspath, wri_abspath,
1408 scratch_pool, scratch_pool));
1410 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1411 NULL, scratch_pool));
1413 if (declaration_only)
1414 return SVN_NO_ERROR;
1416 if (kind == svn_node_dir)
1417 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1419 cancel_func, cancel_baton,
1423 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1424 FALSE /* keep_as_working */,
1425 TRUE /* queue_deletes */,
1426 FALSE /* remove_locks */,
1428 NULL, NULL, scratch_pool));
1429 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1430 cancel_func, cancel_baton,
1434 return SVN_NO_ERROR;
1438 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1439 apr_hash_t **depths,
1440 svn_wc_context_t *wc_ctx,
1441 const char *local_abspath,
1443 apr_pool_t *result_pool,
1444 apr_pool_t *scratch_pool)
1446 if (depth == svn_depth_infinity
1447 || depth == svn_depth_unknown)
1449 return svn_error_trace(
1450 svn_wc__db_externals_gather_definitions(externals, depths,
1451 wc_ctx->db, local_abspath,
1452 result_pool, scratch_pool));
1456 const svn_string_t *value;
1458 *externals = apr_hash_make(result_pool);
1460 local_abspath = apr_pstrdup(result_pool, local_abspath);
1462 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1463 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1467 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1468 return svn_error_trace(err);
1470 svn_error_clear(err);
1475 svn_hash_sets(*externals, local_abspath, value->data);
1477 if (value && depths)
1479 svn_depth_t node_depth;
1480 *depths = apr_hash_make(result_pool);
1482 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1483 NULL, NULL, NULL, &node_depth, NULL,
1484 NULL, NULL, NULL, NULL, NULL, NULL,
1485 NULL, NULL, NULL, NULL, NULL, NULL,
1486 NULL, NULL, NULL, NULL,
1487 wc_ctx->db, local_abspath,
1488 scratch_pool, scratch_pool));
1490 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1493 return SVN_NO_ERROR;
1498 svn_wc__close_db(const char *external_abspath,
1499 svn_wc_context_t *wc_ctx,
1500 apr_pool_t *scratch_pool)
1502 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1504 return SVN_NO_ERROR;
1507 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1508 If @a uri does not appear to be a valid URI, then @a scheme will
1510 static svn_error_t *
1511 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1515 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1519 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1521 *scheme = apr_pstrmemdup(pool, uri, i);
1522 return SVN_NO_ERROR;
1526 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1527 _("URL '%s' does not begin with a scheme"),
1532 svn_wc__resolve_relative_external_url(const char **resolved_url,
1533 const svn_wc_external_item2_t *item,
1534 const char *repos_root_url,
1535 const char *parent_dir_url,
1536 apr_pool_t *result_pool,
1537 apr_pool_t *scratch_pool)
1539 const char *url = item->url;
1540 apr_uri_t parent_dir_uri;
1541 apr_status_t status;
1543 *resolved_url = item->url;
1545 /* If the URL is already absolute, there is nothing to do. */
1546 if (svn_path_is_url(url))
1548 /* "http://server/path" */
1549 *resolved_url = svn_uri_canonicalize(url, result_pool);
1550 return SVN_NO_ERROR;
1555 /* "/path", "//path", and "///path" */
1556 int num_leading_slashes = 1;
1559 num_leading_slashes++;
1561 num_leading_slashes++;
1564 /* "//schema-relative" and in some cases "///schema-relative".
1565 This last format is supported on file:// schema relative. */
1566 url = apr_pstrcat(scratch_pool,
1567 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1568 svn_relpath_canonicalize(url + num_leading_slashes,
1574 /* "^/path" and "../path" */
1575 url = svn_relpath_canonicalize(url, scratch_pool);
1578 /* Parse the parent directory URL into its parts. */
1579 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1581 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1582 _("Illegal parent directory URL '%s'"),
1585 /* If the parent directory URL is at the server root, then the URL
1586 may have no / after the hostname so apr_uri_parse() will leave
1587 the URL's path as NULL. */
1588 if (! parent_dir_uri.path)
1589 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1590 parent_dir_uri.query = NULL;
1591 parent_dir_uri.fragment = NULL;
1593 /* Handle URLs relative to the current directory or to the
1594 repository root. The backpaths may only remove path elements,
1595 not the hostname. This allows an external to refer to another
1596 repository in the same server relative to the location of this
1597 repository, say using SVNParentPath. */
1598 if ((0 == strncmp("../", url, 3)) ||
1599 (0 == strncmp("^/", url, 2)))
1601 apr_array_header_t *base_components;
1602 apr_array_header_t *relative_components;
1605 /* Decompose either the parent directory's URL path or the
1606 repository root's URL path into components. */
1607 if (0 == strncmp("../", url, 3))
1609 base_components = svn_path_decompose(parent_dir_uri.path,
1611 relative_components = svn_path_decompose(url, scratch_pool);
1615 apr_uri_t repos_root_uri;
1617 status = apr_uri_parse(scratch_pool, repos_root_url,
1620 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1621 _("Illegal repository root URL '%s'"),
1624 /* If the repository root URL is at the server root, then
1625 the URL may have no / after the hostname so
1626 apr_uri_parse() will leave the URL's path as NULL. */
1627 if (! repos_root_uri.path)
1628 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1630 base_components = svn_path_decompose(repos_root_uri.path,
1632 relative_components = svn_path_decompose(url + 2, scratch_pool);
1635 for (i = 0; i < relative_components->nelts; ++i)
1637 const char *component = APR_ARRAY_IDX(relative_components,
1640 if (0 == strcmp("..", component))
1642 /* Constructing the final absolute URL together with
1643 apr_uri_unparse() requires that the path be absolute,
1644 so only pop a component if the component being popped
1645 is not the component for the root directory. */
1646 if (base_components->nelts > 1)
1647 apr_array_pop(base_components);
1650 APR_ARRAY_PUSH(base_components, const char *) = component;
1653 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1655 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1656 &parent_dir_uri, 0),
1658 return SVN_NO_ERROR;
1661 /* The remaining URLs are relative to either the scheme or server root
1662 and can only refer to locations inside that scope, so backpaths are
1664 if (svn_path_is_backpath_present(url))
1665 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1666 _("The external relative URL '%s' cannot have "
1667 "backpaths, i.e. '..'"),
1670 /* Relative to the scheme: Build a new URL from the parts we know. */
1671 if (0 == strncmp("//", url, 2))
1675 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1676 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1677 ":", url, (char *)NULL),
1679 return SVN_NO_ERROR;
1682 /* Relative to the server root: Just replace the path portion of the
1686 parent_dir_uri.path = (char *)url;
1687 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1688 &parent_dir_uri, 0),
1690 return SVN_NO_ERROR;
1693 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1694 _("Unrecognized format for the relative external "