2 * props.c : routines dealing with properties 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_file_io.h>
33 #include <apr_strings.h>
34 #include <apr_general.h>
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_pools.h"
39 #include "svn_dirent_uri.h"
41 #include "svn_error.h"
42 #include "svn_props.h"
45 #include "svn_mergeinfo.h"
49 #include "svn_sorts.h"
51 #include "private/svn_wc_private.h"
52 #include "private/svn_mergeinfo_private.h"
53 #include "private/svn_skel.h"
54 #include "private/svn_string_private.h"
55 #include "private/svn_subr_private.h"
59 #include "translate.h"
60 #include "workqueue.h"
61 #include "conflicts.h"
63 #include "svn_private_config.h"
65 /*---------------------------------------------------------------------*/
67 /*** Merging propchanges into the working copy ***/
70 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
71 calculate the deltas between them. */
73 diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
74 const svn_string_t *from_prop_val,
75 const svn_string_t *to_prop_val, apr_pool_t *pool)
77 if (svn_string_compare(from_prop_val, to_prop_val))
79 /* Don't bothering parsing identical mergeinfo. */
80 *deleted = apr_hash_make(pool);
81 *added = apr_hash_make(pool);
85 svn_mergeinfo_t from, to;
86 SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
87 SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
88 SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
94 /* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
95 reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
96 been modified to combine it with incoming mergeinfo from the
99 combine_mergeinfo_props(const svn_string_t **output,
100 const svn_string_t *prop_val1,
101 const svn_string_t *prop_val2,
102 apr_pool_t *result_pool,
103 apr_pool_t *scratch_pool)
105 svn_mergeinfo_t mergeinfo1, mergeinfo2;
106 svn_string_t *mergeinfo_string;
108 SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
109 SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
110 SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
112 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
113 *output = mergeinfo_string;
117 /* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
118 the "base" property value, WORKING_PROP_VAL is the current value,
119 and TO_PROP_VAL is the new value. */
121 combine_forked_mergeinfo_props(const svn_string_t **output,
122 const svn_string_t *from_prop_val,
123 const svn_string_t *working_prop_val,
124 const svn_string_t *to_prop_val,
125 apr_pool_t *result_pool,
126 apr_pool_t *scratch_pool)
128 svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
129 svn_string_t *mergeinfo_string;
131 /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
132 SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
133 working_prop_val, scratch_pool));
134 SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
135 to_prop_val, scratch_pool));
136 SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
137 scratch_pool, scratch_pool));
138 SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
139 scratch_pool, scratch_pool));
141 /* Apply the combined deltas to the base. */
142 SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
144 SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
145 scratch_pool, scratch_pool));
147 SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
148 TRUE, scratch_pool, scratch_pool));
150 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
152 *output = mergeinfo_string;
158 svn_wc_merge_props3(svn_wc_notify_state_t *state,
159 svn_wc_context_t *wc_ctx,
160 const char *local_abspath,
161 const svn_wc_conflict_version_t *left_version,
162 const svn_wc_conflict_version_t *right_version,
163 apr_hash_t *baseprops,
164 const apr_array_header_t *propchanges,
165 svn_boolean_t dry_run,
166 svn_wc_conflict_resolver_func2_t conflict_func,
167 void *conflict_baton,
168 svn_cancel_func_t cancel_func,
170 apr_pool_t *scratch_pool)
173 svn_wc__db_status_t status;
174 svn_node_kind_t kind;
175 apr_hash_t *pristine_props = NULL;
176 apr_hash_t *actual_props;
177 apr_hash_t *new_actual_props;
178 svn_boolean_t had_props, props_mod;
179 svn_boolean_t have_base;
180 svn_boolean_t conflicted;
181 svn_skel_t *work_items = NULL;
182 svn_skel_t *conflict_skel = NULL;
183 svn_wc__db_t *db = wc_ctx->db;
185 /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
188 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
189 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
190 NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
191 &had_props, &props_mod, &have_base, NULL, NULL,
193 scratch_pool, scratch_pool));
195 /* Checks whether the node exists and returns the hidden flag */
196 if (status == svn_wc__db_status_not_present
197 || status == svn_wc__db_status_server_excluded
198 || status == svn_wc__db_status_excluded)
200 return svn_error_createf(
201 SVN_ERR_WC_PATH_NOT_FOUND, NULL,
202 _("The node '%s' was not found."),
203 svn_dirent_local_style(local_abspath, scratch_pool));
205 else if (status != svn_wc__db_status_normal
206 && status != svn_wc__db_status_added
207 && status != svn_wc__db_status_incomplete)
209 return svn_error_createf(
210 SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
211 _("The node '%s' does not have properties in this state."),
212 svn_dirent_local_style(local_abspath, scratch_pool));
216 svn_boolean_t text_conflicted;
217 svn_boolean_t prop_conflicted;
218 svn_boolean_t tree_conflicted;
220 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
226 /* We can't install two text/prop conflicts on a single node, so
227 avoid even checking that we have to merge it */
228 if (text_conflicted || prop_conflicted || tree_conflicted)
230 return svn_error_createf(
231 SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
232 _("Can't merge into conflicted node '%s'"),
233 svn_dirent_local_style(local_abspath,
236 /* else: Conflict was resolved by removing markers */
239 /* The PROPCHANGES may not have non-"normal" properties in it. If entry
240 or wc props were allowed, then the following code would install them
241 into the BASE and/or WORKING properties(!). */
242 for (i = propchanges->nelts; i--; )
244 const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
246 if (!svn_wc_is_normal_prop(change->name))
247 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
248 _("The property '%s' may not be merged "
251 svn_dirent_local_style(local_abspath,
256 SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
257 scratch_pool, scratch_pool));
258 if (pristine_props == NULL)
259 pristine_props = apr_hash_make(scratch_pool);
262 SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
263 scratch_pool, scratch_pool));
265 actual_props = pristine_props;
267 /* Note that while this routine does the "real" work, it's only
268 prepping tempfiles and writing log commands. */
269 SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
272 baseprops /* server_baseprops */,
276 scratch_pool, scratch_pool));
284 const char *dir_abspath;
286 if (kind == svn_node_dir)
287 dir_abspath = local_abspath;
289 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
291 /* Verify that we're holding this directory's write lock. */
292 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
297 svn_skel_t *work_item;
298 SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
304 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
307 scratch_pool, scratch_pool));
309 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
312 /* After a (not-dry-run) merge, we ALWAYS have props to save. */
313 SVN_ERR_ASSERT(new_actual_props != NULL);
315 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
316 svn_wc__has_magic_property(propchanges),
321 if (work_items != NULL)
322 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
325 /* If there is a conflict, try to resolve it. */
326 if (conflict_skel && conflict_func)
328 svn_boolean_t prop_conflicted;
330 SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, kind,
332 NULL /* merge_options */,
333 conflict_func, conflict_baton,
334 cancel_func, cancel_baton,
337 /* Reset *STATE if all prop conflicts were resolved. */
338 SVN_ERR(svn_wc__internal_conflicted_p(
339 NULL, &prop_conflicted, NULL,
340 wc_ctx->db, local_abspath, scratch_pool));
341 if (! prop_conflicted)
342 *state = svn_wc_notify_state_merged;
349 /* Generate a message to describe the property conflict among these four
352 Note that this function (currently) interprets the property values as
353 strings, but they could actually be binary values. We'll keep the
354 types as svn_string_t in case we fix this in the future. */
355 static svn_stringbuf_t *
356 generate_conflict_message(const char *propname,
357 const svn_string_t *original,
358 const svn_string_t *mine,
359 const svn_string_t *incoming,
360 const svn_string_t *incoming_base,
361 apr_pool_t *result_pool)
363 if (incoming_base == NULL)
365 /* Attempting to add the value INCOMING. */
366 SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
370 /* To have a conflict, these must be different. */
371 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
373 /* Note that we don't care whether MINE is locally-added or
374 edited, or just something different that is a copy of the
375 pristine ORIGINAL. */
376 return svn_stringbuf_createf(result_pool,
377 _("Trying to add new property '%s'\n"
378 "but the property already exists.\n"),
382 /* To have a conflict, we must have an ORIGINAL which has been
384 SVN_ERR_ASSERT_NO_RETURN(original != NULL);
385 return svn_stringbuf_createf(result_pool,
386 _("Trying to add new property '%s'\n"
387 "but the property has been locally "
392 if (incoming == NULL)
394 /* Attempting to delete the value INCOMING_BASE. */
395 SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
397 /* Are we trying to delete a local addition? */
398 if (original == NULL && mine != NULL)
399 return svn_stringbuf_createf(result_pool,
400 _("Trying to delete property '%s'\n"
401 "but the property has been locally "
405 /* A conflict can only occur if we originally had the property;
406 otherwise, we would have merged the property-delete into the
407 non-existent property. */
408 SVN_ERR_ASSERT_NO_RETURN(original != NULL);
410 if (svn_string_compare(original, incoming_base))
413 /* We were trying to delete the correct property, but an edit
414 caused the conflict. */
415 return svn_stringbuf_createf(result_pool,
416 _("Trying to delete property '%s'\n"
417 "but the property has been locally "
421 else if (mine == NULL)
423 /* We were trying to delete the property, but we have locally
424 deleted the same property, but with a different value. */
425 return svn_stringbuf_createf(result_pool,
426 _("Trying to delete property '%s'\n"
427 "but the property has been locally "
428 "deleted and had a different "
433 /* We were trying to delete INCOMING_BASE but our ORIGINAL is
434 something else entirely. */
435 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
437 return svn_stringbuf_createf(result_pool,
438 _("Trying to delete property '%s'\n"
439 "but the local property value is "
444 /* Attempting to change the property from INCOMING_BASE to INCOMING. */
446 /* If we have a (current) property value, then it should be different
447 from the INCOMING_BASE; otherwise, the incoming change would have
448 been applied to it. */
449 SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
451 if (original && mine && svn_string_compare(original, mine))
453 /* We have an unchanged property, so the original values must
454 have been different. */
455 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
456 return svn_stringbuf_createf(result_pool,
457 _("Trying to change property '%s'\n"
458 "but the local property value conflicts "
459 "with the incoming change.\n"),
463 if (original && mine)
464 return svn_stringbuf_createf(result_pool,
465 _("Trying to change property '%s'\n"
466 "but the property has already been locally "
467 "changed to a different value.\n"),
471 return svn_stringbuf_createf(result_pool,
472 _("Trying to change property '%s'\nbut "
473 "the property has been locally deleted.\n"),
477 return svn_stringbuf_createf(result_pool,
478 _("Trying to change property '%s'\nbut the "
479 "property has been locally added with a "
480 "different value.\n"),
483 return svn_stringbuf_createf(result_pool,
484 _("Trying to change property '%s'\nbut "
485 "the property does not exist locally.\n"),
490 /* SKEL will be one of:
495 Return NULL for the former (the particular property value was not
496 present), and VALUE for the second. */
497 static const svn_string_t *
498 maybe_prop_value(const svn_skel_t *skel,
499 apr_pool_t *result_pool)
501 if (skel->children == NULL)
504 return svn_string_ncreate(skel->children->data,
510 /* Create a property rejection description for the specified property.
511 The result will be allocated in RESULT_POOL. */
513 prop_conflict_new(const svn_string_t **conflict_desc,
514 const char *propname,
515 const svn_string_t *original,
516 const svn_string_t *mine,
517 const svn_string_t *incoming,
518 const svn_string_t *incoming_base,
519 svn_cancel_func_t cancel_func,
521 apr_pool_t *result_pool,
522 apr_pool_t *scratch_pool)
525 svn_diff_file_options_t *diff_opts;
526 svn_stringbuf_t *buf;
527 svn_boolean_t incoming_base_is_binary;
528 svn_boolean_t mine_is_binary;
529 svn_boolean_t incoming_is_binary;
531 buf = generate_conflict_message(propname, original, mine, incoming,
532 incoming_base, scratch_pool);
534 /* Convert deleted or not-yet-added values to empty-string values, for the
535 purposes of diff generation and binary detection. */
537 mine = svn_string_create_empty(scratch_pool);
538 if (incoming == NULL)
539 incoming = svn_string_create_empty(scratch_pool);
540 if (incoming_base == NULL)
541 incoming_base = svn_string_create_empty(scratch_pool);
543 /* How we render the conflict:
545 We have four sides: original, mine, incoming_base, incoming.
546 We render the conflict as a 3-way diff. A diff3 API has three parts,
550 ||| - modified (or "older")
551 === - latest (or "theirs")
554 We fill those parts as follows:
556 PART FILLED BY SKEL MEMBER USER-FACING ROLE
557 ==== ===================== ================
558 original mine was WORKING tree at conflict creation
559 modified incoming_base left-hand side of merge
560 latest incoming right-hand side of merge
561 (none) original was BASE tree at conflict creation
563 An 'update -r rN' is treated like a 'merge -r BASE:rN', i.e., in an
564 'update' operation skel->original and skel->incoming_base coincide.
566 Note that the term "original" is used both in the skel and in diff3
567 with different meanings. Note also that the skel's ORIGINAL value was
568 at some point in the BASE tree, but the BASE tree need not have contained
569 the INCOMING_BASE value.
571 Yes, it's confusing. */
573 /* If any of the property values involved in the diff is binary data,
574 * do not generate a diff. */
575 incoming_base_is_binary = svn_io_is_binary_data(incoming_base->data,
577 mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
578 incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
580 if (!(incoming_base_is_binary || mine_is_binary || incoming_is_binary))
582 diff_opts = svn_diff_file_options_create(scratch_pool);
583 diff_opts->ignore_space = svn_diff_file_ignore_space_none;
584 diff_opts->ignore_eol_style = FALSE;
585 diff_opts->show_c_function = FALSE;
586 /* Pass skel member INCOMING_BASE into the formal parameter ORIGINAL.
587 Ignore the skel member ORIGINAL. */
588 SVN_ERR(svn_diff_mem_string_diff3(&diff, incoming_base, mine, incoming,
589 diff_opts, scratch_pool));
590 if (svn_diff_contains_conflicts(diff))
592 svn_stream_t *stream;
593 svn_diff_conflict_display_style_t style;
594 const char *mine_marker = _("<<<<<<< (local property value)");
595 const char *incoming_marker = _(">>>>>>> (incoming 'changed to' value)");
596 const char *incoming_base_marker = _("||||||| (incoming 'changed from' value)");
597 const char *separator = "=======";
598 svn_string_t *incoming_base_ascii =
599 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming_base->data,
602 svn_string_t *mine_ascii =
603 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
606 svn_string_t *incoming_ascii =
607 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
611 style = svn_diff_conflict_display_modified_original_latest;
612 stream = svn_stream_from_stringbuf(buf, scratch_pool);
613 SVN_ERR(svn_stream_skip(stream, buf->len));
614 SVN_ERR(svn_diff_mem_string_output_merge3(stream, diff,
618 incoming_base_marker, mine_marker,
619 incoming_marker, separator,
621 cancel_func, cancel_baton,
623 SVN_ERR(svn_stream_close(stream));
625 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
630 /* If we could not print a conflict diff just print full values . */
633 svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
635 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
638 svn_stringbuf_appendbytes(buf, mine->data, mine->len);
639 svn_stringbuf_appendcstr(buf, "\n");
642 if (incoming->len > 0)
644 svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
645 if (incoming_is_binary)
646 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
649 svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
650 svn_stringbuf_appendcstr(buf, "\n");
653 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
657 /* Parse a property conflict description from the provided SKEL.
658 The result includes a descriptive message (see generate_conflict_message)
659 and maybe a diff of property values containing conflict markers.
660 The result will be allocated in RESULT_POOL.
662 Note: SKEL is a single property conflict of the form:
664 ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
666 Note: This is not the same format as the property conflicts we store in
667 wc.db since 1.8. This is the legacy format used in the Workqueue in 1.7-1.8 */
669 prop_conflict_from_skel(const svn_string_t **conflict_desc,
670 const svn_skel_t *skel,
671 svn_cancel_func_t cancel_func,
673 apr_pool_t *result_pool,
674 apr_pool_t *scratch_pool)
676 const svn_string_t *original;
677 const svn_string_t *mine;
678 const svn_string_t *incoming;
679 const svn_string_t *incoming_base;
680 const char *propname;
682 /* Navigate to the property name. */
683 skel = skel->children->next;
685 /* We need to copy these into SCRATCH_POOL in order to nul-terminate
687 propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
688 original = maybe_prop_value(skel->next, scratch_pool);
689 mine = maybe_prop_value(skel->next->next, scratch_pool);
690 incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
691 incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
693 return svn_error_trace(prop_conflict_new(conflict_desc,
696 incoming, incoming_base,
697 cancel_func, cancel_baton,
698 result_pool, scratch_pool));
701 /* Create a property conflict file at PREJFILE based on the property
702 conflicts in CONFLICT_SKEL. */
704 svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
706 const char *local_abspath,
707 const svn_skel_t *prop_conflict_data,
708 svn_cancel_func_t cancel_func,
710 apr_pool_t *result_pool,
711 apr_pool_t *scratch_pool)
713 const char *tempdir_abspath;
714 svn_stream_t *stream;
715 const char *temp_abspath;
716 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
717 const svn_skel_t *scan;
719 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
721 iterpool, iterpool));
723 SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
724 tempdir_abspath, svn_io_file_del_none,
725 scratch_pool, iterpool));
727 if (prop_conflict_data)
729 for (scan = prop_conflict_data->children->next;
730 scan != NULL; scan = scan->next)
732 const svn_string_t *conflict_desc;
734 svn_pool_clear(iterpool);
736 SVN_ERR(prop_conflict_from_skel(&conflict_desc, scan,
737 cancel_func, cancel_baton,
738 iterpool, iterpool));
740 SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
745 svn_wc_operation_t operation;
746 apr_hash_index_t *hi;
747 apr_hash_t *old_props;
748 apr_hash_t *mine_props;
749 apr_hash_t *their_original_props;
750 apr_hash_t *their_props;
751 apr_hash_t *conflicted_props;
752 svn_skel_t *conflicts;
754 SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL,
756 scratch_pool, scratch_pool));
758 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, NULL,
761 scratch_pool, scratch_pool));
763 SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL,
765 &their_original_props,
773 if (operation == svn_wc_operation_merge)
774 SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
775 scratch_pool, scratch_pool));
777 old_props = their_original_props;
779 /* ### TODO: Sort conflicts? */
780 for (hi = apr_hash_first(scratch_pool, conflicted_props);
782 hi = apr_hash_next(hi))
784 const svn_string_t *conflict_desc;
785 const char *propname = apr_hash_this_key(hi);
786 const svn_string_t *old_value;
787 const svn_string_t *mine_value;
788 const svn_string_t *their_value;
789 const svn_string_t *their_original_value;
791 svn_pool_clear(iterpool);
793 old_value = old_props ? svn_hash_gets(old_props, propname) : NULL;
794 mine_value = mine_props ? svn_hash_gets(mine_props, propname) : NULL;
795 their_value = their_props ? svn_hash_gets(their_props, propname)
797 their_original_value = their_original_props
798 ? svn_hash_gets(their_original_props, propname)
801 SVN_ERR(prop_conflict_new(&conflict_desc,
802 propname, old_value, mine_value,
803 their_value, their_original_value,
804 cancel_func, cancel_baton,
805 iterpool, iterpool));
807 SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
811 SVN_ERR(svn_stream_close(stream));
813 svn_pool_destroy(iterpool);
815 *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
820 /* Set the value of *STATE to NEW_VALUE if STATE is not NULL
821 * and NEW_VALUE is a higer order value than *STATE's current value
822 * using this ordering (lower order first):
824 * - unknown, unchanged, inapplicable
833 set_prop_merge_state(svn_wc_notify_state_t *state,
834 svn_wc_notify_state_t new_value)
836 static char ordering[] =
837 { svn_wc_notify_state_unknown,
838 svn_wc_notify_state_unchanged,
839 svn_wc_notify_state_inapplicable,
840 svn_wc_notify_state_changed,
841 svn_wc_notify_state_merged,
842 svn_wc_notify_state_obstructed,
843 svn_wc_notify_state_conflicted };
844 int state_pos = 0, i;
849 /* Find *STATE in our ordering */
850 for (i = 0; i < sizeof(ordering); i++)
852 if (*state == ordering[i])
859 /* Find NEW_VALUE in our ordering
860 * We don't need to look further than where we found *STATE though:
861 * If we find our value, it's order is too low.
862 * If we don't find it, we'll want to set it, no matter its order.
865 for (i = 0; i <= state_pos; i++)
867 if (new_value == ordering[i])
874 /* Apply the addition of a property with name PROPNAME and value NEW_VAL to
875 * the existing property with value WORKING_VAL, that originally had value
878 * Sets *RESULT_VAL to the resulting value.
879 * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
880 * Sets *DID_MERGE to true if the result is caused by a merge
883 apply_single_prop_add(const svn_string_t **result_val,
884 svn_boolean_t *conflict_remains,
885 svn_boolean_t *did_merge,
886 const char *propname,
887 const svn_string_t *pristine_val,
888 const svn_string_t *new_val,
889 const svn_string_t *working_val,
890 apr_pool_t *result_pool,
891 apr_pool_t *scratch_pool)
894 *conflict_remains = FALSE;
898 /* the property already exists in actual_props... */
900 if (svn_string_compare(working_val, new_val))
901 /* The value we want is already there, so it's a merge. */
906 svn_boolean_t merged_prop = FALSE;
908 /* The WC difference doesn't match the new value.
909 We only merge mergeinfo; other props conflict */
910 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
912 const svn_string_t *merged_val;
913 svn_error_t *err = combine_mergeinfo_props(&merged_val,
919 /* Issue #3896 'mergeinfo syntax errors should be treated
920 gracefully': If bogus mergeinfo is present we can't
921 merge intelligently, so raise a conflict instead. */
924 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
925 svn_error_clear(err);
927 return svn_error_trace(err);
932 *result_val = merged_val;
938 *conflict_remains = TRUE;
941 else if (pristine_val)
942 *conflict_remains = TRUE;
943 else /* property doesn't yet exist in actual_props... */
945 *result_val = new_val;
951 /* Apply the deletion of a property to the existing
952 * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
954 * Sets *RESULT_VAL to the resulting value.
955 * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
956 * Sets *DID_MERGE to true if the result is caused by a merge
959 apply_single_prop_delete(const svn_string_t **result_val,
960 svn_boolean_t *conflict_remains,
961 svn_boolean_t *did_merge,
962 const svn_string_t *base_val,
963 const svn_string_t *old_val,
964 const svn_string_t *working_val)
966 *conflict_remains = FALSE;
971 && !svn_string_compare(working_val, old_val))
973 /* We are trying to delete a locally-added prop. */
974 *conflict_remains = TRUE;
980 /* This is a merge, merging a delete into non-existent
981 property or a local addition of same prop value. */
986 else if (svn_string_compare(base_val, old_val))
990 if (svn_string_compare(working_val, old_val))
991 /* they have the same values, so it's an update */
994 *conflict_remains = TRUE;
997 /* The property is locally deleted from the same value, so it's
1003 *conflict_remains = TRUE;
1005 return SVN_NO_ERROR;
1009 /* Merge a change to the mergeinfo property. Similar to
1010 apply_single_prop_change(), except that the property name is always
1011 SVN_PROP_MERGEINFO. */
1012 /* ### This function is extracted straight from the previous all-in-one
1013 version of apply_single_prop_change() by removing the code paths that
1014 were not followed for this property, but with no attempt to rationalize
1016 static svn_error_t *
1017 apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
1018 svn_boolean_t *conflict_remains,
1019 svn_boolean_t *did_merge,
1020 const svn_string_t *base_val,
1021 const svn_string_t *old_val,
1022 const svn_string_t *new_val,
1023 const svn_string_t *working_val,
1024 apr_pool_t *result_pool,
1025 apr_pool_t *scratch_pool)
1027 if ((working_val && ! base_val)
1028 || (! working_val && base_val)
1029 || (working_val && base_val
1030 && !svn_string_compare(working_val, base_val)))
1032 /* Locally changed property */
1035 if (svn_string_compare(working_val, new_val))
1036 /* The new value equals the changed value: a no-op merge */
1040 /* We have base, WC, and new values. Discover
1041 deltas between base <-> WC, and base <->
1042 incoming. Combine those deltas, and apply
1043 them to base to get the new value. */
1044 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1049 *result_val = new_val;
1055 /* There is a base_val but no working_val */
1056 *conflict_remains = TRUE;
1060 else if (! working_val) /* means !working_val && !base_val due
1061 to conditions above: no prop at all */
1063 /* Discover any mergeinfo additions in the
1064 incoming value relative to the base, and
1065 "combine" those with the empty WC value. */
1066 svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
1067 svn_string_t *mergeinfo_string;
1069 SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
1071 old_val, new_val, scratch_pool));
1072 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
1073 added_mergeinfo, result_pool));
1074 *result_val = mergeinfo_string;
1077 else /* means working && base && svn_string_compare(working, base) */
1079 if (svn_string_compare(old_val, base_val))
1080 *result_val = new_val;
1083 /* We have base, WC, and new values. Discover
1084 deltas between base <-> WC, and base <->
1085 incoming. Combine those deltas, and apply
1086 them to base to get the new value. */
1087 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1089 new_val, result_pool,
1091 *result_val = new_val;
1096 return SVN_NO_ERROR;
1099 /* Merge a change to a property, using the rule that if the working value
1100 is equal to the new value then there is nothing we need to do. Else, if
1101 the working value is the same as the old value then apply the change as a
1102 simple update (replacement), otherwise invoke maybe_generate_propconflict().
1103 The definition of the arguments and behaviour is the same as
1104 apply_single_prop_change(). */
1105 static svn_error_t *
1106 apply_single_generic_prop_change(const svn_string_t **result_val,
1107 svn_boolean_t *conflict_remains,
1108 svn_boolean_t *did_merge,
1109 const svn_string_t *old_val,
1110 const svn_string_t *new_val,
1111 const svn_string_t *working_val)
1113 SVN_ERR_ASSERT(old_val != NULL);
1115 /* If working_val is the same as new_val already then there is
1117 if (working_val && new_val
1118 && svn_string_compare(working_val, new_val))
1120 /* All values identical is a trivial, non-notifiable merge */
1121 if (! old_val || ! svn_string_compare(old_val, new_val))
1124 /* If working_val is the same as old_val... */
1125 else if (working_val && old_val
1126 && svn_string_compare(working_val, old_val))
1128 /* A trivial update: change it to new_val. */
1129 *result_val = new_val;
1133 /* Merge the change. */
1134 *conflict_remains = TRUE;
1137 return SVN_NO_ERROR;
1140 /* Change the property with name PROPNAME, setting *RESULT_VAL,
1141 * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
1143 * BASE_VAL contains the working copy base property value. (May be null.)
1145 * OLD_VAL contains the value of the property the server
1146 * thinks it's overwriting. (Not null.)
1148 * NEW_VAL contains the value to be set. (Not null.)
1150 * WORKING_VAL contains the working copy actual value. (May be null.)
1152 static svn_error_t *
1153 apply_single_prop_change(const svn_string_t **result_val,
1154 svn_boolean_t *conflict_remains,
1155 svn_boolean_t *did_merge,
1156 const char *propname,
1157 const svn_string_t *base_val,
1158 const svn_string_t *old_val,
1159 const svn_string_t *new_val,
1160 const svn_string_t *working_val,
1161 apr_pool_t *result_pool,
1162 apr_pool_t *scratch_pool)
1164 svn_boolean_t merged_prop = FALSE;
1166 *conflict_remains = FALSE;
1168 /* Note: The purpose is to apply the change (old_val -> new_val) onto
1169 (working_val). There is no need for base_val to be involved in the
1170 process except as a bit of context to help the user understand and
1171 resolve any conflict. */
1173 /* Decide how to merge, based on whether we know anything special about
1175 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1177 /* We know how to merge any mergeinfo property change...
1179 ...But Issue #3896 'mergeinfo syntax errors should be treated
1180 gracefully' might thwart us. If bogus mergeinfo is present we
1181 can't merge intelligently, so let the standard method deal with
1183 svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
1194 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1195 svn_error_clear(err);
1197 return svn_error_trace(err);
1207 /* The standard method: perform a simple update automatically, but
1208 pass any other kind of merge to maybe_generate_propconflict(). */
1210 SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
1212 old_val, new_val, working_val));
1215 return SVN_NO_ERROR;
1220 svn_wc__merge_props(svn_skel_t **conflict_skel,
1221 svn_wc_notify_state_t *state,
1222 apr_hash_t **new_actual_props,
1224 const char *local_abspath,
1225 apr_hash_t *server_baseprops,
1226 apr_hash_t *pristine_props,
1227 apr_hash_t *actual_props,
1228 const apr_array_header_t *propchanges,
1229 apr_pool_t *result_pool,
1230 apr_pool_t *scratch_pool)
1232 apr_pool_t *iterpool;
1234 apr_hash_t *conflict_props = NULL;
1235 apr_hash_t *their_props;
1237 SVN_ERR_ASSERT(pristine_props != NULL);
1238 SVN_ERR_ASSERT(actual_props != NULL);
1240 *new_actual_props = apr_hash_copy(result_pool, actual_props);
1242 if (!server_baseprops)
1243 server_baseprops = pristine_props;
1245 their_props = apr_hash_copy(scratch_pool, server_baseprops);
1249 /* Start out assuming no changes or conflicts. Don't bother to
1250 examine propchanges->nelts yet; even if we knew there were
1251 propchanges, we wouldn't yet know if they are "normal" props,
1252 as opposed wc or entry props. */
1253 *state = svn_wc_notify_state_unchanged;
1256 /* Looping over the array of incoming propchanges we want to apply: */
1257 iterpool = svn_pool_create(scratch_pool);
1258 for (i = 0; i < propchanges->nelts; i++)
1260 const svn_prop_t *incoming_change
1261 = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1262 const char *propname = incoming_change->name;
1263 const svn_string_t *base_val /* Pristine in WC */
1264 = svn_hash_gets(pristine_props, propname);
1265 const svn_string_t *from_val /* Merge left */
1266 = svn_hash_gets(server_baseprops, propname);
1267 const svn_string_t *to_val /* Merge right */
1268 = incoming_change->value;
1269 const svn_string_t *working_val /* Mine */
1270 = svn_hash_gets(actual_props, propname);
1271 const svn_string_t *result_val;
1272 svn_boolean_t conflict_remains;
1273 svn_boolean_t did_merge = FALSE;
1275 svn_pool_clear(iterpool);
1277 to_val = svn_string_dup(to_val, result_pool);
1279 svn_hash_sets(their_props, propname, to_val);
1282 /* We already know that state is at least `changed', so mark
1283 that, but remember that we may later upgrade to `merged' or
1284 even `conflicted'. */
1285 set_prop_merge_state(state, svn_wc_notify_state_changed);
1287 result_val = working_val;
1289 if (! from_val) /* adding a new property */
1290 SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
1291 &did_merge, propname,
1292 base_val, to_val, working_val,
1293 result_pool, iterpool));
1295 else if (! to_val) /* delete an existing property */
1296 SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
1298 base_val, from_val, working_val));
1300 else /* changing an existing property */
1301 SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
1302 &did_merge, propname,
1303 base_val, from_val, to_val, working_val,
1304 result_pool, iterpool));
1306 if (result_val != working_val)
1307 svn_hash_sets(*new_actual_props, propname, result_val);
1309 set_prop_merge_state(state, svn_wc_notify_state_merged);
1311 /* merging logic complete, now we need to possibly log conflict
1312 data to tmpfiles. */
1314 if (conflict_remains)
1316 set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1318 if (!conflict_props)
1319 conflict_props = apr_hash_make(scratch_pool);
1321 svn_hash_sets(conflict_props, propname, "");
1324 } /* foreach propchange ... */
1325 svn_pool_destroy(iterpool);
1327 /* Finished applying all incoming propchanges to our hashes! */
1329 if (conflict_props != NULL)
1331 /* Ok, we got some conflict. Lets store all the property knowledge we
1332 have for resolving later */
1334 if (!*conflict_skel)
1335 *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1337 SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
1339 NULL /* reject_path */,
1348 return SVN_NO_ERROR;
1352 /* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
1353 If VALUE is null, remove property NAME. */
1354 static svn_error_t *
1355 wcprop_set(svn_wc__db_t *db,
1356 const char *local_abspath,
1358 const svn_string_t *value,
1359 apr_pool_t *scratch_pool)
1361 apr_hash_t *prophash;
1363 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1365 /* Note: this is not well-transacted. But... meh. This is merely a cache,
1366 and if two processes are trying to modify this one entry at the same
1367 time, then fine: we can let one be a winner, and one a loser. Of course,
1368 if there are *other* state changes afoot, then the lack of a txn could
1369 be a real issue, but we cannot solve that here. */
1371 SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1372 scratch_pool, scratch_pool));
1374 if (prophash == NULL)
1375 prophash = apr_hash_make(scratch_pool);
1377 svn_hash_sets(prophash, name, value);
1378 return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
1385 svn_wc__get_actual_props(apr_hash_t **props,
1387 const char *local_abspath,
1388 apr_pool_t *result_pool,
1389 apr_pool_t *scratch_pool)
1391 SVN_ERR_ASSERT(props != NULL);
1392 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1394 /* ### perform some state checking. for example, locally-deleted nodes
1395 ### should not have any ACTUAL props. */
1397 return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
1398 result_pool, scratch_pool));
1403 svn_wc_prop_list2(apr_hash_t **props,
1404 svn_wc_context_t *wc_ctx,
1405 const char *local_abspath,
1406 apr_pool_t *result_pool,
1407 apr_pool_t *scratch_pool)
1409 return svn_error_trace(svn_wc__get_actual_props(props,
1416 struct propname_filter_baton_t {
1417 svn_wc__proplist_receiver_t receiver_func;
1418 void *receiver_baton;
1419 const char *propname;
1422 static svn_error_t *
1423 propname_filter_receiver(void *baton,
1424 const char *local_abspath,
1426 apr_pool_t *scratch_pool)
1428 struct propname_filter_baton_t *pfb = baton;
1429 const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
1433 props = apr_hash_make(scratch_pool);
1434 svn_hash_sets(props, pfb->propname, propval);
1436 SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
1440 return SVN_NO_ERROR;
1444 svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
1445 const char *local_abspath,
1446 const char *propname,
1448 svn_boolean_t pristine,
1449 const apr_array_header_t *changelists,
1450 svn_wc__proplist_receiver_t receiver_func,
1451 void *receiver_baton,
1452 svn_cancel_func_t cancel_func,
1454 apr_pool_t *scratch_pool)
1456 svn_wc__proplist_receiver_t receiver = receiver_func;
1457 void *baton = receiver_baton;
1458 struct propname_filter_baton_t pfb;
1460 pfb.receiver_func = receiver_func;
1461 pfb.receiver_baton = receiver_baton;
1462 pfb.propname = propname;
1464 SVN_ERR_ASSERT(receiver_func);
1469 receiver = propname_filter_receiver;
1474 case svn_depth_empty:
1477 apr_hash_t *changelist_hash = NULL;
1479 if (changelists && changelists->nelts)
1480 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1481 changelists, scratch_pool));
1483 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1484 changelist_hash, scratch_pool))
1488 SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
1490 scratch_pool, scratch_pool));
1492 SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
1493 scratch_pool, scratch_pool));
1495 if (props && apr_hash_count(props) > 0)
1496 SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
1499 case svn_depth_files:
1500 case svn_depth_immediates:
1501 case svn_depth_infinity:
1503 SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
1505 changelists, receiver, baton,
1506 cancel_func, cancel_baton,
1511 SVN_ERR_MALFUNCTION();
1514 return SVN_NO_ERROR;
1518 svn_wc__prop_retrieve_recursive(apr_hash_t **values,
1519 svn_wc_context_t *wc_ctx,
1520 const char *local_abspath,
1521 const char *propname,
1522 apr_pool_t *result_pool,
1523 apr_pool_t *scratch_pool)
1525 return svn_error_trace(
1526 svn_wc__db_prop_retrieve_recursive(values,
1530 result_pool, scratch_pool));
1534 svn_wc_get_pristine_props(apr_hash_t **props,
1535 svn_wc_context_t *wc_ctx,
1536 const char *local_abspath,
1537 apr_pool_t *result_pool,
1538 apr_pool_t *scratch_pool)
1542 SVN_ERR_ASSERT(props != NULL);
1543 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1545 /* Certain node stats do not have properties defined on them. Check the
1546 state, and return NULL for these situations. */
1548 err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
1549 result_pool, scratch_pool);
1553 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1554 return svn_error_trace(err);
1556 svn_error_clear(err);
1558 /* Documented behavior is to set *PROPS to NULL */
1562 return SVN_NO_ERROR;
1566 svn_wc_prop_get2(const svn_string_t **value,
1567 svn_wc_context_t *wc_ctx,
1568 const char *local_abspath,
1570 apr_pool_t *result_pool,
1571 apr_pool_t *scratch_pool)
1573 enum svn_prop_kind kind = svn_property_kind2(name);
1576 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1578 if (kind == svn_prop_entry_kind)
1580 /* we don't do entry properties here */
1581 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1582 _("Property '%s' is an entry property"), name);
1585 err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
1586 result_pool, scratch_pool);
1590 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1591 return svn_error_trace(err);
1593 svn_error_clear(err);
1594 /* Documented behavior is to set *VALUE to NULL */
1598 return SVN_NO_ERROR;
1602 svn_wc__internal_propget(const svn_string_t **value,
1604 const char *local_abspath,
1606 apr_pool_t *result_pool,
1607 apr_pool_t *scratch_pool)
1609 apr_hash_t *prophash = NULL;
1610 enum svn_prop_kind kind = svn_property_kind2(name);
1612 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1613 SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
1615 if (kind == svn_prop_wc_kind)
1617 SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1618 result_pool, scratch_pool),
1619 _("Failed to load properties"));
1624 SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
1625 result_pool, scratch_pool),
1626 _("Failed to load properties"));
1630 *value = svn_hash_gets(prophash, name);
1634 return SVN_NO_ERROR;
1638 /* The special Subversion properties are not valid for all node kinds.
1639 Return an error if NAME is an invalid Subversion property for PATH which
1640 is of kind NODE_KIND. NAME must be in the "svn:" name space.
1642 Note that we only disallow the property if we're sure it's one that
1643 already has a meaning for a different node kind. We don't disallow
1644 setting an *unknown* svn: prop here, at this level; a higher level
1645 should disallow that if desired.
1647 static svn_error_t *
1648 validate_prop_against_node_kind(const char *name,
1650 svn_node_kind_t node_kind,
1653 const char *path_display
1654 = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1659 if (! svn_prop_is_known_svn_dir_prop(name)
1660 && svn_prop_is_known_svn_file_prop(name))
1661 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1662 _("Cannot set '%s' on a directory ('%s')"),
1663 name, path_display);
1666 if (! svn_prop_is_known_svn_file_prop(name)
1667 && svn_prop_is_known_svn_dir_prop(name))
1668 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1669 _("Cannot set '%s' on a file ('%s')"),
1674 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1675 _("'%s' is not a file or directory"),
1679 return SVN_NO_ERROR;
1683 struct getter_baton {
1684 const svn_string_t *mime_type;
1685 const char *local_abspath;
1689 /* Provide the MIME_TYPE and/or push the content to STREAM for the file
1690 * referenced by (getter_baton *) BATON.
1692 * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
1693 static svn_error_t *
1694 get_file_for_validation(const svn_string_t **mime_type,
1695 svn_stream_t *stream,
1699 struct getter_baton *gb = baton;
1702 *mime_type = gb->mime_type;
1706 svn_stream_t *read_stream;
1708 /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
1709 SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
1711 SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
1715 return SVN_NO_ERROR;
1719 /* Validate that a file has a 'non-binary' MIME type and contains
1720 * self-consistent line endings. If not, then return an error.
1722 * Call GETTER (which must not be NULL) with GETTER_BATON to get the
1723 * file's MIME type and/or content. If the MIME type is non-null and
1724 * is categorized as 'binary' then return an error and do not request
1727 * Use PATH (a local path or a URL) only for error messages.
1729 static svn_error_t *
1730 validate_eol_prop_against_file(const char *path,
1731 svn_wc_canonicalize_svn_prop_get_file_t getter,
1735 svn_stream_t *translating_stream;
1737 const svn_string_t *mime_type;
1738 const char *path_display
1739 = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1741 /* First just ask the "getter" for the MIME type. */
1742 SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
1744 /* See if this file has been determined to be binary. */
1745 if (mime_type && svn_mime_type_is_binary(mime_type->data))
1746 return svn_error_createf
1747 (SVN_ERR_ILLEGAL_TARGET, NULL,
1748 _("Can't set '%s': "
1749 "file '%s' has binary mime type property"),
1750 SVN_PROP_EOL_STYLE, path_display);
1752 /* Now ask the getter for the contents of the file; this will do a
1753 newline translation. All we really care about here is whether or
1754 not the function fails on inconsistent line endings. The
1755 function is "translating" to an empty stream. This is
1756 sneeeeeeeeeeeaky. */
1757 translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
1758 "", FALSE, NULL, FALSE,
1761 err = getter(NULL, translating_stream, getter_baton, pool);
1763 err = svn_error_compose_create(err, svn_stream_close(translating_stream));
1765 if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1766 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
1767 _("File '%s' has inconsistent newlines"),
1770 return svn_error_trace(err);
1773 static svn_error_t *
1774 do_propset(svn_wc__db_t *db,
1775 const char *local_abspath,
1776 svn_node_kind_t kind,
1778 const svn_string_t *value,
1779 svn_boolean_t skip_checks,
1780 svn_wc_notify_func2_t notify_func,
1782 apr_pool_t *scratch_pool)
1784 apr_hash_t *prophash;
1785 svn_wc_notify_action_t notify_action;
1786 svn_skel_t *work_item = NULL;
1787 svn_boolean_t clear_recorded_info = FALSE;
1789 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1791 SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
1792 scratch_pool, scratch_pool),
1793 _("Failed to load current properties"));
1795 /* Setting an inappropriate property is not allowed (unless
1796 overridden by 'skip_checks', in some circumstances). Deleting an
1797 inappropriate property is allowed, however, since older clients
1798 allowed (and other clients possibly still allow) setting it in
1800 if (value && svn_prop_is_svn_prop(name))
1802 const svn_string_t *new_value;
1803 struct getter_baton gb;
1805 gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
1806 gb.local_abspath = local_abspath;
1808 SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
1809 local_abspath, kind,
1811 get_file_for_validation, &gb,
1816 if (kind == svn_node_file
1817 && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
1818 || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
1820 SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
1821 scratch_pool, scratch_pool));
1824 /* If we're changing this file's list of expanded keywords, then
1825 * we'll need to invalidate its text timestamp, since keyword
1826 * expansion affects the comparison of working file to text base.
1828 * Here we retrieve the old list of expanded keywords; after the
1829 * property is set, we'll grab the new list and see if it differs
1832 if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
1834 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
1835 apr_hash_t *old_keywords, *new_keywords;
1838 SVN_ERR(svn_wc__expand_keywords(&old_keywords,
1839 db, local_abspath, NULL,
1840 old_value->data, TRUE,
1841 scratch_pool, scratch_pool));
1843 old_keywords = apr_hash_make(scratch_pool);
1846 SVN_ERR(svn_wc__expand_keywords(&new_keywords,
1847 db, local_abspath, NULL,
1849 scratch_pool, scratch_pool));
1851 new_keywords = apr_hash_make(scratch_pool);
1853 if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
1856 /* If the keywords have changed, then the translation of the file
1857 may be different. We should invalidate the RECORDED_SIZE
1858 and RECORDED_TIME on this node.
1860 Note that we don't immediately re-translate the file. But a
1861 "has it changed?" check in the future will do a translation
1862 from the pristine, and it will want to compare the (new)
1863 resulting RECORDED_SIZE against the working copy file.
1865 Also, when this file is (de)translated with the new keywords,
1866 then it could be different, relative to the pristine. We want
1867 to ensure the RECORDED_TIME is different, to indicate that
1868 a full detranslate/compare is performed. */
1869 clear_recorded_info = TRUE;
1872 else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
1874 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
1876 if (((value == NULL) != (old_value == NULL))
1877 || (value && ! svn_string_compare(value, old_value)))
1879 clear_recorded_info = TRUE;
1883 /* Find out what type of property change we are doing: add, modify, or
1885 if (svn_hash_gets(prophash, name) == NULL)
1888 /* Deleting a non-existent property. */
1889 notify_action = svn_wc_notify_property_deleted_nonexistent;
1891 /* Adding a property. */
1892 notify_action = svn_wc_notify_property_added;
1897 /* Deleting the property. */
1898 notify_action = svn_wc_notify_property_deleted;
1900 /* Modifying property. */
1901 notify_action = svn_wc_notify_property_modified;
1904 /* Now we have all the properties in our hash. Simply merge the new
1905 property into it. */
1906 svn_hash_sets(prophash, name, value);
1908 /* Drop it right into the db.. */
1909 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
1910 clear_recorded_info, NULL, work_item,
1913 /* Run our workqueue item for sync'ing flags with props. */
1915 SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
1919 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
1922 notify->prop_name = name;
1923 notify->kind = kind;
1925 (*notify_func)(notify_baton, notify, scratch_pool);
1928 return SVN_NO_ERROR;
1931 /* A baton for propset_walk_cb. */
1932 struct propset_walk_baton
1934 const char *propname; /* The name of the property to set. */
1935 const svn_string_t *propval; /* The value to set. */
1936 svn_wc__db_t *db; /* Database for the tree being walked. */
1937 svn_boolean_t force; /* True iff force was passed. */
1938 svn_wc_notify_func2_t notify_func;
1942 /* An node-walk callback for svn_wc_prop_set4().
1944 * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
1945 * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
1946 * propset_walk_baton *".
1948 static svn_error_t *
1949 propset_walk_cb(const char *local_abspath,
1950 svn_node_kind_t kind,
1952 apr_pool_t *scratch_pool)
1954 struct propset_walk_baton *wb = walk_baton;
1957 err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
1958 wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
1959 if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
1960 || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
1962 svn_error_clear(err);
1966 return svn_error_trace(err);
1970 svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
1971 const char *local_abspath,
1973 const svn_string_t *value,
1975 svn_boolean_t skip_checks,
1976 const apr_array_header_t *changelist_filter,
1977 svn_cancel_func_t cancel_func,
1979 svn_wc_notify_func2_t notify_func,
1981 apr_pool_t *scratch_pool)
1983 enum svn_prop_kind prop_kind = svn_property_kind2(name);
1984 svn_wc__db_status_t status;
1985 svn_node_kind_t kind;
1986 svn_wc__db_t *db = wc_ctx->db;
1988 /* we don't do entry properties here */
1989 if (prop_kind == svn_prop_entry_kind)
1990 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1991 _("Property '%s' is an entry property"), name);
1993 /* Check to see if we're setting the dav cache. */
1994 if (prop_kind == svn_prop_wc_kind)
1996 SVN_ERR_ASSERT(depth == svn_depth_empty);
1997 return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
1998 name, value, scratch_pool));
2001 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
2002 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2003 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2004 NULL, NULL, NULL, NULL, NULL, NULL,
2005 wc_ctx->db, local_abspath,
2006 scratch_pool, scratch_pool));
2008 if (status != svn_wc__db_status_normal
2009 && status != svn_wc__db_status_added
2010 && status != svn_wc__db_status_incomplete)
2012 return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
2013 _("Can't set properties on '%s':"
2014 " invalid status for updating properties."),
2015 svn_dirent_local_style(local_abspath,
2019 /* We have to do this little DIR_ABSPATH dance for backwards compat.
2020 But from 1.7 onwards, all locks are of infinite depth, and from 1.6
2021 backward we never call this API with depth > empty, so we only need
2022 to do the write check once per call, here (and not for every node in
2025 ### Note that we could check for a write lock on local_abspath first
2026 ### if we would want to. And then justy check for kind if that fails.
2027 ### ... but we need kind for the "svn:" property checks anyway */
2029 const char *dir_abspath;
2031 if (kind == svn_node_dir)
2032 dir_abspath = local_abspath;
2034 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
2036 /* Verify that we're holding this directory's write lock. */
2037 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
2040 if (depth == svn_depth_empty || kind != svn_node_dir)
2042 apr_hash_t *changelist_hash = NULL;
2044 if (changelist_filter && changelist_filter->nelts)
2045 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
2048 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
2049 changelist_hash, scratch_pool))
2050 return SVN_NO_ERROR;
2052 SVN_ERR(do_propset(wc_ctx->db, local_abspath,
2053 kind == svn_node_dir
2056 name, value, skip_checks,
2057 notify_func, notify_baton, scratch_pool));
2062 struct propset_walk_baton wb;
2067 wb.force = skip_checks;
2068 wb.notify_func = notify_func;
2069 wb.notify_baton = notify_baton;
2071 SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
2072 FALSE, changelist_filter,
2073 propset_walk_cb, &wb,
2075 cancel_func, cancel_baton,
2079 return SVN_NO_ERROR;
2082 /* Check that NAME names a regular prop. Return an error if it names an
2083 * entry prop or a WC prop. */
2084 static svn_error_t *
2085 ensure_prop_is_regular_kind(const char *name)
2087 enum svn_prop_kind prop_kind = svn_property_kind2(name);
2089 /* we don't do entry properties here */
2090 if (prop_kind == svn_prop_entry_kind)
2091 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
2092 _("Property '%s' is an entry property"), name);
2094 /* Check to see if we're setting the dav cache. */
2095 if (prop_kind == svn_prop_wc_kind)
2096 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
2097 _("Property '%s' is a WC property, not "
2098 "a regular property"), name);
2100 return SVN_NO_ERROR;
2104 svn_wc__canonicalize_props(apr_hash_t **prepared_props,
2105 const char *local_abspath,
2106 svn_node_kind_t node_kind,
2107 const apr_hash_t *props,
2108 svn_boolean_t skip_some_checks,
2109 apr_pool_t *result_pool,
2110 apr_pool_t *scratch_pool)
2112 const svn_string_t *mime_type;
2113 struct getter_baton gb;
2114 apr_hash_index_t *hi;
2116 /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
2117 don't promise to deep-copy the unchanged keys and values. */
2118 *prepared_props = apr_hash_make(result_pool);
2120 /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
2121 * so process that first. */
2122 mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
2125 SVN_ERR(svn_wc_canonicalize_svn_prop(
2126 &mime_type, SVN_PROP_MIME_TYPE, mime_type,
2127 local_abspath, node_kind, skip_some_checks,
2128 NULL, NULL, scratch_pool));
2129 svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
2132 /* Set up the context for canonicalizing the other properties. */
2133 gb.mime_type = mime_type;
2134 gb.local_abspath = local_abspath;
2136 /* Check and canonicalize the other properties. */
2137 for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
2138 hi = apr_hash_next(hi))
2140 const char *name = apr_hash_this_key(hi);
2141 const svn_string_t *value = apr_hash_this_val(hi);
2143 if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
2146 SVN_ERR(ensure_prop_is_regular_kind(name));
2147 SVN_ERR(svn_wc_canonicalize_svn_prop(
2148 &value, name, value,
2149 local_abspath, node_kind, skip_some_checks,
2150 get_file_for_validation, &gb, scratch_pool));
2151 svn_hash_sets(*prepared_props, name, value);
2154 return SVN_NO_ERROR;
2159 svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2160 const char *propname,
2161 const svn_string_t *propval,
2163 svn_node_kind_t kind,
2164 svn_boolean_t skip_some_checks,
2165 svn_wc_canonicalize_svn_prop_get_file_t getter,
2169 svn_stringbuf_t *new_value = NULL;
2171 /* Keep this static, it may get stored (for read-only purposes) in a
2172 hash that outlives this function. */
2173 static const svn_string_t boolean_value
2174 = SVN__STATIC_STRING(SVN_PROP_BOOLEAN_TRUE);
2176 SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2178 /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
2179 if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2181 svn_subst_eol_style_t eol_style;
2182 const char *ignored_eol;
2183 new_value = svn_stringbuf_create_from_string(propval, pool);
2184 svn_stringbuf_strip_whitespace(new_value);
2185 svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2186 if (eol_style == svn_subst_eol_style_unknown)
2187 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2188 _("Unrecognized line ending style '%s' for '%s'"),
2190 svn_dirent_local_style(path, pool));
2191 SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2194 else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2196 new_value = svn_stringbuf_create_from_string(propval, pool);
2197 svn_stringbuf_strip_whitespace(new_value);
2198 SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2200 else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2201 || strcmp(propname, SVN_PROP_EXTERNALS) == 0
2202 || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
2203 || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
2205 /* Make sure that the last line ends in a newline */
2206 if (propval->len == 0
2207 || propval->data[propval->len - 1] != '\n')
2209 new_value = svn_stringbuf_create_from_string(propval, pool);
2210 svn_stringbuf_appendbyte(new_value, '\n');
2213 /* Make sure this is a valid externals property. Do not
2214 allow 'skip_some_checks' to override, as there is no circumstance in
2215 which this is proper (because there is no circumstance in
2216 which Subversion can handle it). */
2217 if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2219 /* We don't allow "." nor ".." as target directories in
2220 an svn:externals line. As it happens, our parse code
2221 checks for this, so all we have to is invoke it --
2222 we're not interested in the parsed result, only in
2223 whether or not the parsing errored. */
2224 apr_array_header_t *externals = NULL;
2225 apr_array_header_t *duplicate_targets = NULL;
2226 SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
2227 propval->data, FALSE,
2229 SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2233 if (duplicate_targets && duplicate_targets->nelts > 0)
2235 const char *more_str = "";
2236 if (duplicate_targets->nelts > 1)
2238 more_str = apr_psprintf(/*scratch_*/pool,
2239 _(" (%d more duplicate targets found)"),
2240 duplicate_targets->nelts - 1);
2242 return svn_error_createf(
2243 SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2244 _("Invalid %s property on '%s': "
2245 "target '%s' appears more than once%s"),
2247 svn_dirent_local_style(path, pool),
2248 APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2253 else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2255 new_value = svn_stringbuf_create_from_string(propval, pool);
2256 svn_stringbuf_strip_whitespace(new_value);
2258 else if (svn_prop_is_boolean(propname))
2260 /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2261 propval = &boolean_value;
2263 else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2265 apr_hash_t *mergeinfo;
2266 svn_string_t *new_value_str;
2268 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2270 /* Non-inheritable mergeinfo is only valid on directories. */
2271 if (kind != svn_node_dir
2272 && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2273 return svn_error_createf(
2274 SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2275 _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2276 svn_dirent_local_style(path, pool));
2278 SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2279 propval = new_value_str;
2283 *propval_p = svn_stringbuf__morph_into_string(new_value);
2285 *propval_p = propval;
2287 return SVN_NO_ERROR;
2292 svn_wc_is_normal_prop(const char *name)
2294 enum svn_prop_kind kind = svn_property_kind2(name);
2295 return (kind == svn_prop_regular_kind);
2300 svn_wc_is_wc_prop(const char *name)
2302 enum svn_prop_kind kind = svn_property_kind2(name);
2303 return (kind == svn_prop_wc_kind);
2308 svn_wc_is_entry_prop(const char *name)
2310 enum svn_prop_kind kind = svn_property_kind2(name);
2311 return (kind == svn_prop_entry_kind);
2316 svn_wc__props_modified(svn_boolean_t *modified_p,
2318 const char *local_abspath,
2319 apr_pool_t *scratch_pool)
2321 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2322 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2323 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2324 NULL, NULL, modified_p, NULL, NULL, NULL,
2326 scratch_pool, scratch_pool));
2328 return SVN_NO_ERROR;
2332 svn_wc_props_modified_p2(svn_boolean_t *modified_p,
2333 svn_wc_context_t* wc_ctx,
2334 const char *local_abspath,
2335 apr_pool_t *scratch_pool)
2337 return svn_error_trace(
2338 svn_wc__props_modified(modified_p,
2345 svn_wc__internal_propdiff(apr_array_header_t **propchanges,
2346 apr_hash_t **original_props,
2348 const char *local_abspath,
2349 apr_pool_t *result_pool,
2350 apr_pool_t *scratch_pool)
2352 apr_hash_t *baseprops;
2354 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2356 /* ### if pristines are not defined, then should this raise an error,
2357 ### or use an empty set? */
2358 SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2359 result_pool, scratch_pool));
2361 if (original_props != NULL)
2362 *original_props = baseprops;
2364 if (propchanges != NULL)
2366 apr_hash_t *actual_props;
2368 /* Some nodes do not have pristine props, so let's just use an empty
2369 set here. Thus, any ACTUAL props are additions. */
2370 if (baseprops == NULL)
2371 baseprops = apr_hash_make(scratch_pool);
2373 SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2374 result_pool, scratch_pool));
2375 /* ### be wary. certain nodes don't have ACTUAL props either. we
2376 ### may want to raise an error. or maybe that is a deletion of
2377 ### any potential pristine props? */
2379 SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2383 return SVN_NO_ERROR;
2387 svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2388 apr_hash_t **original_props,
2389 svn_wc_context_t *wc_ctx,
2390 const char *local_abspath,
2391 apr_pool_t *result_pool,
2392 apr_pool_t *scratch_pool)
2394 return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2395 original_props, wc_ctx->db, local_abspath,
2396 result_pool, scratch_pool));
2400 svn_wc__has_magic_property(const apr_array_header_t *properties)
2404 for (i = 0; i < properties->nelts; i++)
2406 const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2408 if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2409 || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2410 || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2411 || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2412 || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2419 svn_wc__get_iprops(apr_array_header_t **inherited_props,
2420 svn_wc_context_t *wc_ctx,
2421 const char *local_abspath,
2422 const char *propname,
2423 apr_pool_t *result_pool,
2424 apr_pool_t *scratch_pool)
2426 return svn_error_trace(
2427 svn_wc__db_read_inherited_props(inherited_props, NULL,
2428 wc_ctx->db, local_abspath,
2430 result_pool, scratch_pool));
2434 svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2436 svn_wc_context_t *wc_ctx,
2437 const char *local_abspath,
2438 apr_pool_t *result_pool,
2439 apr_pool_t *scratch_pool)
2441 SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2447 return SVN_NO_ERROR;