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 * Set REV_STR to the element that specifies the revision.
72 * PARENT_DIRECTORY_DISPLAY and LINE are given to return a nice error
75 * If this function returns successfully, then LINE_PARTS will have
76 * only two elements in it.
79 find_and_remove_externals_revision(int *rev_idx,
81 const char **line_parts,
83 svn_wc_external_item2_t *item,
84 const char *parent_directory_display,
90 for (i = 0; i < 2; ++i)
92 const char *token = line_parts[i];
94 if (token[0] == '-' && token[1] == 'r')
96 svn_opt_revision_t end_revision = { svn_opt_revision_unspecified };
97 const char *digits_ptr;
103 if (token[2] == '\0')
105 /* There must be a total of four elements in the line if
107 if (num_line_parts != 4)
111 digits_ptr = line_parts[i+1];
115 /* There must be a total of three elements in the line
117 if (num_line_parts != 3)
121 digits_ptr = token+2;
124 if (svn_opt_parse_revision(&item->revision,
126 digits_ptr, pool) != 0)
128 /* We want a single revision, not a range. */
129 if (end_revision.kind != svn_opt_revision_unspecified)
131 /* Allow only numbers and dates, not keywords. */
132 if (item->revision.kind != svn_opt_revision_number
133 && item->revision.kind != svn_opt_revision_date)
136 /* Shift any line elements past the revision specification
137 down over the revision specification. */
138 for (j = i; j < num_line_parts-shift_count; ++j)
139 line_parts[j] = line_parts[j+shift_count];
140 line_parts[num_line_parts-shift_count] = NULL;
142 *rev_str = apr_psprintf(pool, "-r%s", digits_ptr);
144 /* Found the revision, so leave the function immediately, do
145 * not continue looking for additional revisions. */
150 /* No revision was found, so there must be exactly two items in the
152 if (num_line_parts == 2)
156 return svn_error_createf
157 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
158 _("Error parsing %s property on '%s': '%s'"),
160 parent_directory_display,
165 svn_wc__parse_externals_description(apr_array_header_t **externals_p,
166 apr_array_header_t **parser_infos_p,
167 const char *defining_directory,
169 svn_boolean_t canonicalize_url,
173 apr_array_header_t *externals = NULL;
174 apr_array_header_t *parser_infos = NULL;
175 apr_array_header_t *lines = svn_cstring_split(desc, "\n\r", TRUE, pool);
176 const char *defining_directory_display = svn_path_is_url(defining_directory) ?
177 defining_directory : svn_dirent_local_style(defining_directory, pool);
179 /* If an error occurs halfway through parsing, *externals_p should stay
180 * untouched. So, store the list in a local var first. */
182 externals = apr_array_make(pool, 1, sizeof(svn_wc_external_item2_t *));
186 apr_array_make(pool, 1, sizeof(svn_wc__externals_parser_info_t *));
188 for (i = 0; i < lines->nelts; i++)
190 const char *line = APR_ARRAY_IDX(lines, i, const char *);
194 svn_wc_external_item2_t *item;
197 svn_boolean_t token0_is_url;
198 svn_boolean_t token1_is_url;
199 svn_wc__externals_parser_info_t *info = NULL;
201 /* Index into line_parts where the revision specification
204 const char *rev_str = NULL;
206 if ((! line) || (line[0] == '#'))
211 status = apr_tokenize_to_argv(line, &line_parts, pool);
213 return svn_error_wrap_apr(status,
214 _("Can't split line into components: '%s'"),
216 /* Count the number of tokens. */
217 for (num_line_parts = 0; line_parts[num_line_parts]; num_line_parts++)
220 SVN_ERR(svn_wc_external_item2_create(&item, pool));
221 item->revision.kind = svn_opt_revision_unspecified;
222 item->peg_revision.kind = svn_opt_revision_unspecified;
225 info = apr_pcalloc(pool, sizeof(*info));
228 * There are six different formats of externals:
237 * The last three allow peg revisions in the URL.
239 * With relative URLs and no '-rN' or '-r N', there is no way to
240 * distinguish between 'DIR URL' and 'URL DIR' when URL is a
241 * relative URL like /svn/repos/trunk, so this case is taken as
244 if (num_line_parts < 2 || num_line_parts > 4)
245 return svn_error_createf
246 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
247 _("Error parsing %s property on '%s': '%s'"),
249 defining_directory_display,
252 /* To make it easy to check for the forms, find and remove -r N
253 or -rN from the line item array. If it is found, rev_idx
254 contains the index into line_parts where '-r' was found and
255 set item->revision to the parsed revision. */
256 /* ### ugh. stupid cast. */
257 SVN_ERR(find_and_remove_externals_revision(&rev_idx,
259 (const char **)line_parts,
260 num_line_parts, item,
261 defining_directory_display,
264 token0 = line_parts[0];
265 token1 = line_parts[1];
267 token0_is_url = svn_path_is_url(token0);
268 token1_is_url = svn_path_is_url(token1);
270 if (token0_is_url && token1_is_url)
271 return svn_error_createf
272 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
273 _("Invalid %s property on '%s': "
274 "cannot use two absolute URLs ('%s' and '%s') in an external; "
275 "one must be a path where an absolute or relative URL is "
277 SVN_PROP_EXTERNALS, defining_directory_display, token0, token1);
279 if (0 == rev_idx && token1_is_url)
280 return svn_error_createf
281 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
282 _("Invalid %s property on '%s': "
283 "cannot use a URL '%s' as the target directory for an external "
285 SVN_PROP_EXTERNALS, defining_directory_display, token1);
287 if (1 == rev_idx && token0_is_url)
288 return svn_error_createf
289 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
290 _("Invalid %s property on '%s': "
291 "cannot use a URL '%s' as the target directory for an external "
293 SVN_PROP_EXTERNALS, defining_directory_display, token0);
295 /* The appearance of -r N or -rN forces the type of external.
296 If -r is at the beginning of the line or the first token is
297 an absolute URL or if the second token is not an absolute
298 URL, then the URL supports peg revisions. */
300 (-1 == rev_idx && (token0_is_url || ! token1_is_url)))
302 /* The URL is passed to svn_opt_parse_path in
303 uncanonicalized form so that the scheme relative URL
304 //hostname/foo is not collapsed to a server root relative
305 URL /hostname/foo. */
306 SVN_ERR(svn_opt_parse_path(&item->peg_revision, &item->url,
308 item->target_dir = token1;
312 info->format = svn_wc__external_description_format_2;
315 info->rev_str = apr_pstrdup(pool, rev_str);
317 if (item->peg_revision.kind != svn_opt_revision_unspecified)
318 info->peg_rev_str = strrchr(token0, '@');
323 item->target_dir = token0;
325 item->peg_revision = item->revision;
329 info->format = svn_wc__external_description_format_1;
333 info->rev_str = apr_pstrdup(pool, rev_str);
334 info->peg_rev_str = info->rev_str;
339 SVN_ERR(svn_opt_resolve_revisions(&item->peg_revision,
340 &item->revision, TRUE, FALSE,
343 item->target_dir = svn_dirent_internal_style(item->target_dir, pool);
345 if (item->target_dir[0] == '\0'
346 || svn_dirent_is_absolute(item->target_dir)
347 || svn_path_is_backpath_present(item->target_dir)
348 || !svn_dirent_skip_ancestor("dummy",
349 svn_dirent_join("dummy",
352 return svn_error_createf
353 (SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
354 _("Invalid %s property on '%s': "
355 "target '%s' is an absolute path or involves '..'"),
357 defining_directory_display,
360 if (canonicalize_url)
362 /* Uh... this is stupid. But it's consistent with what our
363 code did before we split up the relpath/dirent/uri APIs.
364 Still, given this, it's no wonder that our own libraries
365 don't ask this function to canonicalize the results. */
366 if (svn_path_is_url(item->url))
367 item->url = svn_uri_canonicalize(item->url, pool);
369 item->url = svn_dirent_canonicalize(item->url, pool);
373 APR_ARRAY_PUSH(externals, svn_wc_external_item2_t *) = item;
375 APR_ARRAY_PUSH(parser_infos, svn_wc__externals_parser_info_t *) = info;
379 *externals_p = externals;
381 *parser_infos_p = parser_infos;
387 svn_wc_parse_externals_description3(apr_array_header_t **externals_p,
388 const char *defining_directory,
390 svn_boolean_t canonicalize_url,
393 return svn_error_trace(svn_wc__parse_externals_description(externals_p,
402 svn_wc__externals_find_target_dups(apr_array_header_t **duplicate_targets,
403 apr_array_header_t *externals,
405 apr_pool_t *scratch_pool)
411 apr_hash_t *targets = apr_hash_make(scratch_pool);
412 apr_hash_t *targets2 = NULL;
413 *duplicate_targets = NULL;
415 for (i = 0; i < externals->nelts; i++)
417 target = APR_ARRAY_IDX(externals, i,
418 svn_wc_external_item2_t*)->target_dir;
419 len = apr_hash_count(targets);
420 svn_hash_sets(targets, target, "");
421 if (len == apr_hash_count(targets))
423 /* Hashtable length is unchanged. This must be a duplicate. */
425 /* Collapse multiple duplicates of the same target by using a second
428 targets2 = apr_hash_make(scratch_pool);
429 len2 = apr_hash_count(targets2);
430 svn_hash_sets(targets2, target, "");
431 if (len2 < apr_hash_count(targets2))
433 /* The second hash list just got bigger, i.e. this target has
434 * not been counted as duplicate before. */
435 if (! *duplicate_targets)
437 *duplicate_targets = apr_array_make(
438 pool, 1, sizeof(svn_wc_external_item2_t*));
440 APR_ARRAY_PUSH((*duplicate_targets), const char *) = target;
442 /* Else, this same target has already been recorded as a duplicate,
443 * don't count it again. */
454 /* We explicitly use wri_abspath and local_abspath here, because we
455 might want to install file externals in an obstructing working copy */
456 const char *wri_abspath; /* The working defining the file external */
457 const char *local_abspath; /* The file external itself */
458 const char *name; /* The basename of the file external itself */
460 /* Information from the caller */
461 svn_boolean_t use_commit_times;
462 const apr_array_header_t *ext_patterns;
463 const char *diff3cmd;
465 const char *repos_root_url;
466 const char *repos_uuid;
467 const char *old_repos_relpath;
468 const char *new_repos_relpath;
470 const char *record_ancestor_abspath;
471 const char *recorded_repos_relpath;
472 svn_revnum_t recorded_peg_revision;
473 svn_revnum_t recorded_revision;
475 /* Introducing a new file external */
478 svn_cancel_func_t cancel_func;
480 svn_wc_notify_func2_t notify_func;
483 svn_revnum_t *target_revision;
485 /* What was there before the update */
486 svn_revnum_t original_revision;
487 const svn_checksum_t *original_checksum;
489 /* What we are installing now */
490 svn_wc__db_install_data_t *install_data;
491 svn_checksum_t *new_sha1_checksum;
492 svn_checksum_t *new_md5_checksum;
494 /* List of incoming propchanges */
495 apr_array_header_t *propchanges;
497 /* Array of svn_prop_inherited_item_t * structures representing the
498 properties inherited by the base node at LOCAL_ABSPATH. */
499 apr_array_header_t *iprops;
501 /* The last change information */
502 svn_revnum_t changed_rev;
503 apr_time_t changed_date;
504 const char *changed_author;
506 svn_boolean_t had_props;
508 svn_boolean_t file_closed;
511 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
513 set_target_revision(void *edit_baton,
514 svn_revnum_t target_revision,
517 struct edit_baton *eb = edit_baton;
519 *eb->target_revision = target_revision;
524 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
526 open_root(void *edit_baton,
527 svn_revnum_t base_revision,
528 apr_pool_t *dir_pool,
531 *root_baton = edit_baton;
535 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
537 add_file(const char *path,
539 const char *copyfrom_path,
540 svn_revnum_t copyfrom_revision,
541 apr_pool_t *file_pool,
544 struct edit_baton *eb = parent_baton;
545 if (strcmp(path, eb->name))
546 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
547 _("This editor can only update '%s'"),
548 svn_dirent_local_style(eb->local_abspath,
552 eb->original_revision = SVN_INVALID_REVNUM;
558 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
560 open_file(const char *path,
562 svn_revnum_t base_revision,
563 apr_pool_t *file_pool,
566 struct edit_baton *eb = parent_baton;
567 svn_node_kind_t kind;
568 if (strcmp(path, eb->name))
569 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
570 _("This editor can only update '%s'"),
571 svn_dirent_local_style(eb->local_abspath,
575 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
576 &eb->old_repos_relpath, NULL, NULL,
578 &eb->changed_date, &eb->changed_author,
579 NULL, &eb->original_checksum, NULL, NULL,
580 &eb->had_props, NULL, NULL,
581 eb->db, eb->local_abspath,
582 eb->pool, file_pool));
584 if (kind != svn_node_file)
585 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
586 _("Node '%s' is no existing file external"),
587 svn_dirent_local_style(eb->local_abspath,
592 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
594 apply_textdelta(void *file_baton,
595 const char *base_checksum_digest,
597 svn_txdelta_window_handler_t *handler,
598 void **handler_baton)
600 struct edit_baton *eb = file_baton;
601 svn_stream_t *src_stream;
602 svn_stream_t *dest_stream;
604 if (eb->original_checksum)
606 if (base_checksum_digest)
608 svn_checksum_t *expected_checksum;
609 const svn_checksum_t *original_md5;
611 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
612 base_checksum_digest, pool));
614 if (eb->original_checksum->kind != svn_checksum_md5)
615 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
616 eb->db, eb->wri_abspath,
617 eb->original_checksum,
620 original_md5 = eb->original_checksum;
622 if (!svn_checksum_match(expected_checksum, original_md5))
623 return svn_error_trace(svn_checksum_mismatch_err(
627 _("Base checksum mismatch for '%s'"),
628 svn_dirent_local_style(eb->local_abspath,
632 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
633 eb->wri_abspath, eb->original_checksum,
637 src_stream = svn_stream_empty(pool);
639 SVN_ERR(svn_wc__db_pristine_prepare_install(&dest_stream,
641 &eb->new_sha1_checksum,
642 &eb->new_md5_checksum,
643 eb->db, eb->wri_abspath,
646 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
647 handler, handler_baton);
652 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
654 change_file_prop(void *file_baton,
656 const svn_string_t *value,
659 struct edit_baton *eb = file_baton;
660 svn_prop_t *propchange;
662 propchange = apr_array_push(eb->propchanges);
663 propchange->name = apr_pstrdup(eb->pool, name);
664 propchange->value = svn_string_dup(value, eb->pool);
669 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
671 close_file(void *file_baton,
672 const char *expected_md5_digest,
675 struct edit_baton *eb = file_baton;
676 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
677 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
678 svn_boolean_t obstructed = FALSE;
680 eb->file_closed = TRUE; /* We bump the revision here */
682 /* Check the checksum, if provided */
683 if (expected_md5_digest)
685 svn_checksum_t *expected_md5_checksum;
686 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
688 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
689 expected_md5_digest, pool));
691 if (actual_md5_checksum == NULL)
693 actual_md5_checksum = eb->original_checksum;
695 if (actual_md5_checksum != NULL
696 && actual_md5_checksum->kind != svn_checksum_md5)
698 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
699 eb->db, eb->wri_abspath,
705 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
706 return svn_checksum_mismatch_err(
707 expected_md5_checksum,
708 actual_md5_checksum, pool,
709 _("Checksum mismatch for '%s'"),
710 svn_dirent_local_style(eb->local_abspath, pool));
713 /* First move the file in the pristine store; this hands over the cleanup
714 behavior to the pristine store. */
715 if (eb->new_sha1_checksum)
717 SVN_ERR(svn_wc__db_pristine_install(eb->install_data,
718 eb->new_sha1_checksum,
719 eb->new_md5_checksum, pool));
721 eb->install_data = NULL;
724 /* Merge the changes */
726 svn_skel_t *all_work_items = NULL;
727 svn_skel_t *conflict_skel = NULL;
728 svn_skel_t *work_item;
729 apr_hash_t *base_props = NULL;
730 apr_hash_t *actual_props = NULL;
731 apr_hash_t *new_pristine_props = NULL;
732 apr_hash_t *new_actual_props = NULL;
733 apr_hash_t *new_dav_props = NULL;
734 const svn_checksum_t *new_checksum = NULL;
735 const svn_checksum_t *original_checksum = NULL;
737 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
741 new_checksum = eb->original_checksum;
744 SVN_ERR(svn_wc__db_base_get_props(
745 &base_props, eb->db, eb->local_abspath, pool, pool));
747 SVN_ERR(svn_wc__db_read_props(
748 &actual_props, eb->db, eb->local_abspath, pool, pool));
752 base_props = apr_hash_make(pool);
755 actual_props = apr_hash_make(pool);
757 if (eb->new_sha1_checksum)
758 new_checksum = eb->new_sha1_checksum;
760 /* Merge the properties */
762 apr_array_header_t *entry_prop_changes;
763 apr_array_header_t *dav_prop_changes;
764 apr_array_header_t *regular_prop_changes;
767 SVN_ERR(svn_categorize_props(eb->propchanges, &entry_prop_changes,
768 &dav_prop_changes, ®ular_prop_changes,
771 /* Read the entry-prop changes to update the last-changed info. */
772 for (i = 0; i < entry_prop_changes->nelts; i++)
774 const svn_prop_t *prop = &APR_ARRAY_IDX(entry_prop_changes, i,
778 continue; /* authz or something */
780 if (! strcmp(prop->name, SVN_PROP_ENTRY_LAST_AUTHOR))
781 eb->changed_author = apr_pstrdup(pool, prop->value->data);
782 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_REV))
785 SVN_ERR(svn_cstring_atoi64(&rev, prop->value->data));
786 eb->changed_rev = (svn_revnum_t)rev;
788 else if (! strcmp(prop->name, SVN_PROP_ENTRY_COMMITTED_DATE))
789 SVN_ERR(svn_time_from_cstring(&eb->changed_date, prop->value->data,
793 /* Store the DAV-prop (aka WC-prop) changes. (This treats a list
794 * of changes as a list of new props, but we only use this when
795 * adding a new file and it's equivalent in that case.) */
796 if (dav_prop_changes->nelts > 0)
797 new_dav_props = svn_prop_array_to_hash(dav_prop_changes, pool);
799 /* Merge the regular prop changes. */
800 if (regular_prop_changes->nelts > 0)
802 new_pristine_props = svn_prop__patch(base_props, regular_prop_changes,
804 SVN_ERR(svn_wc__merge_props(&conflict_skel,
807 eb->db, eb->local_abspath,
808 NULL /* server_baseprops*/,
811 regular_prop_changes,
816 new_pristine_props = base_props;
817 new_actual_props = actual_props;
822 if (eb->new_sha1_checksum)
824 svn_node_kind_t disk_kind;
825 svn_boolean_t install_pristine = FALSE;
827 SVN_ERR(svn_io_check_path(eb->local_abspath, &disk_kind, pool));
829 if (disk_kind == svn_node_none)
831 /* Just install the file */
832 install_pristine = TRUE;
833 content_state = svn_wc_notify_state_changed;
835 else if (disk_kind != svn_node_file
836 || (eb->added && disk_kind == svn_node_file))
838 /* The node is obstructed; we just change the DB */
840 content_state = svn_wc_notify_state_unchanged;
844 svn_boolean_t is_mod;
845 SVN_ERR(svn_wc__internal_file_modified_p(&is_mod,
846 eb->db, eb->local_abspath,
851 install_pristine = TRUE;
852 content_state = svn_wc_notify_state_changed;
856 svn_boolean_t found_text_conflict;
858 /* Ok, we have to do some work to merge a local change */
859 SVN_ERR(svn_wc__perform_file_merge(&work_item,
861 &found_text_conflict,
869 eb->original_revision,
870 *eb->target_revision,
877 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
880 if (found_text_conflict)
881 content_state = svn_wc_notify_state_conflicted;
883 content_state = svn_wc_notify_state_merged;
886 if (install_pristine)
888 SVN_ERR(svn_wc__wq_build_file_install(&work_item, eb->db,
891 eb->use_commit_times, TRUE,
894 all_work_items = svn_wc__wq_merge(all_work_items, work_item, pool);
899 content_state = svn_wc_notify_state_unchanged;
900 /* ### Retranslate on magic property changes, etc. */
903 /* Generate a conflict description, if needed */
906 SVN_ERR(svn_wc__conflict_skel_set_op_switch(
908 svn_wc_conflict_version_create2(
911 eb->old_repos_relpath,
912 eb->original_revision,
915 svn_wc_conflict_version_create2(
918 eb->new_repos_relpath,
919 *eb->target_revision,
923 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
924 eb->db, eb->local_abspath,
927 all_work_items = svn_wc__wq_merge(all_work_items, work_item,
931 /* Install the file in the DB */
932 SVN_ERR(svn_wc__db_external_add_file(
936 eb->new_repos_relpath,
939 *eb->target_revision,
947 eb->record_ancestor_abspath,
948 eb->recorded_repos_relpath,
949 eb->recorded_peg_revision,
950 eb->recorded_revision,
951 TRUE, new_actual_props,
952 FALSE /* keep_recorded_info */,
957 /* close_edit may also update iprops for switched files, catching
958 those for which close_file is never called (e.g. an update of a
959 file external with no changes). So as a minor optimization we
960 clear the iprops so as not to set them again in close_edit. */
963 /* Run the work queue to complete the installation */
964 SVN_ERR(svn_wc__wq_run(eb->db, eb->wri_abspath,
965 eb->cancel_func, eb->cancel_baton, pool));
971 svn_wc_notify_action_t action;
972 svn_wc_notify_t *notify;
975 action = obstructed ? svn_wc_notify_update_shadowed_update
976 : svn_wc_notify_update_update;
978 action = obstructed ? svn_wc_notify_update_shadowed_add
979 : svn_wc_notify_update_add;
981 notify = svn_wc_create_notify(eb->local_abspath, action, pool);
982 notify->kind = svn_node_file;
984 notify->revision = *eb->target_revision;
985 notify->prop_state = prop_state;
986 notify->content_state = content_state;
988 notify->old_revision = eb->original_revision;
990 eb->notify_func(eb->notify_baton, notify, pool);
996 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
998 close_edit(void *edit_baton,
1001 struct edit_baton *eb = edit_baton;
1003 if (!eb->file_closed)
1005 apr_hash_t *wcroot_iprops = NULL;
1006 /* The file wasn't updated, but its url or revision might have...
1007 e.g. switch between branches for relative externals.
1009 Just bump the information as that is just as expensive as
1010 investigating when we should and shouldn't update it...
1011 and avoid hard to debug edge cases */
1015 wcroot_iprops = apr_hash_make(pool);
1016 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
1019 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
1022 eb->new_repos_relpath,
1025 *eb->target_revision,
1027 /* exclude_relpaths */,
1029 TRUE /* empty update */,
1035 return SVN_NO_ERROR;
1039 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
1041 svn_revnum_t *target_revision,
1042 svn_wc_context_t *wc_ctx,
1043 const char *local_abspath,
1044 const char *wri_abspath,
1046 const char *repos_root_url,
1047 const char *repos_uuid,
1048 apr_array_header_t *iprops,
1049 svn_boolean_t use_commit_times,
1050 const char *diff3_cmd,
1051 const apr_array_header_t *preserved_exts,
1052 const char *record_ancestor_abspath,
1053 const char *recorded_url,
1054 const svn_opt_revision_t *recorded_peg_rev,
1055 const svn_opt_revision_t *recorded_rev,
1056 svn_cancel_func_t cancel_func,
1058 svn_wc_notify_func2_t notify_func,
1060 apr_pool_t *result_pool,
1061 apr_pool_t *scratch_pool)
1063 svn_wc__db_t *db = wc_ctx->db;
1064 apr_pool_t *edit_pool = result_pool;
1065 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1066 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1068 eb->pool = edit_pool;
1070 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1072 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1074 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1075 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1076 eb->target_revision = target_revision;
1078 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1079 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1080 eb->new_repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url, url, edit_pool);
1081 eb->old_repos_relpath = eb->new_repos_relpath;
1083 eb->original_revision = SVN_INVALID_REVNUM;
1085 eb->iprops = iprops;
1087 eb->use_commit_times = use_commit_times;
1088 eb->ext_patterns = preserved_exts;
1089 eb->diff3cmd = diff3_cmd;
1091 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1092 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1095 eb->changed_rev = SVN_INVALID_REVNUM;
1097 if (recorded_peg_rev->kind == svn_opt_revision_number)
1098 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1100 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1102 if (recorded_rev->kind == svn_opt_revision_number)
1103 eb->recorded_revision = recorded_rev->value.number;
1105 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1107 eb->cancel_func = cancel_func;
1108 eb->cancel_baton = cancel_baton;
1109 eb->notify_func = notify_func;
1110 eb->notify_baton = notify_baton;
1112 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1114 tree_editor->open_root = open_root;
1115 tree_editor->set_target_revision = set_target_revision;
1116 tree_editor->add_file = add_file;
1117 tree_editor->open_file = open_file;
1118 tree_editor->apply_textdelta = apply_textdelta;
1119 tree_editor->change_file_prop = change_file_prop;
1120 tree_editor->close_file = close_file;
1121 tree_editor->close_edit = close_edit;
1123 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1130 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1131 const char *local_abspath,
1132 const svn_ra_reporter3_t *reporter,
1134 svn_boolean_t restore_files,
1135 svn_boolean_t use_commit_times,
1136 svn_cancel_func_t cancel_func,
1138 svn_wc_notify_func2_t notify_func,
1140 apr_pool_t *scratch_pool)
1142 svn_wc__db_t *db = wc_ctx->db;
1144 svn_node_kind_t kind;
1145 svn_wc__db_lock_t *lock;
1146 svn_revnum_t revision;
1147 const char *repos_root_url;
1148 const char *repos_relpath;
1149 svn_boolean_t update_root;
1151 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1152 &repos_relpath, &repos_root_url, NULL, NULL,
1153 NULL, NULL, NULL, NULL, NULL, &lock,
1154 NULL, NULL, &update_root,
1156 scratch_pool, scratch_pool);
1159 || kind == svn_node_dir
1162 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1163 return svn_error_trace(err);
1165 svn_error_clear(err);
1167 /* We don't know about this node, so all we have to do is tell
1168 the reporter that we don't know this node.
1170 But first we have to start the report by sending some basic
1171 information for the root. */
1173 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1174 FALSE, NULL, scratch_pool));
1175 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1177 /* Finish the report, which causes the update editor to be
1179 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1181 return SVN_NO_ERROR;
1187 svn_node_kind_t disk_kind;
1188 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1190 if (disk_kind == svn_node_none)
1192 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1197 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1198 return svn_error_trace(err);
1200 svn_error_clear(err);
1205 /* Report that we know the path */
1206 SVN_ERR(reporter->set_path(report_baton, "", revision,
1207 svn_depth_infinity, FALSE, NULL,
1210 /* For compatibility with the normal update editor report we report
1211 the target as switched.
1213 ### We can probably report a parent url and unswitched later */
1214 SVN_ERR(reporter->link_path(report_baton, "",
1215 svn_path_url_add_component2(repos_root_url,
1220 FALSE /* start_empty*/,
1221 lock ? lock->token : NULL,
1225 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1229 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1230 const char **defining_abspath,
1231 const char **defining_url,
1232 svn_revnum_t *defining_operational_revision,
1233 svn_revnum_t *defining_revision,
1234 svn_wc_context_t *wc_ctx,
1235 const char *wri_abspath,
1236 const char *local_abspath,
1237 svn_boolean_t ignore_enoent,
1238 apr_pool_t *result_pool,
1239 apr_pool_t *scratch_pool)
1241 const char *repos_root_url;
1242 svn_wc__db_status_t status;
1243 svn_node_kind_t kind;
1246 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1247 defining_url ? &repos_root_url : NULL, NULL,
1248 defining_url, defining_operational_revision,
1250 wc_ctx->db, local_abspath, wri_abspath,
1251 result_pool, scratch_pool);
1255 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1256 return svn_error_trace(err);
1258 svn_error_clear(err);
1261 *external_kind = svn_node_none;
1263 if (defining_abspath)
1264 *defining_abspath = NULL;
1267 *defining_url = NULL;
1269 if (defining_operational_revision)
1270 *defining_operational_revision = SVN_INVALID_REVNUM;
1272 if (defining_revision)
1273 *defining_revision = SVN_INVALID_REVNUM;
1275 return SVN_NO_ERROR;
1280 if (status != svn_wc__db_status_normal)
1281 *external_kind = svn_node_unknown;
1286 case svn_node_symlink:
1287 *external_kind = svn_node_file;
1290 *external_kind = svn_node_dir;
1293 *external_kind = svn_node_none;
1297 if (defining_url && *defining_url)
1298 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1301 return SVN_NO_ERROR;
1304 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1305 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1306 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1307 static svn_error_t *
1308 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1309 svn_wc_context_t *wc_ctx,
1310 svn_wc__committable_external_info_t *xinfo,
1311 apr_pool_t *scratch_pool)
1313 const char *repos_relpath;
1314 const char *repos_root_url;
1317 *is_rolled_out = FALSE;
1319 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1320 &repos_root_url, NULL, NULL, NULL, NULL,
1321 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1322 wc_ctx->db, xinfo->local_abspath,
1323 scratch_pool, scratch_pool);
1327 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1329 svn_error_clear(err);
1330 return SVN_NO_ERROR;
1335 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1336 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1337 return SVN_NO_ERROR;
1341 svn_wc__committable_externals_below(apr_array_header_t **externals,
1342 svn_wc_context_t *wc_ctx,
1343 const char *local_abspath,
1345 apr_pool_t *result_pool,
1346 apr_pool_t *scratch_pool)
1348 apr_array_header_t *orig_externals;
1350 apr_pool_t *iterpool;
1352 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1353 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1356 depth != svn_depth_infinity,
1357 result_pool, scratch_pool));
1359 if (orig_externals == NULL)
1360 return SVN_NO_ERROR;
1362 iterpool = svn_pool_create(scratch_pool);
1364 for (i = 0; i < orig_externals->nelts; i++)
1366 svn_boolean_t is_rolled_out;
1368 svn_wc__committable_external_info_t *xinfo =
1369 APR_ARRAY_IDX(orig_externals, i,
1370 svn_wc__committable_external_info_t *);
1372 /* Discard dirs for svn_depth_files (s.a.). */
1373 if (depth == svn_depth_files
1374 && xinfo->kind == svn_node_dir)
1377 svn_pool_clear(iterpool);
1379 /* Discard those externals that are not currently checked out. */
1380 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1382 if (! is_rolled_out)
1385 if (*externals == NULL)
1386 *externals = apr_array_make(
1388 sizeof(svn_wc__committable_external_info_t *));
1390 APR_ARRAY_PUSH(*externals,
1391 svn_wc__committable_external_info_t *) = xinfo;
1393 if (depth != svn_depth_infinity)
1396 /* Are there any nested externals? */
1397 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1398 xinfo->local_abspath,
1400 result_pool, iterpool));
1403 return SVN_NO_ERROR;
1407 svn_wc__externals_defined_below(apr_hash_t **externals,
1408 svn_wc_context_t *wc_ctx,
1409 const char *local_abspath,
1410 apr_pool_t *result_pool,
1411 apr_pool_t *scratch_pool)
1413 return svn_error_trace(
1414 svn_wc__db_externals_defined_below(externals,
1415 wc_ctx->db, local_abspath,
1416 result_pool, scratch_pool));
1420 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1421 const char *defining_abspath,
1422 const char *local_abspath,
1423 svn_node_kind_t kind,
1424 const char *repos_root_url,
1425 const char *repos_uuid,
1426 const char *repos_relpath,
1427 svn_revnum_t operational_revision,
1428 svn_revnum_t revision,
1429 apr_pool_t *scratch_pool)
1431 SVN_ERR_ASSERT(kind == svn_node_dir);
1432 return svn_error_trace(
1433 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1439 operational_revision,
1446 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1447 const char *wri_abspath,
1448 const char *local_abspath,
1449 svn_boolean_t declaration_only,
1450 svn_cancel_func_t cancel_func,
1452 apr_pool_t *scratch_pool)
1454 svn_wc__db_status_t status;
1455 svn_node_kind_t kind;
1457 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1459 wc_ctx->db, local_abspath, wri_abspath,
1460 scratch_pool, scratch_pool));
1462 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1463 NULL, scratch_pool));
1465 if (declaration_only)
1466 return SVN_NO_ERROR;
1468 if (kind == svn_node_dir)
1469 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1471 cancel_func, cancel_baton,
1475 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1478 NULL, NULL, scratch_pool));
1479 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1480 cancel_func, cancel_baton,
1484 return SVN_NO_ERROR;
1488 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1489 apr_hash_t **depths,
1490 svn_wc_context_t *wc_ctx,
1491 const char *local_abspath,
1493 apr_pool_t *result_pool,
1494 apr_pool_t *scratch_pool)
1496 if (depth == svn_depth_infinity
1497 || depth == svn_depth_unknown)
1499 return svn_error_trace(
1500 svn_wc__db_externals_gather_definitions(externals, depths,
1501 wc_ctx->db, local_abspath,
1502 result_pool, scratch_pool));
1506 const svn_string_t *value;
1508 *externals = apr_hash_make(result_pool);
1510 local_abspath = apr_pstrdup(result_pool, local_abspath);
1512 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1513 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1517 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1518 return svn_error_trace(err);
1520 svn_error_clear(err);
1525 svn_hash_sets(*externals, local_abspath, value->data);
1527 if (value && depths)
1529 svn_depth_t node_depth;
1530 *depths = apr_hash_make(result_pool);
1532 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1533 NULL, NULL, NULL, &node_depth, NULL,
1534 NULL, NULL, NULL, NULL, NULL, NULL,
1535 NULL, NULL, NULL, NULL, NULL, NULL,
1536 NULL, NULL, NULL, NULL,
1537 wc_ctx->db, local_abspath,
1538 scratch_pool, scratch_pool));
1540 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1543 return SVN_NO_ERROR;
1548 svn_wc__close_db(const char *external_abspath,
1549 svn_wc_context_t *wc_ctx,
1550 apr_pool_t *scratch_pool)
1552 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1554 return SVN_NO_ERROR;
1557 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1558 If @a uri does not appear to be a valid URI, then @a scheme will
1560 static svn_error_t *
1561 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1565 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1569 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1571 *scheme = apr_pstrmemdup(pool, uri, i);
1572 return SVN_NO_ERROR;
1576 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1577 _("URL '%s' does not begin with a scheme"),
1582 svn_wc__resolve_relative_external_url(const char **resolved_url,
1583 const svn_wc_external_item2_t *item,
1584 const char *repos_root_url,
1585 const char *parent_dir_url,
1586 apr_pool_t *result_pool,
1587 apr_pool_t *scratch_pool)
1589 const char *url = item->url;
1590 apr_uri_t parent_dir_uri;
1591 apr_status_t status;
1593 *resolved_url = item->url;
1595 /* If the URL is already absolute, there is nothing to do. */
1596 if (svn_path_is_url(url))
1598 /* "http://server/path" */
1599 *resolved_url = svn_uri_canonicalize(url, result_pool);
1600 return SVN_NO_ERROR;
1605 /* "/path", "//path", and "///path" */
1606 int num_leading_slashes = 1;
1609 num_leading_slashes++;
1611 num_leading_slashes++;
1614 /* "//schema-relative" and in some cases "///schema-relative".
1615 This last format is supported on file:// schema relative. */
1616 url = apr_pstrcat(scratch_pool,
1617 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1618 svn_relpath_canonicalize(url + num_leading_slashes,
1624 /* "^/path" and "../path" */
1625 url = svn_relpath_canonicalize(url, scratch_pool);
1628 /* Parse the parent directory URL into its parts. */
1629 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1631 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1632 _("Illegal parent directory URL '%s'"),
1635 /* If the parent directory URL is at the server root, then the URL
1636 may have no / after the hostname so apr_uri_parse() will leave
1637 the URL's path as NULL. */
1638 if (! parent_dir_uri.path)
1639 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1640 parent_dir_uri.query = NULL;
1641 parent_dir_uri.fragment = NULL;
1643 /* Handle URLs relative to the current directory or to the
1644 repository root. The backpaths may only remove path elements,
1645 not the hostname. This allows an external to refer to another
1646 repository in the same server relative to the location of this
1647 repository, say using SVNParentPath. */
1648 if ((0 == strncmp("../", url, 3)) ||
1649 (0 == strncmp("^/", url, 2)))
1651 apr_array_header_t *base_components;
1652 apr_array_header_t *relative_components;
1655 /* Decompose either the parent directory's URL path or the
1656 repository root's URL path into components. */
1657 if (0 == strncmp("../", url, 3))
1659 base_components = svn_path_decompose(parent_dir_uri.path,
1661 relative_components = svn_path_decompose(url, scratch_pool);
1665 apr_uri_t repos_root_uri;
1667 status = apr_uri_parse(scratch_pool, repos_root_url,
1670 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1671 _("Illegal repository root URL '%s'"),
1674 /* If the repository root URL is at the server root, then
1675 the URL may have no / after the hostname so
1676 apr_uri_parse() will leave the URL's path as NULL. */
1677 if (! repos_root_uri.path)
1678 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1680 base_components = svn_path_decompose(repos_root_uri.path,
1682 relative_components = svn_path_decompose(url + 2, scratch_pool);
1685 for (i = 0; i < relative_components->nelts; ++i)
1687 const char *component = APR_ARRAY_IDX(relative_components,
1690 if (0 == strcmp("..", component))
1692 /* Constructing the final absolute URL together with
1693 apr_uri_unparse() requires that the path be absolute,
1694 so only pop a component if the component being popped
1695 is not the component for the root directory. */
1696 if (base_components->nelts > 1)
1697 apr_array_pop(base_components);
1700 APR_ARRAY_PUSH(base_components, const char *) = component;
1703 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1705 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1706 &parent_dir_uri, 0),
1708 return SVN_NO_ERROR;
1711 /* The remaining URLs are relative to either the scheme or server root
1712 and can only refer to locations inside that scope, so backpaths are
1714 if (svn_path_is_backpath_present(url))
1715 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1716 _("The external relative URL '%s' cannot have "
1717 "backpaths, i.e. '..'"),
1720 /* Relative to the scheme: Build a new URL from the parts we know. */
1721 if (0 == strncmp("//", url, 2))
1725 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1726 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1727 ":", url, SVN_VA_NULL),
1729 return SVN_NO_ERROR;
1732 /* Relative to the server root: Just replace the path portion of the
1736 parent_dir_uri.path = (char *)url;
1737 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1738 &parent_dir_uri, 0),
1740 return SVN_NO_ERROR;
1743 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1744 _("Unrecognized format for the relative external "