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 /* Forward declaration. */
67 prop_conflict_from_skel(const svn_string_t **conflict_desc,
68 const svn_skel_t *skel,
69 apr_pool_t *result_pool,
70 apr_pool_t *scratch_pool);
72 /* Given a *SINGLE* property conflict in PROP_SKEL, generate a description
73 for it, and write it to STREAM, along with a trailing EOL sequence.
75 See prop_conflict_from_skel() for details on PROP_SKEL. */
77 append_prop_conflict(svn_stream_t *stream,
78 const svn_skel_t *prop_skel,
81 /* TODO: someday, perhaps prefix each conflict_description with a
82 timestamp or something? */
83 const svn_string_t *conflict_desc;
85 SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
87 return svn_stream_puts(stream, conflict_desc->data);
90 /*---------------------------------------------------------------------*/
92 /*** Merging propchanges into the working copy ***/
95 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
96 calculate the deltas between them. */
98 diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
99 const svn_string_t *from_prop_val,
100 const svn_string_t *to_prop_val, apr_pool_t *pool)
102 if (svn_string_compare(from_prop_val, to_prop_val))
104 /* Don't bothering parsing identical mergeinfo. */
105 *deleted = apr_hash_make(pool);
106 *added = apr_hash_make(pool);
110 svn_mergeinfo_t from, to;
111 SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
112 SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
113 SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
119 /* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
120 reconstitute it into *OUTPUT. Call when the WC's mergeinfo has
121 been modified to combine it with incoming mergeinfo from the
124 combine_mergeinfo_props(const svn_string_t **output,
125 const svn_string_t *prop_val1,
126 const svn_string_t *prop_val2,
127 apr_pool_t *result_pool,
128 apr_pool_t *scratch_pool)
130 svn_mergeinfo_t mergeinfo1, mergeinfo2;
131 svn_string_t *mergeinfo_string;
133 SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
134 SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
135 SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
137 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
138 *output = mergeinfo_string;
142 /* Perform a 3-way merge operation on mergeinfo. FROM_PROP_VAL is
143 the "base" property value, WORKING_PROP_VAL is the current value,
144 and TO_PROP_VAL is the new value. */
146 combine_forked_mergeinfo_props(const svn_string_t **output,
147 const svn_string_t *from_prop_val,
148 const svn_string_t *working_prop_val,
149 const svn_string_t *to_prop_val,
150 apr_pool_t *result_pool,
151 apr_pool_t *scratch_pool)
153 svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
154 svn_string_t *mergeinfo_string;
156 /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
157 SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
158 working_prop_val, scratch_pool));
159 SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
160 to_prop_val, scratch_pool));
161 SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
162 scratch_pool, scratch_pool));
163 SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
164 scratch_pool, scratch_pool));
166 /* Apply the combined deltas to the base. */
167 SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
169 SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
170 scratch_pool, scratch_pool));
172 SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
173 TRUE, scratch_pool, scratch_pool));
175 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
177 *output = mergeinfo_string;
183 svn_wc_merge_props3(svn_wc_notify_state_t *state,
184 svn_wc_context_t *wc_ctx,
185 const char *local_abspath,
186 const svn_wc_conflict_version_t *left_version,
187 const svn_wc_conflict_version_t *right_version,
188 apr_hash_t *baseprops,
189 const apr_array_header_t *propchanges,
190 svn_boolean_t dry_run,
191 svn_wc_conflict_resolver_func2_t conflict_func,
192 void *conflict_baton,
193 svn_cancel_func_t cancel_func,
195 apr_pool_t *scratch_pool)
198 svn_wc__db_status_t status;
199 svn_node_kind_t kind;
200 apr_hash_t *pristine_props = NULL;
201 apr_hash_t *actual_props;
202 apr_hash_t *new_actual_props;
203 svn_boolean_t had_props, props_mod;
204 svn_boolean_t have_base;
205 svn_boolean_t conflicted;
206 svn_skel_t *work_items = NULL;
207 svn_skel_t *conflict_skel = NULL;
208 svn_wc__db_t *db = wc_ctx->db;
210 /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
213 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
214 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
215 NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
216 &had_props, &props_mod, &have_base, NULL, NULL,
218 scratch_pool, scratch_pool));
220 /* Checks whether the node exists and returns the hidden flag */
221 if (status == svn_wc__db_status_not_present
222 || status == svn_wc__db_status_server_excluded
223 || status == svn_wc__db_status_excluded)
225 return svn_error_createf(
226 SVN_ERR_WC_PATH_NOT_FOUND, NULL,
227 _("The node '%s' was not found."),
228 svn_dirent_local_style(local_abspath, scratch_pool));
230 else if (status != svn_wc__db_status_normal
231 && status != svn_wc__db_status_added
232 && status != svn_wc__db_status_incomplete)
234 return svn_error_createf(
235 SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
236 _("The node '%s' does not have properties in this state."),
237 svn_dirent_local_style(local_abspath, scratch_pool));
241 svn_boolean_t text_conflicted;
242 svn_boolean_t prop_conflicted;
243 svn_boolean_t tree_conflicted;
245 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
251 /* We can't install two text/prop conflicts on a single node, so
252 avoid even checking that we have to merge it */
253 if (text_conflicted || prop_conflicted || tree_conflicted)
255 return svn_error_createf(
256 SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
257 _("Can't merge into conflicted node '%s'"),
258 svn_dirent_local_style(local_abspath,
261 /* else: Conflict was resolved by removing markers */
264 /* The PROPCHANGES may not have non-"normal" properties in it. If entry
265 or wc props were allowed, then the following code would install them
266 into the BASE and/or WORKING properties(!). */
267 for (i = propchanges->nelts; i--; )
269 const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
271 if (!svn_wc_is_normal_prop(change->name))
272 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
273 _("The property '%s' may not be merged "
276 svn_dirent_local_style(local_abspath,
281 SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
282 scratch_pool, scratch_pool));
283 if (pristine_props == NULL)
284 pristine_props = apr_hash_make(scratch_pool);
287 SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
288 scratch_pool, scratch_pool));
290 actual_props = pristine_props;
292 /* Note that while this routine does the "real" work, it's only
293 prepping tempfiles and writing log commands. */
294 SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
297 baseprops /* server_baseprops */,
301 scratch_pool, scratch_pool));
309 const char *dir_abspath;
311 if (kind == svn_node_dir)
312 dir_abspath = local_abspath;
314 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
316 /* Verify that we're holding this directory's write lock. */
317 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
322 svn_skel_t *work_item;
323 SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
329 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
332 scratch_pool, scratch_pool));
334 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
337 /* After a (not-dry-run) merge, we ALWAYS have props to save. */
338 SVN_ERR_ASSERT(new_actual_props != NULL);
340 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
341 svn_wc__has_magic_property(propchanges),
346 if (work_items != NULL)
347 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
350 /* If there is a conflict, try to resolve it. */
351 if (conflict_skel && conflict_func)
353 svn_boolean_t prop_conflicted;
355 SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
356 NULL /* merge_options */,
357 conflict_func, conflict_baton,
358 cancel_func, cancel_baton,
361 /* Reset *STATE if all prop conflicts were resolved. */
362 SVN_ERR(svn_wc__internal_conflicted_p(
363 NULL, &prop_conflicted, NULL,
364 wc_ctx->db, local_abspath, scratch_pool));
365 if (! prop_conflicted)
366 *state = svn_wc_notify_state_merged;
373 /* Generate a message to describe the property conflict among these four
376 Note that this function (currently) interprets the property values as
377 strings, but they could actually be binary values. We'll keep the
378 types as svn_string_t in case we fix this in the future. */
379 static svn_stringbuf_t *
380 generate_conflict_message(const char *propname,
381 const svn_string_t *original,
382 const svn_string_t *mine,
383 const svn_string_t *incoming,
384 const svn_string_t *incoming_base,
385 apr_pool_t *result_pool)
387 if (incoming_base == NULL)
389 /* Attempting to add the value INCOMING. */
390 SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
394 /* To have a conflict, these must be different. */
395 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
397 /* Note that we don't care whether MINE is locally-added or
398 edited, or just something different that is a copy of the
399 pristine ORIGINAL. */
400 return svn_stringbuf_createf(result_pool,
401 _("Trying to add new property '%s'\n"
402 "but the property already exists.\n"),
406 /* To have a conflict, we must have an ORIGINAL which has been
408 SVN_ERR_ASSERT_NO_RETURN(original != NULL);
409 return svn_stringbuf_createf(result_pool,
410 _("Trying to add new property '%s'\n"
411 "but the property has been locally "
416 if (incoming == NULL)
418 /* Attempting to delete the value INCOMING_BASE. */
419 SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
421 /* Are we trying to delete a local addition? */
422 if (original == NULL && mine != NULL)
423 return svn_stringbuf_createf(result_pool,
424 _("Trying to delete property '%s'\n"
425 "but the property has been locally "
429 /* A conflict can only occur if we originally had the property;
430 otherwise, we would have merged the property-delete into the
431 non-existent property. */
432 SVN_ERR_ASSERT_NO_RETURN(original != NULL);
434 if (svn_string_compare(original, incoming_base))
437 /* We were trying to delete the correct property, but an edit
438 caused the conflict. */
439 return svn_stringbuf_createf(result_pool,
440 _("Trying to delete property '%s'\n"
441 "but the property has been locally "
445 else if (mine == NULL)
447 /* We were trying to delete the property, but we have locally
448 deleted the same property, but with a different value. */
449 return svn_stringbuf_createf(result_pool,
450 _("Trying to delete property '%s'\n"
451 "but the property has been locally "
452 "deleted and had a different "
457 /* We were trying to delete INCOMING_BASE but our ORIGINAL is
458 something else entirely. */
459 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
461 return svn_stringbuf_createf(result_pool,
462 _("Trying to delete property '%s'\n"
463 "but the local property value is "
468 /* Attempting to change the property from INCOMING_BASE to INCOMING. */
470 /* If we have a (current) property value, then it should be different
471 from the INCOMING_BASE; otherwise, the incoming change would have
472 been applied to it. */
473 SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
475 if (original && mine && svn_string_compare(original, mine))
477 /* We have an unchanged property, so the original values must
478 have been different. */
479 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
480 return svn_stringbuf_createf(result_pool,
481 _("Trying to change property '%s'\n"
482 "but the local property value conflicts "
483 "with the incoming change.\n"),
487 if (original && mine)
488 return svn_stringbuf_createf(result_pool,
489 _("Trying to change property '%s'\n"
490 "but the property has already been locally "
491 "changed to a different value.\n"),
495 return svn_stringbuf_createf(result_pool,
496 _("Trying to change property '%s'\nbut "
497 "the property has been locally deleted.\n"),
501 return svn_stringbuf_createf(result_pool,
502 _("Trying to change property '%s'\nbut the "
503 "property has been locally added with a "
504 "different value.\n"),
507 return svn_stringbuf_createf(result_pool,
508 _("Trying to change property '%s'\nbut "
509 "the property does not exist locally.\n"),
514 /* SKEL will be one of:
519 Return NULL for the former (the particular property value was not
520 present), and VALUE for the second. */
521 static const svn_string_t *
522 maybe_prop_value(const svn_skel_t *skel,
523 apr_pool_t *result_pool)
525 if (skel->children == NULL)
528 return svn_string_ncreate(skel->children->data,
534 /* Parse a property conflict description from the provided SKEL.
535 The result includes a descriptive message (see generate_conflict_message)
536 and maybe a diff of property values containing conflict markers.
537 The result will be allocated in RESULT_POOL.
539 Note: SKEL is a single property conflict of the form:
541 ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
543 See notes/wc-ng/conflict-storage for more information. */
545 prop_conflict_from_skel(const svn_string_t **conflict_desc,
546 const svn_skel_t *skel,
547 apr_pool_t *result_pool,
548 apr_pool_t *scratch_pool)
550 const svn_string_t *original;
551 const svn_string_t *mine;
552 const svn_string_t *incoming;
553 const svn_string_t *incoming_base;
554 const char *propname;
556 svn_diff_file_options_t *diff_opts;
557 svn_stringbuf_t *buf;
558 svn_boolean_t original_is_binary;
559 svn_boolean_t mine_is_binary;
560 svn_boolean_t incoming_is_binary;
562 /* Navigate to the property name. */
563 skel = skel->children->next;
565 /* We need to copy these into SCRATCH_POOL in order to nul-terminate
567 propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
568 original = maybe_prop_value(skel->next, scratch_pool);
569 mine = maybe_prop_value(skel->next->next, scratch_pool);
570 incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
571 incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
573 buf = generate_conflict_message(propname, original, mine, incoming,
574 incoming_base, scratch_pool);
577 mine = svn_string_create_empty(scratch_pool);
578 if (incoming == NULL)
579 incoming = svn_string_create_empty(scratch_pool);
581 /* Pick a suitable base for the conflict diff.
582 * The incoming value is always a change,
583 * but the local value might not have changed. */
584 if (original == NULL)
587 original = incoming_base;
589 original = svn_string_create_empty(scratch_pool);
591 else if (incoming_base && svn_string_compare(original, mine))
592 original = incoming_base;
594 /* If any of the property values involved in the diff is binary data,
595 * do not generate a diff. */
596 original_is_binary = svn_io_is_binary_data(original->data, original->len);
597 mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
598 incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
600 if (!(original_is_binary || mine_is_binary || incoming_is_binary))
602 diff_opts = svn_diff_file_options_create(scratch_pool);
603 diff_opts->ignore_space = svn_diff_file_ignore_space_none;
604 diff_opts->ignore_eol_style = FALSE;
605 diff_opts->show_c_function = FALSE;
606 SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming,
607 diff_opts, scratch_pool));
608 if (svn_diff_contains_conflicts(diff))
610 svn_stream_t *stream;
611 svn_diff_conflict_display_style_t style;
612 const char *mine_marker = _("<<<<<<< (local property value)");
613 const char *incoming_marker = _(">>>>>>> (incoming property value)");
614 const char *separator = "=======";
615 svn_string_t *original_ascii =
616 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data,
619 svn_string_t *mine_ascii =
620 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
623 svn_string_t *incoming_ascii =
624 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
628 style = svn_diff_conflict_display_modified_latest;
629 stream = svn_stream_from_stringbuf(buf, scratch_pool);
630 SVN_ERR(svn_stream_skip(stream, buf->len));
631 SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff,
636 incoming_marker, separator,
637 style, scratch_pool));
638 SVN_ERR(svn_stream_close(stream));
640 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
645 /* If we could not print a conflict diff just print full values . */
648 svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
650 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
653 svn_stringbuf_appendbytes(buf, mine->data, mine->len);
654 svn_stringbuf_appendcstr(buf, "\n");
657 if (incoming->len > 0)
659 svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
660 if (incoming_is_binary)
661 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
664 svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
665 svn_stringbuf_appendcstr(buf, "\n");
668 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
673 /* Create a property conflict file at PREJFILE based on the property
674 conflicts in CONFLICT_SKEL. */
676 svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
678 const char *local_abspath,
679 const svn_skel_t *conflict_skel,
680 apr_pool_t *result_pool,
681 apr_pool_t *scratch_pool)
683 const char *tempdir_abspath;
684 svn_stream_t *stream;
685 const char *temp_abspath;
686 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
687 const svn_skel_t *scan;
689 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
691 iterpool, iterpool));
693 SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
694 tempdir_abspath, svn_io_file_del_none,
695 scratch_pool, iterpool));
697 for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next)
699 svn_pool_clear(iterpool);
701 SVN_ERR(append_prop_conflict(stream, scan, iterpool));
704 SVN_ERR(svn_stream_close(stream));
706 svn_pool_destroy(iterpool);
708 *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
713 /* Set the value of *STATE to NEW_VALUE if STATE is not NULL
714 * and NEW_VALUE is a higer order value than *STATE's current value
715 * using this ordering (lower order first):
717 * - unknown, unchanged, inapplicable
726 set_prop_merge_state(svn_wc_notify_state_t *state,
727 svn_wc_notify_state_t new_value)
729 static char ordering[] =
730 { svn_wc_notify_state_unknown,
731 svn_wc_notify_state_unchanged,
732 svn_wc_notify_state_inapplicable,
733 svn_wc_notify_state_changed,
734 svn_wc_notify_state_merged,
735 svn_wc_notify_state_obstructed,
736 svn_wc_notify_state_conflicted };
737 int state_pos = 0, i;
742 /* Find *STATE in our ordering */
743 for (i = 0; i < sizeof(ordering); i++)
745 if (*state == ordering[i])
752 /* Find NEW_VALUE in our ordering
753 * We don't need to look further than where we found *STATE though:
754 * If we find our value, it's order is too low.
755 * If we don't find it, we'll want to set it, no matter its order.
758 for (i = 0; i <= state_pos; i++)
760 if (new_value == ordering[i])
767 /* Apply the addition of a property with name PROPNAME and value NEW_VAL to
768 * the existing property with value WORKING_VAL, that originally had value
771 * Sets *RESULT_VAL to the resulting value.
772 * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
773 * Sets *DID_MERGE to true if the result is caused by a merge
776 apply_single_prop_add(const svn_string_t **result_val,
777 svn_boolean_t *conflict_remains,
778 svn_boolean_t *did_merge,
779 const char *propname,
780 const svn_string_t *pristine_val,
781 const svn_string_t *new_val,
782 const svn_string_t *working_val,
783 apr_pool_t *result_pool,
784 apr_pool_t *scratch_pool)
787 *conflict_remains = FALSE;
791 /* the property already exists in actual_props... */
793 if (svn_string_compare(working_val, new_val))
794 /* The value we want is already there, so it's a merge. */
799 svn_boolean_t merged_prop = FALSE;
801 /* The WC difference doesn't match the new value.
802 We only merge mergeinfo; other props conflict */
803 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
805 const svn_string_t *merged_val;
806 svn_error_t *err = combine_mergeinfo_props(&merged_val,
812 /* Issue #3896 'mergeinfo syntax errors should be treated
813 gracefully': If bogus mergeinfo is present we can't
814 merge intelligently, so raise a conflict instead. */
817 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
818 svn_error_clear(err);
820 return svn_error_trace(err);
825 *result_val = merged_val;
831 *conflict_remains = TRUE;
834 else if (pristine_val)
835 *conflict_remains = TRUE;
836 else /* property doesn't yet exist in actual_props... */
838 *result_val = new_val;
844 /* Apply the deletion of a property to the existing
845 * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
847 * Sets *RESULT_VAL to the resulting value.
848 * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
849 * Sets *DID_MERGE to true if the result is caused by a merge
852 apply_single_prop_delete(const svn_string_t **result_val,
853 svn_boolean_t *conflict_remains,
854 svn_boolean_t *did_merge,
855 const svn_string_t *base_val,
856 const svn_string_t *old_val,
857 const svn_string_t *working_val)
859 *conflict_remains = FALSE;
864 && !svn_string_compare(working_val, old_val))
866 /* We are trying to delete a locally-added prop. */
867 *conflict_remains = TRUE;
873 /* This is a merge, merging a delete into non-existent
874 property or a local addition of same prop value. */
879 else if (svn_string_compare(base_val, old_val))
883 if (svn_string_compare(working_val, old_val))
884 /* they have the same values, so it's an update */
887 *conflict_remains = TRUE;
890 /* The property is locally deleted from the same value, so it's
896 *conflict_remains = TRUE;
902 /* Merge a change to the mergeinfo property. Similar to
903 apply_single_prop_change(), except that the property name is always
904 SVN_PROP_MERGEINFO. */
905 /* ### This function is extracted straight from the previous all-in-one
906 version of apply_single_prop_change() by removing the code paths that
907 were not followed for this property, but with no attempt to rationalize
910 apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
911 svn_boolean_t *conflict_remains,
912 svn_boolean_t *did_merge,
913 const svn_string_t *base_val,
914 const svn_string_t *old_val,
915 const svn_string_t *new_val,
916 const svn_string_t *working_val,
917 apr_pool_t *result_pool,
918 apr_pool_t *scratch_pool)
920 if ((working_val && ! base_val)
921 || (! working_val && base_val)
922 || (working_val && base_val
923 && !svn_string_compare(working_val, base_val)))
925 /* Locally changed property */
928 if (svn_string_compare(working_val, new_val))
929 /* The new value equals the changed value: a no-op merge */
933 /* We have base, WC, and new values. Discover
934 deltas between base <-> WC, and base <->
935 incoming. Combine those deltas, and apply
936 them to base to get the new value. */
937 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
942 *result_val = new_val;
948 /* There is a base_val but no working_val */
949 *conflict_remains = TRUE;
953 else if (! working_val) /* means !working_val && !base_val due
954 to conditions above: no prop at all */
956 /* Discover any mergeinfo additions in the
957 incoming value relative to the base, and
958 "combine" those with the empty WC value. */
959 svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
960 svn_string_t *mergeinfo_string;
962 SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
964 old_val, new_val, scratch_pool));
965 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
966 added_mergeinfo, result_pool));
967 *result_val = mergeinfo_string;
970 else /* means working && base && svn_string_compare(working, base) */
972 if (svn_string_compare(old_val, base_val))
973 *result_val = new_val;
976 /* We have base, WC, and new values. Discover
977 deltas between base <-> WC, and base <->
978 incoming. Combine those deltas, and apply
979 them to base to get the new value. */
980 SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
982 new_val, result_pool,
984 *result_val = new_val;
992 /* Merge a change to a property, using the rule that if the working value
993 is equal to the new value then there is nothing we need to do. Else, if
994 the working value is the same as the old value then apply the change as a
995 simple update (replacement), otherwise invoke maybe_generate_propconflict().
996 The definition of the arguments and behaviour is the same as
997 apply_single_prop_change(). */
999 apply_single_generic_prop_change(const svn_string_t **result_val,
1000 svn_boolean_t *conflict_remains,
1001 svn_boolean_t *did_merge,
1002 const svn_string_t *old_val,
1003 const svn_string_t *new_val,
1004 const svn_string_t *working_val)
1006 SVN_ERR_ASSERT(old_val != NULL);
1008 /* If working_val is the same as new_val already then there is
1010 if (working_val && new_val
1011 && svn_string_compare(working_val, new_val))
1013 /* All values identical is a trivial, non-notifiable merge */
1014 if (! old_val || ! svn_string_compare(old_val, new_val))
1017 /* If working_val is the same as old_val... */
1018 else if (working_val && old_val
1019 && svn_string_compare(working_val, old_val))
1021 /* A trivial update: change it to new_val. */
1022 *result_val = new_val;
1026 /* Merge the change. */
1027 *conflict_remains = TRUE;
1030 return SVN_NO_ERROR;
1033 /* Change the property with name PROPNAME, setting *RESULT_VAL,
1034 * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
1036 * BASE_VAL contains the working copy base property value. (May be null.)
1038 * OLD_VAL contains the value of the property the server
1039 * thinks it's overwriting. (Not null.)
1041 * NEW_VAL contains the value to be set. (Not null.)
1043 * WORKING_VAL contains the working copy actual value. (May be null.)
1045 static svn_error_t *
1046 apply_single_prop_change(const svn_string_t **result_val,
1047 svn_boolean_t *conflict_remains,
1048 svn_boolean_t *did_merge,
1049 const char *propname,
1050 const svn_string_t *base_val,
1051 const svn_string_t *old_val,
1052 const svn_string_t *new_val,
1053 const svn_string_t *working_val,
1054 apr_pool_t *result_pool,
1055 apr_pool_t *scratch_pool)
1057 svn_boolean_t merged_prop = FALSE;
1059 *conflict_remains = FALSE;
1061 /* Note: The purpose is to apply the change (old_val -> new_val) onto
1062 (working_val). There is no need for base_val to be involved in the
1063 process except as a bit of context to help the user understand and
1064 resolve any conflict. */
1066 /* Decide how to merge, based on whether we know anything special about
1068 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1070 /* We know how to merge any mergeinfo property change...
1072 ...But Issue #3896 'mergeinfo syntax errors should be treated
1073 gracefully' might thwart us. If bogus mergeinfo is present we
1074 can't merge intelligently, so let the standard method deal with
1076 svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
1087 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1088 svn_error_clear(err);
1090 return svn_error_trace(err);
1100 /* The standard method: perform a simple update automatically, but
1101 pass any other kind of merge to maybe_generate_propconflict(). */
1103 SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
1105 old_val, new_val, working_val));
1108 return SVN_NO_ERROR;
1113 svn_wc__merge_props(svn_skel_t **conflict_skel,
1114 svn_wc_notify_state_t *state,
1115 apr_hash_t **new_actual_props,
1117 const char *local_abspath,
1118 apr_hash_t *server_baseprops,
1119 apr_hash_t *pristine_props,
1120 apr_hash_t *actual_props,
1121 const apr_array_header_t *propchanges,
1122 apr_pool_t *result_pool,
1123 apr_pool_t *scratch_pool)
1125 apr_pool_t *iterpool;
1127 apr_hash_t *conflict_props = NULL;
1128 apr_hash_t *their_props;
1130 SVN_ERR_ASSERT(pristine_props != NULL);
1131 SVN_ERR_ASSERT(actual_props != NULL);
1133 *new_actual_props = apr_hash_copy(result_pool, actual_props);
1135 if (!server_baseprops)
1136 server_baseprops = pristine_props;
1138 their_props = apr_hash_copy(scratch_pool, server_baseprops);
1142 /* Start out assuming no changes or conflicts. Don't bother to
1143 examine propchanges->nelts yet; even if we knew there were
1144 propchanges, we wouldn't yet know if they are "normal" props,
1145 as opposed wc or entry props. */
1146 *state = svn_wc_notify_state_unchanged;
1149 /* Looping over the array of incoming propchanges we want to apply: */
1150 iterpool = svn_pool_create(scratch_pool);
1151 for (i = 0; i < propchanges->nelts; i++)
1153 const svn_prop_t *incoming_change
1154 = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1155 const char *propname = incoming_change->name;
1156 const svn_string_t *base_val /* Pristine in WC */
1157 = svn_hash_gets(pristine_props, propname);
1158 const svn_string_t *from_val /* Merge left */
1159 = svn_hash_gets(server_baseprops, propname);
1160 const svn_string_t *to_val /* Merge right */
1161 = incoming_change->value;
1162 const svn_string_t *working_val /* Mine */
1163 = svn_hash_gets(actual_props, propname);
1164 const svn_string_t *result_val;
1165 svn_boolean_t conflict_remains;
1166 svn_boolean_t did_merge = FALSE;
1168 svn_pool_clear(iterpool);
1170 to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL;
1172 svn_hash_sets(their_props, propname, to_val);
1175 /* We already know that state is at least `changed', so mark
1176 that, but remember that we may later upgrade to `merged' or
1177 even `conflicted'. */
1178 set_prop_merge_state(state, svn_wc_notify_state_changed);
1180 result_val = working_val;
1182 if (! from_val) /* adding a new property */
1183 SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
1184 &did_merge, propname,
1185 base_val, to_val, working_val,
1186 result_pool, iterpool));
1188 else if (! to_val) /* delete an existing property */
1189 SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
1191 base_val, from_val, working_val));
1193 else /* changing an existing property */
1194 SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
1195 &did_merge, propname,
1196 base_val, from_val, to_val, working_val,
1197 result_pool, iterpool));
1199 if (result_val != working_val)
1200 svn_hash_sets(*new_actual_props, propname, result_val);
1202 set_prop_merge_state(state, svn_wc_notify_state_merged);
1204 /* merging logic complete, now we need to possibly log conflict
1205 data to tmpfiles. */
1207 if (conflict_remains)
1209 set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1211 if (!conflict_props)
1212 conflict_props = apr_hash_make(scratch_pool);
1214 svn_hash_sets(conflict_props, propname, "");
1217 } /* foreach propchange ... */
1218 svn_pool_destroy(iterpool);
1220 /* Finished applying all incoming propchanges to our hashes! */
1222 if (conflict_props != NULL)
1224 /* Ok, we got some conflict. Lets store all the property knowledge we
1225 have for resolving later */
1227 if (!*conflict_skel)
1228 *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1230 SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
1232 NULL /* reject_path */,
1241 return SVN_NO_ERROR;
1245 /* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
1246 If VALUE is null, remove property NAME. */
1247 static svn_error_t *
1248 wcprop_set(svn_wc__db_t *db,
1249 const char *local_abspath,
1251 const svn_string_t *value,
1252 apr_pool_t *scratch_pool)
1254 apr_hash_t *prophash;
1256 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1258 /* Note: this is not well-transacted. But... meh. This is merely a cache,
1259 and if two processes are trying to modify this one entry at the same
1260 time, then fine: we can let one be a winner, and one a loser. Of course,
1261 if there are *other* state changes afoot, then the lack of a txn could
1262 be a real issue, but we cannot solve that here. */
1264 SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1265 scratch_pool, scratch_pool));
1267 if (prophash == NULL)
1268 prophash = apr_hash_make(scratch_pool);
1270 svn_hash_sets(prophash, name, value);
1271 return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
1278 svn_wc__get_actual_props(apr_hash_t **props,
1280 const char *local_abspath,
1281 apr_pool_t *result_pool,
1282 apr_pool_t *scratch_pool)
1284 SVN_ERR_ASSERT(props != NULL);
1285 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1287 /* ### perform some state checking. for example, locally-deleted nodes
1288 ### should not have any ACTUAL props. */
1290 return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
1291 result_pool, scratch_pool));
1296 svn_wc_prop_list2(apr_hash_t **props,
1297 svn_wc_context_t *wc_ctx,
1298 const char *local_abspath,
1299 apr_pool_t *result_pool,
1300 apr_pool_t *scratch_pool)
1302 return svn_error_trace(svn_wc__get_actual_props(props,
1309 struct propname_filter_baton_t {
1310 svn_wc__proplist_receiver_t receiver_func;
1311 void *receiver_baton;
1312 const char *propname;
1315 static svn_error_t *
1316 propname_filter_receiver(void *baton,
1317 const char *local_abspath,
1319 apr_pool_t *scratch_pool)
1321 struct propname_filter_baton_t *pfb = baton;
1322 const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
1326 props = apr_hash_make(scratch_pool);
1327 svn_hash_sets(props, pfb->propname, propval);
1329 SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
1333 return SVN_NO_ERROR;
1337 svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
1338 const char *local_abspath,
1339 const char *propname,
1341 svn_boolean_t pristine,
1342 const apr_array_header_t *changelists,
1343 svn_wc__proplist_receiver_t receiver_func,
1344 void *receiver_baton,
1345 svn_cancel_func_t cancel_func,
1347 apr_pool_t *scratch_pool)
1349 svn_wc__proplist_receiver_t receiver = receiver_func;
1350 void *baton = receiver_baton;
1351 struct propname_filter_baton_t pfb;
1353 pfb.receiver_func = receiver_func;
1354 pfb.receiver_baton = receiver_baton;
1355 pfb.propname = propname;
1357 SVN_ERR_ASSERT(receiver_func);
1362 receiver = propname_filter_receiver;
1367 case svn_depth_empty:
1370 apr_hash_t *changelist_hash = NULL;
1372 if (changelists && changelists->nelts)
1373 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1374 changelists, scratch_pool));
1376 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1377 changelist_hash, scratch_pool))
1381 SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
1383 scratch_pool, scratch_pool));
1385 SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
1386 scratch_pool, scratch_pool));
1388 if (props && apr_hash_count(props) > 0)
1389 SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
1392 case svn_depth_files:
1393 case svn_depth_immediates:
1394 case svn_depth_infinity:
1396 SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
1398 changelists, receiver, baton,
1399 cancel_func, cancel_baton,
1404 SVN_ERR_MALFUNCTION();
1407 return SVN_NO_ERROR;
1411 svn_wc__prop_retrieve_recursive(apr_hash_t **values,
1412 svn_wc_context_t *wc_ctx,
1413 const char *local_abspath,
1414 const char *propname,
1415 apr_pool_t *result_pool,
1416 apr_pool_t *scratch_pool)
1418 return svn_error_trace(
1419 svn_wc__db_prop_retrieve_recursive(values,
1423 result_pool, scratch_pool));
1427 svn_wc_get_pristine_props(apr_hash_t **props,
1428 svn_wc_context_t *wc_ctx,
1429 const char *local_abspath,
1430 apr_pool_t *result_pool,
1431 apr_pool_t *scratch_pool)
1435 SVN_ERR_ASSERT(props != NULL);
1436 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1438 /* Certain node stats do not have properties defined on them. Check the
1439 state, and return NULL for these situations. */
1441 err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
1442 result_pool, scratch_pool);
1446 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1447 return svn_error_trace(err);
1449 svn_error_clear(err);
1451 /* Documented behavior is to set *PROPS to NULL */
1455 return SVN_NO_ERROR;
1459 svn_wc_prop_get2(const svn_string_t **value,
1460 svn_wc_context_t *wc_ctx,
1461 const char *local_abspath,
1463 apr_pool_t *result_pool,
1464 apr_pool_t *scratch_pool)
1466 enum svn_prop_kind kind = svn_property_kind2(name);
1469 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1471 if (kind == svn_prop_entry_kind)
1473 /* we don't do entry properties here */
1474 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1475 _("Property '%s' is an entry property"), name);
1478 err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
1479 result_pool, scratch_pool);
1483 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1484 return svn_error_trace(err);
1486 svn_error_clear(err);
1487 /* Documented behavior is to set *VALUE to NULL */
1491 return SVN_NO_ERROR;
1495 svn_wc__internal_propget(const svn_string_t **value,
1497 const char *local_abspath,
1499 apr_pool_t *result_pool,
1500 apr_pool_t *scratch_pool)
1502 apr_hash_t *prophash = NULL;
1503 enum svn_prop_kind kind = svn_property_kind2(name);
1505 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1506 SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
1508 if (kind == svn_prop_wc_kind)
1510 SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1511 result_pool, scratch_pool),
1512 _("Failed to load properties"));
1517 SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
1518 result_pool, scratch_pool),
1519 _("Failed to load properties"));
1523 *value = svn_hash_gets(prophash, name);
1527 return SVN_NO_ERROR;
1531 /* The special Subversion properties are not valid for all node kinds.
1532 Return an error if NAME is an invalid Subversion property for PATH which
1533 is of kind NODE_KIND. NAME must be in the "svn:" name space.
1535 Note that we only disallow the property if we're sure it's one that
1536 already has a meaning for a different node kind. We don't disallow
1537 setting an *unknown* svn: prop here, at this level; a higher level
1538 should disallow that if desired.
1540 static svn_error_t *
1541 validate_prop_against_node_kind(const char *name,
1543 svn_node_kind_t node_kind,
1546 const char *path_display
1547 = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1552 if (! svn_prop_is_known_svn_dir_prop(name)
1553 && svn_prop_is_known_svn_file_prop(name))
1554 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1555 _("Cannot set '%s' on a directory ('%s')"),
1556 name, path_display);
1559 if (! svn_prop_is_known_svn_file_prop(name)
1560 && svn_prop_is_known_svn_dir_prop(name))
1561 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1562 _("Cannot set '%s' on a file ('%s')"),
1567 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1568 _("'%s' is not a file or directory"),
1572 return SVN_NO_ERROR;
1576 struct getter_baton {
1577 const svn_string_t *mime_type;
1578 const char *local_abspath;
1582 /* Provide the MIME_TYPE and/or push the content to STREAM for the file
1583 * referenced by (getter_baton *) BATON.
1585 * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
1586 static svn_error_t *
1587 get_file_for_validation(const svn_string_t **mime_type,
1588 svn_stream_t *stream,
1592 struct getter_baton *gb = baton;
1595 *mime_type = gb->mime_type;
1599 svn_stream_t *read_stream;
1601 /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
1602 SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
1604 SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
1608 return SVN_NO_ERROR;
1612 /* Validate that a file has a 'non-binary' MIME type and contains
1613 * self-consistent line endings. If not, then return an error.
1615 * Call GETTER (which must not be NULL) with GETTER_BATON to get the
1616 * file's MIME type and/or content. If the MIME type is non-null and
1617 * is categorized as 'binary' then return an error and do not request
1620 * Use PATH (a local path or a URL) only for error messages.
1622 static svn_error_t *
1623 validate_eol_prop_against_file(const char *path,
1624 svn_wc_canonicalize_svn_prop_get_file_t getter,
1628 svn_stream_t *translating_stream;
1630 const svn_string_t *mime_type;
1631 const char *path_display
1632 = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1634 /* First just ask the "getter" for the MIME type. */
1635 SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
1637 /* See if this file has been determined to be binary. */
1638 if (mime_type && svn_mime_type_is_binary(mime_type->data))
1639 return svn_error_createf
1640 (SVN_ERR_ILLEGAL_TARGET, NULL,
1641 _("Can't set '%s': "
1642 "file '%s' has binary mime type property"),
1643 SVN_PROP_EOL_STYLE, path_display);
1645 /* Now ask the getter for the contents of the file; this will do a
1646 newline translation. All we really care about here is whether or
1647 not the function fails on inconsistent line endings. The
1648 function is "translating" to an empty stream. This is
1649 sneeeeeeeeeeeaky. */
1650 translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
1651 "", FALSE, NULL, FALSE,
1654 err = getter(NULL, translating_stream, getter_baton, pool);
1656 err = svn_error_compose_create(err, svn_stream_close(translating_stream));
1658 if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1659 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
1660 _("File '%s' has inconsistent newlines"),
1663 return svn_error_trace(err);
1666 static svn_error_t *
1667 do_propset(svn_wc__db_t *db,
1668 const char *local_abspath,
1669 svn_node_kind_t kind,
1671 const svn_string_t *value,
1672 svn_boolean_t skip_checks,
1673 svn_wc_notify_func2_t notify_func,
1675 apr_pool_t *scratch_pool)
1677 apr_hash_t *prophash;
1678 svn_wc_notify_action_t notify_action;
1679 svn_skel_t *work_item = NULL;
1680 svn_boolean_t clear_recorded_info = FALSE;
1682 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1684 SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
1685 scratch_pool, scratch_pool),
1686 _("Failed to load current properties"));
1688 /* Setting an inappropriate property is not allowed (unless
1689 overridden by 'skip_checks', in some circumstances). Deleting an
1690 inappropriate property is allowed, however, since older clients
1691 allowed (and other clients possibly still allow) setting it in
1693 if (value && svn_prop_is_svn_prop(name))
1695 const svn_string_t *new_value;
1696 struct getter_baton gb;
1698 gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
1699 gb.local_abspath = local_abspath;
1701 SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
1702 local_abspath, kind,
1704 get_file_for_validation, &gb,
1709 if (kind == svn_node_file
1710 && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
1711 || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
1713 SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
1714 scratch_pool, scratch_pool));
1717 /* If we're changing this file's list of expanded keywords, then
1718 * we'll need to invalidate its text timestamp, since keyword
1719 * expansion affects the comparison of working file to text base.
1721 * Here we retrieve the old list of expanded keywords; after the
1722 * property is set, we'll grab the new list and see if it differs
1725 if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
1727 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
1728 apr_hash_t *old_keywords, *new_keywords;
1731 SVN_ERR(svn_wc__expand_keywords(&old_keywords,
1732 db, local_abspath, NULL,
1733 old_value->data, TRUE,
1734 scratch_pool, scratch_pool));
1736 old_keywords = apr_hash_make(scratch_pool);
1739 SVN_ERR(svn_wc__expand_keywords(&new_keywords,
1740 db, local_abspath, NULL,
1742 scratch_pool, scratch_pool));
1744 new_keywords = apr_hash_make(scratch_pool);
1746 if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
1749 /* If the keywords have changed, then the translation of the file
1750 may be different. We should invalidate the RECORDED_SIZE
1751 and RECORDED_TIME on this node.
1753 Note that we don't immediately re-translate the file. But a
1754 "has it changed?" check in the future will do a translation
1755 from the pristine, and it will want to compare the (new)
1756 resulting RECORDED_SIZE against the working copy file.
1758 Also, when this file is (de)translated with the new keywords,
1759 then it could be different, relative to the pristine. We want
1760 to ensure the RECORDED_TIME is different, to indicate that
1761 a full detranslate/compare is performed. */
1762 clear_recorded_info = TRUE;
1765 else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
1767 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
1769 if (((value == NULL) != (old_value == NULL))
1770 || (value && ! svn_string_compare(value, old_value)))
1772 clear_recorded_info = TRUE;
1776 /* Find out what type of property change we are doing: add, modify, or
1778 if (svn_hash_gets(prophash, name) == NULL)
1781 /* Deleting a non-existent property. */
1782 notify_action = svn_wc_notify_property_deleted_nonexistent;
1784 /* Adding a property. */
1785 notify_action = svn_wc_notify_property_added;
1790 /* Deleting the property. */
1791 notify_action = svn_wc_notify_property_deleted;
1793 /* Modifying property. */
1794 notify_action = svn_wc_notify_property_modified;
1797 /* Now we have all the properties in our hash. Simply merge the new
1798 property into it. */
1799 svn_hash_sets(prophash, name, value);
1801 /* Drop it right into the db.. */
1802 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
1803 clear_recorded_info, NULL, work_item,
1806 /* Run our workqueue item for sync'ing flags with props. */
1808 SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
1812 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
1815 notify->prop_name = name;
1816 notify->kind = kind;
1818 (*notify_func)(notify_baton, notify, scratch_pool);
1821 return SVN_NO_ERROR;
1824 /* A baton for propset_walk_cb. */
1825 struct propset_walk_baton
1827 const char *propname; /* The name of the property to set. */
1828 const svn_string_t *propval; /* The value to set. */
1829 svn_wc__db_t *db; /* Database for the tree being walked. */
1830 svn_boolean_t force; /* True iff force was passed. */
1831 svn_wc_notify_func2_t notify_func;
1835 /* An node-walk callback for svn_wc_prop_set4().
1837 * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
1838 * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
1839 * propset_walk_baton *".
1841 static svn_error_t *
1842 propset_walk_cb(const char *local_abspath,
1843 svn_node_kind_t kind,
1845 apr_pool_t *scratch_pool)
1847 struct propset_walk_baton *wb = walk_baton;
1850 err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
1851 wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
1852 if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
1853 || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
1855 svn_error_clear(err);
1859 return svn_error_trace(err);
1863 svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
1864 const char *local_abspath,
1866 const svn_string_t *value,
1868 svn_boolean_t skip_checks,
1869 const apr_array_header_t *changelist_filter,
1870 svn_cancel_func_t cancel_func,
1872 svn_wc_notify_func2_t notify_func,
1874 apr_pool_t *scratch_pool)
1876 enum svn_prop_kind prop_kind = svn_property_kind2(name);
1877 svn_wc__db_status_t status;
1878 svn_node_kind_t kind;
1879 svn_wc__db_t *db = wc_ctx->db;
1881 /* we don't do entry properties here */
1882 if (prop_kind == svn_prop_entry_kind)
1883 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1884 _("Property '%s' is an entry property"), name);
1886 /* Check to see if we're setting the dav cache. */
1887 if (prop_kind == svn_prop_wc_kind)
1889 SVN_ERR_ASSERT(depth == svn_depth_empty);
1890 return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
1891 name, value, scratch_pool));
1894 SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
1895 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1896 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1897 NULL, NULL, NULL, NULL, NULL, NULL,
1898 wc_ctx->db, local_abspath,
1899 scratch_pool, scratch_pool));
1901 if (status != svn_wc__db_status_normal
1902 && status != svn_wc__db_status_added
1903 && status != svn_wc__db_status_incomplete)
1905 return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
1906 _("Can't set properties on '%s':"
1907 " invalid status for updating properties."),
1908 svn_dirent_local_style(local_abspath,
1912 /* We have to do this little DIR_ABSPATH dance for backwards compat.
1913 But from 1.7 onwards, all locks are of infinite depth, and from 1.6
1914 backward we never call this API with depth > empty, so we only need
1915 to do the write check once per call, here (and not for every node in
1918 ### Note that we could check for a write lock on local_abspath first
1919 ### if we would want to. And then justy check for kind if that fails.
1920 ### ... but we need kind for the "svn:" property checks anyway */
1922 const char *dir_abspath;
1924 if (kind == svn_node_dir)
1925 dir_abspath = local_abspath;
1927 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
1929 /* Verify that we're holding this directory's write lock. */
1930 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
1933 if (depth == svn_depth_empty || kind != svn_node_dir)
1935 apr_hash_t *changelist_hash = NULL;
1937 if (changelist_filter && changelist_filter->nelts)
1938 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
1941 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1942 changelist_hash, scratch_pool))
1943 return SVN_NO_ERROR;
1945 SVN_ERR(do_propset(wc_ctx->db, local_abspath,
1946 kind == svn_node_dir
1949 name, value, skip_checks,
1950 notify_func, notify_baton, scratch_pool));
1955 struct propset_walk_baton wb;
1960 wb.force = skip_checks;
1961 wb.notify_func = notify_func;
1962 wb.notify_baton = notify_baton;
1964 SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
1965 FALSE, changelist_filter,
1966 propset_walk_cb, &wb,
1968 cancel_func, cancel_baton,
1972 return SVN_NO_ERROR;
1975 /* Check that NAME names a regular prop. Return an error if it names an
1976 * entry prop or a WC prop. */
1977 static svn_error_t *
1978 ensure_prop_is_regular_kind(const char *name)
1980 enum svn_prop_kind prop_kind = svn_property_kind2(name);
1982 /* we don't do entry properties here */
1983 if (prop_kind == svn_prop_entry_kind)
1984 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1985 _("Property '%s' is an entry property"), name);
1987 /* Check to see if we're setting the dav cache. */
1988 if (prop_kind == svn_prop_wc_kind)
1989 return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1990 _("Property '%s' is a WC property, not "
1991 "a regular property"), name);
1993 return SVN_NO_ERROR;
1997 svn_wc__canonicalize_props(apr_hash_t **prepared_props,
1998 const char *local_abspath,
1999 svn_node_kind_t node_kind,
2000 const apr_hash_t *props,
2001 svn_boolean_t skip_some_checks,
2002 apr_pool_t *result_pool,
2003 apr_pool_t *scratch_pool)
2005 const svn_string_t *mime_type;
2006 struct getter_baton gb;
2007 apr_hash_index_t *hi;
2009 /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
2010 don't promise to deep-copy the unchanged keys and values. */
2011 *prepared_props = apr_hash_make(result_pool);
2013 /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
2014 * so process that first. */
2015 mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
2018 SVN_ERR(svn_wc_canonicalize_svn_prop(
2019 &mime_type, SVN_PROP_MIME_TYPE, mime_type,
2020 local_abspath, node_kind, skip_some_checks,
2021 NULL, NULL, scratch_pool));
2022 svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
2025 /* Set up the context for canonicalizing the other properties. */
2026 gb.mime_type = mime_type;
2027 gb.local_abspath = local_abspath;
2029 /* Check and canonicalize the other properties. */
2030 for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
2031 hi = apr_hash_next(hi))
2033 const char *name = svn__apr_hash_index_key(hi);
2034 const svn_string_t *value = svn__apr_hash_index_val(hi);
2036 if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
2039 SVN_ERR(ensure_prop_is_regular_kind(name));
2040 SVN_ERR(svn_wc_canonicalize_svn_prop(
2041 &value, name, value,
2042 local_abspath, node_kind, skip_some_checks,
2043 get_file_for_validation, &gb, scratch_pool));
2044 svn_hash_sets(*prepared_props, name, value);
2047 return SVN_NO_ERROR;
2052 svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2053 const char *propname,
2054 const svn_string_t *propval,
2056 svn_node_kind_t kind,
2057 svn_boolean_t skip_some_checks,
2058 svn_wc_canonicalize_svn_prop_get_file_t getter,
2062 svn_stringbuf_t *new_value = NULL;
2064 /* Keep this static, it may get stored (for read-only purposes) in a
2065 hash that outlives this function. */
2066 static const svn_string_t boolean_value =
2068 SVN_PROP_BOOLEAN_TRUE,
2069 sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
2072 SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2074 /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
2075 if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2077 svn_subst_eol_style_t eol_style;
2078 const char *ignored_eol;
2079 new_value = svn_stringbuf_create_from_string(propval, pool);
2080 svn_stringbuf_strip_whitespace(new_value);
2081 svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2082 if (eol_style == svn_subst_eol_style_unknown)
2083 return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2084 _("Unrecognized line ending style '%s' for '%s'"),
2086 svn_dirent_local_style(path, pool));
2087 SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2090 else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2092 new_value = svn_stringbuf_create_from_string(propval, pool);
2093 svn_stringbuf_strip_whitespace(new_value);
2094 SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2096 else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2097 || strcmp(propname, SVN_PROP_EXTERNALS) == 0
2098 || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
2099 || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
2101 /* Make sure that the last line ends in a newline */
2102 if (propval->len == 0
2103 || propval->data[propval->len - 1] != '\n')
2105 new_value = svn_stringbuf_create_from_string(propval, pool);
2106 svn_stringbuf_appendbyte(new_value, '\n');
2109 /* Make sure this is a valid externals property. Do not
2110 allow 'skip_some_checks' to override, as there is no circumstance in
2111 which this is proper (because there is no circumstance in
2112 which Subversion can handle it). */
2113 if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2115 /* We don't allow "." nor ".." as target directories in
2116 an svn:externals line. As it happens, our parse code
2117 checks for this, so all we have to is invoke it --
2118 we're not interested in the parsed result, only in
2119 whether or not the parsing errored. */
2120 apr_array_header_t *externals = NULL;
2121 apr_array_header_t *duplicate_targets = NULL;
2122 SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
2123 propval->data, FALSE,
2125 SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2129 if (duplicate_targets && duplicate_targets->nelts > 0)
2131 const char *more_str = "";
2132 if (duplicate_targets->nelts > 1)
2134 more_str = apr_psprintf(/*scratch_*/pool,
2135 _(" (%d more duplicate targets found)"),
2136 duplicate_targets->nelts - 1);
2138 return svn_error_createf(
2139 SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2140 _("Invalid %s property on '%s': "
2141 "target '%s' appears more than once%s"),
2143 svn_dirent_local_style(path, pool),
2144 APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2149 else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2151 new_value = svn_stringbuf_create_from_string(propval, pool);
2152 svn_stringbuf_strip_whitespace(new_value);
2154 else if (svn_prop_is_boolean(propname))
2156 /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2157 propval = &boolean_value;
2159 else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2161 apr_hash_t *mergeinfo;
2162 svn_string_t *new_value_str;
2164 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2166 /* Non-inheritable mergeinfo is only valid on directories. */
2167 if (kind != svn_node_dir
2168 && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2169 return svn_error_createf(
2170 SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2171 _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2172 svn_dirent_local_style(path, pool));
2174 SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2175 propval = new_value_str;
2179 *propval_p = svn_stringbuf__morph_into_string(new_value);
2181 *propval_p = propval;
2183 return SVN_NO_ERROR;
2188 svn_wc_is_normal_prop(const char *name)
2190 enum svn_prop_kind kind = svn_property_kind2(name);
2191 return (kind == svn_prop_regular_kind);
2196 svn_wc_is_wc_prop(const char *name)
2198 enum svn_prop_kind kind = svn_property_kind2(name);
2199 return (kind == svn_prop_wc_kind);
2204 svn_wc_is_entry_prop(const char *name)
2206 enum svn_prop_kind kind = svn_property_kind2(name);
2207 return (kind == svn_prop_entry_kind);
2212 svn_wc__props_modified(svn_boolean_t *modified_p,
2214 const char *local_abspath,
2215 apr_pool_t *scratch_pool)
2217 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2218 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2219 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2220 NULL, NULL, modified_p, NULL, NULL, NULL,
2222 scratch_pool, scratch_pool));
2224 return SVN_NO_ERROR;
2228 svn_wc_props_modified_p2(svn_boolean_t *modified_p,
2229 svn_wc_context_t* wc_ctx,
2230 const char *local_abspath,
2231 apr_pool_t *scratch_pool)
2233 return svn_error_trace(
2234 svn_wc__props_modified(modified_p,
2241 svn_wc__internal_propdiff(apr_array_header_t **propchanges,
2242 apr_hash_t **original_props,
2244 const char *local_abspath,
2245 apr_pool_t *result_pool,
2246 apr_pool_t *scratch_pool)
2248 apr_hash_t *baseprops;
2250 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2252 /* ### if pristines are not defined, then should this raise an error,
2253 ### or use an empty set? */
2254 SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2255 result_pool, scratch_pool));
2257 if (original_props != NULL)
2258 *original_props = baseprops;
2260 if (propchanges != NULL)
2262 apr_hash_t *actual_props;
2264 /* Some nodes do not have pristine props, so let's just use an empty
2265 set here. Thus, any ACTUAL props are additions. */
2266 if (baseprops == NULL)
2267 baseprops = apr_hash_make(scratch_pool);
2269 SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2270 result_pool, scratch_pool));
2271 /* ### be wary. certain nodes don't have ACTUAL props either. we
2272 ### may want to raise an error. or maybe that is a deletion of
2273 ### any potential pristine props? */
2275 SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2279 return SVN_NO_ERROR;
2283 svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2284 apr_hash_t **original_props,
2285 svn_wc_context_t *wc_ctx,
2286 const char *local_abspath,
2287 apr_pool_t *result_pool,
2288 apr_pool_t *scratch_pool)
2290 return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2291 original_props, wc_ctx->db, local_abspath,
2292 result_pool, scratch_pool));
2296 svn_wc__has_magic_property(const apr_array_header_t *properties)
2300 for (i = 0; i < properties->nelts; i++)
2302 const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2304 if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2305 || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2306 || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2307 || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2308 || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2315 svn_wc__get_iprops(apr_array_header_t **inherited_props,
2316 svn_wc_context_t *wc_ctx,
2317 const char *local_abspath,
2318 const char *propname,
2319 apr_pool_t *result_pool,
2320 apr_pool_t *scratch_pool)
2322 return svn_error_trace(
2323 svn_wc__db_read_inherited_props(inherited_props, NULL,
2324 wc_ctx->db, local_abspath,
2326 result_pool, scratch_pool));
2330 svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2332 svn_wc_context_t *wc_ctx,
2333 const char *local_abspath,
2334 apr_pool_t *result_pool,
2335 apr_pool_t *scratch_pool)
2337 SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2343 return SVN_NO_ERROR;