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;
409 const char *repos_root_url;
410 const char *repos_uuid;
412 const char *record_ancestor_abspath;
413 const char *recorded_repos_relpath;
414 svn_revnum_t recorded_peg_revision;
415 svn_revnum_t recorded_revision;
417 /* Introducing a new file external */
420 svn_wc_conflict_resolver_func2_t conflict_func;
421 void *conflict_baton;
422 svn_cancel_func_t cancel_func;
424 svn_wc_notify_func2_t notify_func;
427 svn_revnum_t *target_revision;
429 /* What was there before the update */
430 svn_revnum_t original_revision;
431 const svn_checksum_t *original_checksum;
433 /* What we are installing now */
434 const char *new_pristine_abspath;
435 svn_checksum_t *new_sha1_checksum;
436 svn_checksum_t *new_md5_checksum;
438 /* List of incoming propchanges */
439 apr_array_header_t *propchanges;
441 /* Array of svn_prop_inherited_item_t * structures representing the
442 properties inherited by the base node at LOCAL_ABSPATH. */
443 apr_array_header_t *iprops;
445 /* The last change information */
446 svn_revnum_t changed_rev;
447 apr_time_t changed_date;
448 const char *changed_author;
450 svn_boolean_t had_props;
452 svn_boolean_t file_closed;
455 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
457 set_target_revision(void *edit_baton,
458 svn_revnum_t target_revision,
461 struct edit_baton *eb = edit_baton;
463 *eb->target_revision = target_revision;
468 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
470 open_root(void *edit_baton,
471 svn_revnum_t base_revision,
472 apr_pool_t *dir_pool,
475 *root_baton = edit_baton;
479 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
481 add_file(const char *path,
483 const char *copyfrom_path,
484 svn_revnum_t copyfrom_revision,
485 apr_pool_t *file_pool,
488 struct edit_baton *eb = parent_baton;
489 if (strcmp(path, eb->name))
490 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
491 _("This editor can only update '%s'"),
492 svn_dirent_local_style(eb->local_abspath,
496 eb->original_revision = SVN_INVALID_REVNUM;
502 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
504 open_file(const char *path,
506 svn_revnum_t base_revision,
507 apr_pool_t *file_pool,
510 struct edit_baton *eb = parent_baton;
511 svn_node_kind_t kind;
512 if (strcmp(path, eb->name))
513 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
514 _("This editor can only update '%s'"),
515 svn_dirent_local_style(eb->local_abspath,
519 SVN_ERR(svn_wc__db_base_get_info(NULL, &kind, &eb->original_revision,
520 NULL, NULL, NULL, &eb->changed_rev,
521 &eb->changed_date, &eb->changed_author,
522 NULL, &eb->original_checksum, NULL, NULL,
523 &eb->had_props, NULL, NULL,
524 eb->db, eb->local_abspath,
525 eb->pool, file_pool));
527 if (kind != svn_node_file)
528 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
529 _("Node '%s' is no existing file external"),
530 svn_dirent_local_style(eb->local_abspath,
535 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
537 apply_textdelta(void *file_baton,
538 const char *base_checksum_digest,
540 svn_txdelta_window_handler_t *handler,
541 void **handler_baton)
543 struct edit_baton *eb = file_baton;
544 svn_stream_t *src_stream;
545 svn_stream_t *dest_stream;
547 if (eb->original_checksum)
549 if (base_checksum_digest)
551 svn_checksum_t *expected_checksum;
552 const svn_checksum_t *original_md5;
554 SVN_ERR(svn_checksum_parse_hex(&expected_checksum, svn_checksum_md5,
555 base_checksum_digest, pool));
557 if (eb->original_checksum->kind != svn_checksum_md5)
558 SVN_ERR(svn_wc__db_pristine_get_md5(&original_md5,
559 eb->db, eb->wri_abspath,
560 eb->original_checksum,
563 original_md5 = eb->original_checksum;
565 if (!svn_checksum_match(expected_checksum, original_md5))
566 return svn_error_trace(svn_checksum_mismatch_err(
570 _("Base checksum mismatch for '%s'"),
571 svn_dirent_local_style(eb->local_abspath,
575 SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, eb->db,
576 eb->wri_abspath, eb->original_checksum,
580 src_stream = svn_stream_empty(pool);
582 SVN_ERR(svn_wc__open_writable_base(&dest_stream, &eb->new_pristine_abspath,
583 &eb->new_md5_checksum,
584 &eb->new_sha1_checksum,
585 eb->db, eb->wri_abspath,
588 svn_txdelta_apply(src_stream, dest_stream, NULL, eb->local_abspath, pool,
589 handler, handler_baton);
594 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
596 change_file_prop(void *file_baton,
598 const svn_string_t *value,
601 struct edit_baton *eb = file_baton;
602 svn_prop_t *propchange;
604 propchange = apr_array_push(eb->propchanges);
605 propchange->name = apr_pstrdup(eb->pool, name);
606 propchange->value = value ? svn_string_dup(value, eb->pool) : NULL;
611 /* svn_delta_editor_t function for svn_wc__get_file_external_editor */
613 close_file(void *file_baton,
614 const char *expected_md5_digest,
617 struct edit_baton *eb = file_baton;
618 svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown;
619 svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown;
620 svn_boolean_t obstructed = FALSE;
622 eb->file_closed = TRUE; /* We bump the revision here */
624 /* Check the checksum, if provided */
625 if (expected_md5_digest)
627 svn_checksum_t *expected_md5_checksum;
628 const svn_checksum_t *actual_md5_checksum = eb->new_md5_checksum;
630 SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5,
631 expected_md5_digest, pool));
633 if (actual_md5_checksum == NULL)
635 actual_md5_checksum = eb->original_checksum;
637 if (actual_md5_checksum != NULL
638 && actual_md5_checksum->kind != svn_checksum_md5)
640 SVN_ERR(svn_wc__db_pristine_get_md5(&actual_md5_checksum,
641 eb->db, eb->wri_abspath,
647 if (! svn_checksum_match(expected_md5_checksum, actual_md5_checksum))
648 return svn_checksum_mismatch_err(
649 expected_md5_checksum,
650 actual_md5_checksum, pool,
651 _("Checksum mismatch for '%s'"),
652 svn_dirent_local_style(eb->local_abspath, pool));
655 /* First move the file in the pristine store; this hands over the cleanup
656 behavior to the pristine store. */
657 if (eb->new_sha1_checksum)
659 SVN_ERR(svn_wc__db_pristine_install(eb->db, eb->new_pristine_abspath,
660 eb->new_sha1_checksum,
661 eb->new_md5_checksum, pool));
663 eb->new_pristine_abspath = NULL;
666 /* Merge the changes */
668 svn_skel_t *all_work_items = NULL;
669 svn_skel_t *conflict_skel = NULL;
670 svn_skel_t *work_item;
671 apr_hash_t *base_props = NULL;
672 apr_hash_t *actual_props = NULL;
673 apr_hash_t *new_pristine_props = NULL;
674 apr_hash_t *new_actual_props = NULL;
675 apr_hash_t *new_dav_props = NULL;
676 const svn_checksum_t *new_checksum = NULL;
677 const svn_checksum_t *original_checksum = NULL;
679 svn_boolean_t added = !SVN_IS_VALID_REVNUM(eb->original_revision);
680 const char *repos_relpath = svn_uri_skip_ancestor(eb->repos_root_url,
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(
857 eb->original_revision,
860 svn_wc_conflict_version_create2(
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(
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;
951 apr_hash_t *wcroot_iprops = NULL;
955 wcroot_iprops = apr_hash_make(pool);
956 svn_hash_sets(wcroot_iprops, eb->local_abspath, eb->iprops);
959 /* The node wasn't updated, so we just have to bump its revision */
960 SVN_ERR(svn_wc__db_op_bump_revisions_post_update(eb->db,
964 *eb->target_revision,
976 svn_wc__get_file_external_editor(const svn_delta_editor_t **editor,
978 svn_revnum_t *target_revision,
979 svn_wc_context_t *wc_ctx,
980 const char *local_abspath,
981 const char *wri_abspath,
983 const char *repos_root_url,
984 const char *repos_uuid,
985 apr_array_header_t *iprops,
986 svn_boolean_t use_commit_times,
987 const char *diff3_cmd,
988 const apr_array_header_t *preserved_exts,
989 const char *record_ancestor_abspath,
990 const char *recorded_url,
991 const svn_opt_revision_t *recorded_peg_rev,
992 const svn_opt_revision_t *recorded_rev,
993 svn_wc_conflict_resolver_func2_t conflict_func,
994 void *conflict_baton,
995 svn_cancel_func_t cancel_func,
997 svn_wc_notify_func2_t notify_func,
999 apr_pool_t *result_pool,
1000 apr_pool_t *scratch_pool)
1002 svn_wc__db_t *db = wc_ctx->db;
1003 apr_pool_t *edit_pool = result_pool;
1004 struct edit_baton *eb = apr_pcalloc(edit_pool, sizeof(*eb));
1005 svn_delta_editor_t *tree_editor = svn_delta_default_editor(edit_pool);
1007 eb->pool = edit_pool;
1009 eb->local_abspath = apr_pstrdup(edit_pool, local_abspath);
1011 eb->wri_abspath = apr_pstrdup(edit_pool, wri_abspath);
1013 eb->wri_abspath = svn_dirent_dirname(local_abspath, edit_pool);
1014 eb->name = svn_dirent_basename(eb->local_abspath, NULL);
1015 eb->target_revision = target_revision;
1017 eb->url = apr_pstrdup(edit_pool, url);
1018 eb->repos_root_url = apr_pstrdup(edit_pool, repos_root_url);
1019 eb->repos_uuid = apr_pstrdup(edit_pool, repos_uuid);
1021 eb->iprops = iprops;
1023 eb->use_commit_times = use_commit_times;
1024 eb->ext_patterns = preserved_exts;
1025 eb->diff3cmd = diff3_cmd;
1027 eb->record_ancestor_abspath = apr_pstrdup(edit_pool,record_ancestor_abspath);
1028 eb->recorded_repos_relpath = svn_uri_skip_ancestor(repos_root_url, recorded_url,
1031 eb->changed_rev = SVN_INVALID_REVNUM;
1033 if (recorded_peg_rev->kind == svn_opt_revision_number)
1034 eb->recorded_peg_revision = recorded_peg_rev->value.number;
1036 eb->recorded_peg_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1038 if (recorded_rev->kind == svn_opt_revision_number)
1039 eb->recorded_revision = recorded_rev->value.number;
1041 eb->recorded_revision = SVN_INVALID_REVNUM; /* Not fixed/HEAD */
1043 eb->conflict_func = conflict_func;
1044 eb->conflict_baton = conflict_baton;
1045 eb->cancel_func = cancel_func;
1046 eb->cancel_baton = cancel_baton;
1047 eb->notify_func = notify_func;
1048 eb->notify_baton = notify_baton;
1050 eb->propchanges = apr_array_make(edit_pool, 1, sizeof(svn_prop_t));
1052 tree_editor->open_root = open_root;
1053 tree_editor->set_target_revision = set_target_revision;
1054 tree_editor->add_file = add_file;
1055 tree_editor->open_file = open_file;
1056 tree_editor->apply_textdelta = apply_textdelta;
1057 tree_editor->change_file_prop = change_file_prop;
1058 tree_editor->close_file = close_file;
1059 tree_editor->close_edit = close_edit;
1061 return svn_delta_get_cancellation_editor(cancel_func, cancel_baton,
1068 svn_wc__crawl_file_external(svn_wc_context_t *wc_ctx,
1069 const char *local_abspath,
1070 const svn_ra_reporter3_t *reporter,
1072 svn_boolean_t restore_files,
1073 svn_boolean_t use_commit_times,
1074 svn_cancel_func_t cancel_func,
1076 svn_wc_notify_func2_t notify_func,
1078 apr_pool_t *scratch_pool)
1080 svn_wc__db_t *db = wc_ctx->db;
1082 svn_node_kind_t kind;
1083 svn_wc__db_lock_t *lock;
1084 svn_revnum_t revision;
1085 const char *repos_root_url;
1086 const char *repos_relpath;
1087 svn_boolean_t update_root;
1089 err = svn_wc__db_base_get_info(NULL, &kind, &revision,
1090 &repos_relpath, &repos_root_url, NULL, NULL,
1091 NULL, NULL, NULL, NULL, NULL, &lock,
1092 NULL, NULL, &update_root,
1094 scratch_pool, scratch_pool);
1097 || kind == svn_node_dir
1100 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1101 return svn_error_trace(err);
1103 svn_error_clear(err);
1105 /* We don't know about this node, so all we have to do is tell
1106 the reporter that we don't know this node.
1108 But first we have to start the report by sending some basic
1109 information for the root. */
1111 SVN_ERR(reporter->set_path(report_baton, "", 0, svn_depth_infinity,
1112 FALSE, NULL, scratch_pool));
1113 SVN_ERR(reporter->delete_path(report_baton, "", scratch_pool));
1115 /* Finish the report, which causes the update editor to be
1117 SVN_ERR(reporter->finish_report(report_baton, scratch_pool));
1119 return SVN_NO_ERROR;
1125 svn_node_kind_t disk_kind;
1126 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
1128 if (disk_kind == svn_node_none)
1130 err = svn_wc_restore(wc_ctx, local_abspath, use_commit_times,
1135 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1136 return svn_error_trace(err);
1138 svn_error_clear(err);
1143 /* Report that we know the path */
1144 SVN_ERR(reporter->set_path(report_baton, "", revision,
1145 svn_depth_infinity, FALSE, NULL,
1148 /* For compatibility with the normal update editor report we report
1149 the target as switched.
1151 ### We can probably report a parent url and unswitched later */
1152 SVN_ERR(reporter->link_path(report_baton, "",
1153 svn_path_url_add_component2(repos_root_url,
1158 FALSE /* start_empty*/,
1159 lock ? lock->token : NULL,
1163 return svn_error_trace(reporter->finish_report(report_baton, scratch_pool));
1167 svn_wc__read_external_info(svn_node_kind_t *external_kind,
1168 const char **defining_abspath,
1169 const char **defining_url,
1170 svn_revnum_t *defining_operational_revision,
1171 svn_revnum_t *defining_revision,
1172 svn_wc_context_t *wc_ctx,
1173 const char *wri_abspath,
1174 const char *local_abspath,
1175 svn_boolean_t ignore_enoent,
1176 apr_pool_t *result_pool,
1177 apr_pool_t *scratch_pool)
1179 const char *repos_root_url;
1180 svn_wc__db_status_t status;
1181 svn_node_kind_t kind;
1184 err = svn_wc__db_external_read(&status, &kind, defining_abspath,
1185 defining_url ? &repos_root_url : NULL, NULL,
1186 defining_url, defining_operational_revision,
1188 wc_ctx->db, local_abspath, wri_abspath,
1189 result_pool, scratch_pool);
1193 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND || !ignore_enoent)
1194 return svn_error_trace(err);
1196 svn_error_clear(err);
1199 *external_kind = svn_node_none;
1201 if (defining_abspath)
1202 *defining_abspath = NULL;
1205 *defining_url = NULL;
1207 if (defining_operational_revision)
1208 *defining_operational_revision = SVN_INVALID_REVNUM;
1210 if (defining_revision)
1211 *defining_revision = SVN_INVALID_REVNUM;
1213 return SVN_NO_ERROR;
1218 if (status != svn_wc__db_status_normal)
1219 *external_kind = svn_node_unknown;
1224 case svn_node_symlink:
1225 *external_kind = svn_node_file;
1228 *external_kind = svn_node_dir;
1231 *external_kind = svn_node_none;
1235 if (defining_url && *defining_url)
1236 *defining_url = svn_path_url_add_component2(repos_root_url, *defining_url,
1239 return SVN_NO_ERROR;
1242 /* Return TRUE in *IS_ROLLED_OUT iff a node exists at XINFO->LOCAL_ABSPATH and
1243 * if that node's origin corresponds with XINFO->REPOS_ROOT_URL and
1244 * XINFO->REPOS_RELPATH. All allocations are made in SCRATCH_POOL. */
1245 static svn_error_t *
1246 is_external_rolled_out(svn_boolean_t *is_rolled_out,
1247 svn_wc_context_t *wc_ctx,
1248 svn_wc__committable_external_info_t *xinfo,
1249 apr_pool_t *scratch_pool)
1251 const char *repos_relpath;
1252 const char *repos_root_url;
1255 *is_rolled_out = FALSE;
1257 err = svn_wc__db_base_get_info(NULL, NULL, NULL, &repos_relpath,
1258 &repos_root_url, NULL, NULL, NULL, NULL,
1259 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1260 wc_ctx->db, xinfo->local_abspath,
1261 scratch_pool, scratch_pool);
1265 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
1267 svn_error_clear(err);
1268 return SVN_NO_ERROR;
1273 *is_rolled_out = (strcmp(xinfo->repos_root_url, repos_root_url) == 0 &&
1274 strcmp(xinfo->repos_relpath, repos_relpath) == 0);
1275 return SVN_NO_ERROR;
1279 svn_wc__committable_externals_below(apr_array_header_t **externals,
1280 svn_wc_context_t *wc_ctx,
1281 const char *local_abspath,
1283 apr_pool_t *result_pool,
1284 apr_pool_t *scratch_pool)
1286 apr_array_header_t *orig_externals;
1288 apr_pool_t *iterpool;
1290 /* For svn_depth_files, this also fetches dirs. They are filtered later. */
1291 SVN_ERR(svn_wc__db_committable_externals_below(&orig_externals,
1294 depth != svn_depth_infinity,
1295 result_pool, scratch_pool));
1297 if (orig_externals == NULL)
1298 return SVN_NO_ERROR;
1300 iterpool = svn_pool_create(scratch_pool);
1302 for (i = 0; i < orig_externals->nelts; i++)
1304 svn_boolean_t is_rolled_out;
1306 svn_wc__committable_external_info_t *xinfo =
1307 APR_ARRAY_IDX(orig_externals, i,
1308 svn_wc__committable_external_info_t *);
1310 /* Discard dirs for svn_depth_files (s.a.). */
1311 if (depth == svn_depth_files
1312 && xinfo->kind == svn_node_dir)
1315 svn_pool_clear(iterpool);
1317 /* Discard those externals that are not currently checked out. */
1318 SVN_ERR(is_external_rolled_out(&is_rolled_out, wc_ctx, xinfo,
1320 if (! is_rolled_out)
1323 if (*externals == NULL)
1324 *externals = apr_array_make(
1326 sizeof(svn_wc__committable_external_info_t *));
1328 APR_ARRAY_PUSH(*externals,
1329 svn_wc__committable_external_info_t *) = xinfo;
1331 if (depth != svn_depth_infinity)
1334 /* Are there any nested externals? */
1335 SVN_ERR(svn_wc__committable_externals_below(externals, wc_ctx,
1336 xinfo->local_abspath,
1338 result_pool, iterpool));
1341 return SVN_NO_ERROR;
1345 svn_wc__externals_defined_below(apr_hash_t **externals,
1346 svn_wc_context_t *wc_ctx,
1347 const char *local_abspath,
1348 apr_pool_t *result_pool,
1349 apr_pool_t *scratch_pool)
1351 return svn_error_trace(
1352 svn_wc__db_externals_defined_below(externals,
1353 wc_ctx->db, local_abspath,
1354 result_pool, scratch_pool));
1358 svn_wc__external_register(svn_wc_context_t *wc_ctx,
1359 const char *defining_abspath,
1360 const char *local_abspath,
1361 svn_node_kind_t kind,
1362 const char *repos_root_url,
1363 const char *repos_uuid,
1364 const char *repos_relpath,
1365 svn_revnum_t operational_revision,
1366 svn_revnum_t revision,
1367 apr_pool_t *scratch_pool)
1369 SVN_ERR_ASSERT(kind == svn_node_dir);
1370 return svn_error_trace(
1371 svn_wc__db_external_add_dir(wc_ctx->db, local_abspath,
1377 operational_revision,
1384 svn_wc__external_remove(svn_wc_context_t *wc_ctx,
1385 const char *wri_abspath,
1386 const char *local_abspath,
1387 svn_boolean_t declaration_only,
1388 svn_cancel_func_t cancel_func,
1390 apr_pool_t *scratch_pool)
1392 svn_wc__db_status_t status;
1393 svn_node_kind_t kind;
1395 SVN_ERR(svn_wc__db_external_read(&status, &kind, NULL, NULL, NULL, NULL,
1397 wc_ctx->db, local_abspath, wri_abspath,
1398 scratch_pool, scratch_pool));
1400 SVN_ERR(svn_wc__db_external_remove(wc_ctx->db, local_abspath, wri_abspath,
1401 NULL, scratch_pool));
1403 if (declaration_only)
1404 return SVN_NO_ERROR;
1406 if (kind == svn_node_dir)
1407 SVN_ERR(svn_wc_remove_from_revision_control2(wc_ctx, local_abspath,
1409 cancel_func, cancel_baton,
1413 SVN_ERR(svn_wc__db_base_remove(wc_ctx->db, local_abspath,
1414 FALSE /* keep_as_working */,
1415 TRUE /* queue_deletes */,
1416 FALSE /* remove_locks */,
1418 NULL, NULL, scratch_pool));
1419 SVN_ERR(svn_wc__wq_run(wc_ctx->db, local_abspath,
1420 cancel_func, cancel_baton,
1424 return SVN_NO_ERROR;
1428 svn_wc__externals_gather_definitions(apr_hash_t **externals,
1429 apr_hash_t **depths,
1430 svn_wc_context_t *wc_ctx,
1431 const char *local_abspath,
1433 apr_pool_t *result_pool,
1434 apr_pool_t *scratch_pool)
1436 if (depth == svn_depth_infinity
1437 || depth == svn_depth_unknown)
1439 return svn_error_trace(
1440 svn_wc__db_externals_gather_definitions(externals, depths,
1441 wc_ctx->db, local_abspath,
1442 result_pool, scratch_pool));
1446 const svn_string_t *value;
1448 *externals = apr_hash_make(result_pool);
1450 local_abspath = apr_pstrdup(result_pool, local_abspath);
1452 err = svn_wc_prop_get2(&value, wc_ctx, local_abspath,
1453 SVN_PROP_EXTERNALS, result_pool, scratch_pool);
1457 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
1458 return svn_error_trace(err);
1460 svn_error_clear(err);
1465 svn_hash_sets(*externals, local_abspath, value->data);
1467 if (value && depths)
1469 svn_depth_t node_depth;
1470 *depths = apr_hash_make(result_pool);
1472 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL,
1473 NULL, NULL, NULL, &node_depth, NULL,
1474 NULL, NULL, NULL, NULL, NULL, NULL,
1475 NULL, NULL, NULL, NULL, NULL, NULL,
1476 NULL, NULL, NULL, NULL,
1477 wc_ctx->db, local_abspath,
1478 scratch_pool, scratch_pool));
1480 svn_hash_sets(*depths, local_abspath, svn_depth_to_word(node_depth));
1483 return SVN_NO_ERROR;
1488 svn_wc__close_db(const char *external_abspath,
1489 svn_wc_context_t *wc_ctx,
1490 apr_pool_t *scratch_pool)
1492 SVN_ERR(svn_wc__db_drop_root(wc_ctx->db, external_abspath,
1494 return SVN_NO_ERROR;
1497 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
1498 If @a uri does not appear to be a valid URI, then @a scheme will
1500 static svn_error_t *
1501 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
1505 for (i = 0; uri[i] && uri[i] != ':'; ++i)
1509 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
1511 *scheme = apr_pstrmemdup(pool, uri, i);
1512 return SVN_NO_ERROR;
1516 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1517 _("URL '%s' does not begin with a scheme"),
1522 svn_wc__resolve_relative_external_url(const char **resolved_url,
1523 const svn_wc_external_item2_t *item,
1524 const char *repos_root_url,
1525 const char *parent_dir_url,
1526 apr_pool_t *result_pool,
1527 apr_pool_t *scratch_pool)
1529 const char *url = item->url;
1530 apr_uri_t parent_dir_uri;
1531 apr_status_t status;
1533 *resolved_url = item->url;
1535 /* If the URL is already absolute, there is nothing to do. */
1536 if (svn_path_is_url(url))
1538 /* "http://server/path" */
1539 *resolved_url = svn_uri_canonicalize(url, result_pool);
1540 return SVN_NO_ERROR;
1545 /* "/path", "//path", and "///path" */
1546 int num_leading_slashes = 1;
1549 num_leading_slashes++;
1551 num_leading_slashes++;
1554 /* "//schema-relative" and in some cases "///schema-relative".
1555 This last format is supported on file:// schema relative. */
1556 url = apr_pstrcat(scratch_pool,
1557 apr_pstrndup(scratch_pool, url, num_leading_slashes),
1558 svn_relpath_canonicalize(url + num_leading_slashes,
1564 /* "^/path" and "../path" */
1565 url = svn_relpath_canonicalize(url, scratch_pool);
1568 /* Parse the parent directory URL into its parts. */
1569 status = apr_uri_parse(scratch_pool, parent_dir_url, &parent_dir_uri);
1571 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1572 _("Illegal parent directory URL '%s'"),
1575 /* If the parent directory URL is at the server root, then the URL
1576 may have no / after the hostname so apr_uri_parse() will leave
1577 the URL's path as NULL. */
1578 if (! parent_dir_uri.path)
1579 parent_dir_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1580 parent_dir_uri.query = NULL;
1581 parent_dir_uri.fragment = NULL;
1583 /* Handle URLs relative to the current directory or to the
1584 repository root. The backpaths may only remove path elements,
1585 not the hostname. This allows an external to refer to another
1586 repository in the same server relative to the location of this
1587 repository, say using SVNParentPath. */
1588 if ((0 == strncmp("../", url, 3)) ||
1589 (0 == strncmp("^/", url, 2)))
1591 apr_array_header_t *base_components;
1592 apr_array_header_t *relative_components;
1595 /* Decompose either the parent directory's URL path or the
1596 repository root's URL path into components. */
1597 if (0 == strncmp("../", url, 3))
1599 base_components = svn_path_decompose(parent_dir_uri.path,
1601 relative_components = svn_path_decompose(url, scratch_pool);
1605 apr_uri_t repos_root_uri;
1607 status = apr_uri_parse(scratch_pool, repos_root_url,
1610 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1611 _("Illegal repository root URL '%s'"),
1614 /* If the repository root URL is at the server root, then
1615 the URL may have no / after the hostname so
1616 apr_uri_parse() will leave the URL's path as NULL. */
1617 if (! repos_root_uri.path)
1618 repos_root_uri.path = apr_pstrmemdup(scratch_pool, "/", 1);
1620 base_components = svn_path_decompose(repos_root_uri.path,
1622 relative_components = svn_path_decompose(url + 2, scratch_pool);
1625 for (i = 0; i < relative_components->nelts; ++i)
1627 const char *component = APR_ARRAY_IDX(relative_components,
1630 if (0 == strcmp("..", component))
1632 /* Constructing the final absolute URL together with
1633 apr_uri_unparse() requires that the path be absolute,
1634 so only pop a component if the component being popped
1635 is not the component for the root directory. */
1636 if (base_components->nelts > 1)
1637 apr_array_pop(base_components);
1640 APR_ARRAY_PUSH(base_components, const char *) = component;
1643 parent_dir_uri.path = (char *)svn_path_compose(base_components,
1645 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1646 &parent_dir_uri, 0),
1648 return SVN_NO_ERROR;
1651 /* The remaining URLs are relative to either the scheme or server root
1652 and can only refer to locations inside that scope, so backpaths are
1654 if (svn_path_is_backpath_present(url))
1655 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1656 _("The external relative URL '%s' cannot have "
1657 "backpaths, i.e. '..'"),
1660 /* Relative to the scheme: Build a new URL from the parts we know. */
1661 if (0 == strncmp("//", url, 2))
1665 SVN_ERR(uri_scheme(&scheme, repos_root_url, scratch_pool));
1666 *resolved_url = svn_uri_canonicalize(apr_pstrcat(scratch_pool, scheme,
1667 ":", url, (char *)NULL),
1669 return SVN_NO_ERROR;
1672 /* Relative to the server root: Just replace the path portion of the
1676 parent_dir_uri.path = (char *)url;
1677 *resolved_url = svn_uri_canonicalize(apr_uri_unparse(scratch_pool,
1678 &parent_dir_uri, 0),
1680 return SVN_NO_ERROR;
1683 return svn_error_createf(SVN_ERR_BAD_URL, 0,
1684 _("Unrecognized format for the relative external "