1 /*
2 * props.c : routines dealing with properties in the working copy
3 *
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
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
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
20 * under the License.
21 * ====================================================================
22 */
23
24
25
26 #include <stdlib.h>
27 #include <string.h>
28
29 #include <apr_pools.h>
30 #include <apr_hash.h>
31 #include <apr_tables.h>
32 #include <apr_file_io.h>
33 #include <apr_strings.h>
34 #include <apr_general.h>
35
36 #include "svn_types.h"
37 #include "svn_string.h"
38 #include "svn_pools.h"
39 #include "svn_dirent_uri.h"
40 #include "svn_path.h"
41 #include "svn_error.h"
42 #include "svn_props.h"
43 #include "svn_io.h"
44 #include "svn_hash.h"
45 #include "svn_mergeinfo.h"
46 #include "svn_wc.h"
47 #include "svn_utf.h"
48 #include "svn_diff.h"
49 #include "svn_sorts.h"
50
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"
56
57 #include "wc.h"
58 #include "props.h"
59 #include "translate.h"
60 #include "workqueue.h"
61 #include "conflicts.h"
62
63 #include "svn_private_config.h"
64
65 /*---------------------------------------------------------------------*/
66
67 /*** Merging propchanges into the working copy ***/
68
69
70 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
71 calculate the deltas between them. */
72 static svn_error_t *
diff_mergeinfo_props(svn_mergeinfo_t * deleted,svn_mergeinfo_t * added,const svn_string_t * from_prop_val,const svn_string_t * to_prop_val,apr_pool_t * pool)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)
76 {
77 if (svn_string_compare(from_prop_val, to_prop_val))
78 {
79 /* Don't bothering parsing identical mergeinfo. */
80 *deleted = apr_hash_make(pool);
81 *added = apr_hash_make(pool);
82 }
83 else
84 {
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,
89 TRUE, pool, pool));
90 }
91 return SVN_NO_ERROR;
92 }
93
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
97 repos. */
98 static svn_error_t *
combine_mergeinfo_props(const svn_string_t ** output,const svn_string_t * prop_val1,const svn_string_t * prop_val2,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
104 {
105 svn_mergeinfo_t mergeinfo1, mergeinfo2;
106 svn_string_t *mergeinfo_string;
107
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,
111 scratch_pool));
112 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
113 *output = mergeinfo_string;
114 return SVN_NO_ERROR;
115 }
116
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. */
120 static svn_error_t *
combine_forked_mergeinfo_props(const svn_string_t ** output,const svn_string_t * from_prop_val,const svn_string_t * working_prop_val,const svn_string_t * to_prop_val,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
127 {
128 svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
129 svn_string_t *mergeinfo_string;
130
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));
140
141 /* Apply the combined deltas to the base. */
142 SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
143 scratch_pool));
144 SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
145 scratch_pool, scratch_pool));
146
147 SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
148 TRUE, scratch_pool, scratch_pool));
149
150 SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
151 result_pool));
152 *output = mergeinfo_string;
153 return SVN_NO_ERROR;
154 }
155
156
157 svn_error_t *
svn_wc_merge_props3(svn_wc_notify_state_t * state,svn_wc_context_t * wc_ctx,const char * local_abspath,const svn_wc_conflict_version_t * left_version,const svn_wc_conflict_version_t * right_version,apr_hash_t * baseprops,const apr_array_header_t * propchanges,svn_boolean_t dry_run,svn_wc_conflict_resolver_func2_t conflict_func,void * conflict_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)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,
169 void *cancel_baton,
170 apr_pool_t *scratch_pool)
171 {
172 int i;
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;
184
185 /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
186 may be NULL. */
187
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,
192 db, local_abspath,
193 scratch_pool, scratch_pool));
194
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)
199 {
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));
204 }
205 else if (status != svn_wc__db_status_normal
206 && status != svn_wc__db_status_added
207 && status != svn_wc__db_status_incomplete)
208 {
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));
213 }
214 else if (conflicted)
215 {
216 svn_boolean_t text_conflicted;
217 svn_boolean_t prop_conflicted;
218 svn_boolean_t tree_conflicted;
219
220 SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
221 &prop_conflicted,
222 &tree_conflicted,
223 db, local_abspath,
224 scratch_pool));
225
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)
229 {
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,
234 scratch_pool));
235 }
236 /* else: Conflict was resolved by removing markers */
237 }
238
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--; )
243 {
244 const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
245
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 "
249 "into '%s'."),
250 change->name,
251 svn_dirent_local_style(local_abspath,
252 scratch_pool));
253 }
254
255 if (had_props)
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);
260
261 if (props_mod)
262 SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
263 scratch_pool, scratch_pool));
264 else
265 actual_props = pristine_props;
266
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,
270 &new_actual_props,
271 db, local_abspath,
272 baseprops /* server_baseprops */,
273 pristine_props,
274 actual_props,
275 propchanges,
276 scratch_pool, scratch_pool));
277
278 if (dry_run)
279 {
280 return SVN_NO_ERROR;
281 }
282
283 {
284 const char *dir_abspath;
285
286 if (kind == svn_node_dir)
287 dir_abspath = local_abspath;
288 else
289 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
290
291 /* Verify that we're holding this directory's write lock. */
292 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
293 }
294
295 if (conflict_skel)
296 {
297 svn_skel_t *work_item;
298 SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
299 left_version,
300 right_version,
301 scratch_pool,
302 scratch_pool));
303
304 SVN_ERR(svn_wc__conflict_create_markers(&work_item,
305 db, local_abspath,
306 conflict_skel,
307 scratch_pool, scratch_pool));
308
309 work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
310 }
311
312 /* After a (not-dry-run) merge, we ALWAYS have props to save. */
313 SVN_ERR_ASSERT(new_actual_props != NULL);
314
315 SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
316 svn_wc__has_magic_property(propchanges),
317 conflict_skel,
318 work_items,
319 scratch_pool));
320
321 if (work_items != NULL)
322 SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
323 scratch_pool));
324
325 /* If there is a conflict, try to resolve it. */
326 if (conflict_skel && conflict_func)
327 {
328 svn_boolean_t prop_conflicted;
329
330 SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, kind,
331 conflict_skel,
332 NULL /* merge_options */,
333 conflict_func, conflict_baton,
334 cancel_func, cancel_baton,
335 scratch_pool));
336
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;
343 }
344
345 return SVN_NO_ERROR;
346 }
347
348
349 /* Generate a message to describe the property conflict among these four
350 values.
351
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 *
generate_conflict_message(const char * propname,const svn_string_t * original,const svn_string_t * mine,const svn_string_t * incoming,const svn_string_t * incoming_base,apr_pool_t * result_pool)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)
362 {
363 if (incoming_base == NULL)
364 {
365 /* Attempting to add the value INCOMING. */
366 SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
367
368 if (mine)
369 {
370 /* To have a conflict, these must be different. */
371 SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
372
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"),
379 propname);
380 }
381
382 /* To have a conflict, we must have an ORIGINAL which has been
383 locally-deleted. */
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 "
388 "deleted.\n"),
389 propname);
390 }
391
392 if (incoming == NULL)
393 {
394 /* Attempting to delete the value INCOMING_BASE. */
395 SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
396
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 "
402 "added.\n"),
403 propname);
404
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);
409
410 if (svn_string_compare(original, incoming_base))
411 {
412 if (mine)
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 "
418 "modified.\n"),
419 propname);
420 }
421 else if (mine == NULL)
422 {
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 "
429 "value.\n"),
430 propname);
431 }
432
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));
436
437 return svn_stringbuf_createf(result_pool,
438 _("Trying to delete property '%s'\n"
439 "but the local property value is "
440 "different.\n"),
441 propname);
442 }
443
444 /* Attempting to change the property from INCOMING_BASE to INCOMING. */
445
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));
450
451 if (original && mine && svn_string_compare(original, mine))
452 {
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"),
460 propname);
461 }
462
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"),
468 propname);
469
470 if (original)
471 return svn_stringbuf_createf(result_pool,
472 _("Trying to change property '%s'\nbut "
473 "the property has been locally deleted.\n"),
474 propname);
475
476 if (mine)
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"),
481 propname);
482
483 return svn_stringbuf_createf(result_pool,
484 _("Trying to change property '%s'\nbut "
485 "the property does not exist locally.\n"),
486 propname);
487 }
488
489
490 /* SKEL will be one of:
491
492 ()
493 (VALUE)
494
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 *
maybe_prop_value(const svn_skel_t * skel,apr_pool_t * result_pool)498 maybe_prop_value(const svn_skel_t *skel,
499 apr_pool_t *result_pool)
500 {
501 if (skel->children == NULL)
502 return NULL;
503
504 return svn_string_ncreate(skel->children->data,
505 skel->children->len,
506 result_pool);
507 }
508
509
510 /* Create a property rejection description for the specified property.
511 The result will be allocated in RESULT_POOL. */
512 static svn_error_t *
prop_conflict_new(const svn_string_t ** conflict_desc,const char * propname,const svn_string_t * original,const svn_string_t * mine,const svn_string_t * incoming,const svn_string_t * incoming_base,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_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,
520 void *cancel_baton,
521 apr_pool_t *result_pool,
522 apr_pool_t *scratch_pool)
523 {
524 svn_diff_t *diff;
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;
530
531 buf = generate_conflict_message(propname, original, mine, incoming,
532 incoming_base, scratch_pool);
533
534 /* Convert deleted or not-yet-added values to empty-string values, for the
535 purposes of diff generation and binary detection. */
536 if (mine == NULL)
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);
542
543 /* How we render the conflict:
544
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,
547 called:
548
549 <<< - original
550 ||| - modified (or "older")
551 === - latest (or "theirs")
552 >>>
553
554 We fill those parts as follows:
555
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
562
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.
565
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.
570
571 Yes, it's confusing. */
572
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,
576 incoming_base->len);
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);
579
580 if (!(incoming_base_is_binary || mine_is_binary || incoming_is_binary))
581 {
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))
591 {
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,
600 scratch_pool),
601 scratch_pool);
602 svn_string_t *mine_ascii =
603 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
604 scratch_pool),
605 scratch_pool);
606 svn_string_t *incoming_ascii =
607 svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
608 scratch_pool),
609 scratch_pool);
610
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,
615 incoming_base_ascii,
616 mine_ascii,
617 incoming_ascii,
618 incoming_base_marker, mine_marker,
619 incoming_marker, separator,
620 style,
621 cancel_func, cancel_baton,
622 scratch_pool));
623 SVN_ERR(svn_stream_close(stream));
624
625 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
626 return SVN_NO_ERROR;
627 }
628 }
629
630 /* If we could not print a conflict diff just print full values . */
631 if (mine->len > 0)
632 {
633 svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
634 if (mine_is_binary)
635 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
636 "binary data\n"));
637 else
638 svn_stringbuf_appendbytes(buf, mine->data, mine->len);
639 svn_stringbuf_appendcstr(buf, "\n");
640 }
641
642 if (incoming->len > 0)
643 {
644 svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
645 if (incoming_is_binary)
646 svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
647 "binary data\n"));
648 else
649 svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
650 svn_stringbuf_appendcstr(buf, "\n");
651 }
652
653 *conflict_desc = svn_string_create_from_buf(buf, result_pool);
654 return SVN_NO_ERROR;
655 }
656
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.
661
662 Note: SKEL is a single property conflict of the form:
663
664 ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
665
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 */
668 static svn_error_t *
prop_conflict_from_skel(const svn_string_t ** conflict_desc,const svn_skel_t * skel,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)669 prop_conflict_from_skel(const svn_string_t **conflict_desc,
670 const svn_skel_t *skel,
671 svn_cancel_func_t cancel_func,
672 void *cancel_baton,
673 apr_pool_t *result_pool,
674 apr_pool_t *scratch_pool)
675 {
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;
681
682 /* Navigate to the property name. */
683 skel = skel->children->next;
684
685 /* We need to copy these into SCRATCH_POOL in order to nul-terminate
686 the values. */
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);
692
693 return svn_error_trace(prop_conflict_new(conflict_desc,
694 propname,
695 original, mine,
696 incoming, incoming_base,
697 cancel_func, cancel_baton,
698 result_pool, scratch_pool));
699 }
700
701 /* Create a property conflict file at PREJFILE based on the property
702 conflicts in CONFLICT_SKEL. */
703 svn_error_t *
svn_wc__create_prejfile(const char ** tmp_prejfile_abspath,svn_wc__db_t * db,const char * local_abspath,const svn_skel_t * prop_conflict_data,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)704 svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
705 svn_wc__db_t *db,
706 const char *local_abspath,
707 const svn_skel_t *prop_conflict_data,
708 svn_cancel_func_t cancel_func,
709 void *cancel_baton,
710 apr_pool_t *result_pool,
711 apr_pool_t *scratch_pool)
712 {
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;
718
719 SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
720 db, local_abspath,
721 iterpool, iterpool));
722
723 SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
724 tempdir_abspath, svn_io_file_del_none,
725 scratch_pool, iterpool));
726
727 if (prop_conflict_data)
728 {
729 for (scan = prop_conflict_data->children->next;
730 scan != NULL; scan = scan->next)
731 {
732 const svn_string_t *conflict_desc;
733
734 svn_pool_clear(iterpool);
735
736 SVN_ERR(prop_conflict_from_skel(&conflict_desc, scan,
737 cancel_func, cancel_baton,
738 iterpool, iterpool));
739
740 SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
741 }
742 }
743 else
744 {
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;
753
754 SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL,
755 db, local_abspath,
756 scratch_pool, scratch_pool));
757
758 SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, NULL,
759 db, local_abspath,
760 conflicts,
761 scratch_pool, scratch_pool));
762
763 SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL,
764 &mine_props,
765 &their_original_props,
766 &their_props,
767 &conflicted_props,
768 db, local_abspath,
769 conflicts,
770 scratch_pool,
771 scratch_pool));
772
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));
776 else
777 old_props = their_original_props;
778
779 /* ### TODO: Sort conflicts? */
780 for (hi = apr_hash_first(scratch_pool, conflicted_props);
781 hi;
782 hi = apr_hash_next(hi))
783 {
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;
790
791 svn_pool_clear(iterpool);
792
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)
796 : NULL;
797 their_original_value = their_original_props
798 ? svn_hash_gets(their_original_props, propname)
799 : NULL;
800
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));
806
807 SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
808 }
809 }
810
811 SVN_ERR(svn_stream_close(stream));
812
813 svn_pool_destroy(iterpool);
814
815 *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
816 return SVN_NO_ERROR;
817 }
818
819
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):
823 *
824 * - unknown, unchanged, inapplicable
825 * - changed
826 * - merged
827 * - missing
828 * - obstructed
829 * - conflicted
830 *
831 */
832 static void
set_prop_merge_state(svn_wc_notify_state_t * state,svn_wc_notify_state_t new_value)833 set_prop_merge_state(svn_wc_notify_state_t *state,
834 svn_wc_notify_state_t new_value)
835 {
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;
845
846 if (! state)
847 return;
848
849 /* Find *STATE in our ordering */
850 for (i = 0; i < sizeof(ordering); i++)
851 {
852 if (*state == ordering[i])
853 {
854 state_pos = i;
855 break;
856 }
857 }
858
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.
863 */
864
865 for (i = 0; i <= state_pos; i++)
866 {
867 if (new_value == ordering[i])
868 return;
869 }
870
871 *state = new_value;
872 }
873
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
876 * PRISTINE_VAL.
877 *
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
881 */
882 static svn_error_t *
apply_single_prop_add(const svn_string_t ** result_val,svn_boolean_t * conflict_remains,svn_boolean_t * did_merge,const char * propname,const svn_string_t * pristine_val,const svn_string_t * new_val,const svn_string_t * working_val,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
892
893 {
894 *conflict_remains = FALSE;
895
896 if (working_val)
897 {
898 /* the property already exists in actual_props... */
899
900 if (svn_string_compare(working_val, new_val))
901 /* The value we want is already there, so it's a merge. */
902 *did_merge = TRUE;
903
904 else
905 {
906 svn_boolean_t merged_prop = FALSE;
907
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)
911 {
912 const svn_string_t *merged_val;
913 svn_error_t *err = combine_mergeinfo_props(&merged_val,
914 working_val,
915 new_val,
916 result_pool,
917 scratch_pool);
918
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. */
922 if (err)
923 {
924 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
925 svn_error_clear(err);
926 else
927 return svn_error_trace(err);
928 }
929 else
930 {
931 merged_prop = TRUE;
932 *result_val = merged_val;
933 *did_merge = TRUE;
934 }
935 }
936
937 if (!merged_prop)
938 *conflict_remains = TRUE;
939 }
940 }
941 else if (pristine_val)
942 *conflict_remains = TRUE;
943 else /* property doesn't yet exist in actual_props... */
944 /* so just set it */
945 *result_val = new_val;
946
947 return SVN_NO_ERROR;
948 }
949
950
951 /* Apply the deletion of a property to the existing
952 * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
953 *
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
957 */
958 static svn_error_t *
apply_single_prop_delete(const svn_string_t ** result_val,svn_boolean_t * conflict_remains,svn_boolean_t * did_merge,const svn_string_t * base_val,const svn_string_t * old_val,const svn_string_t * working_val)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)
965 {
966 *conflict_remains = FALSE;
967
968 if (! base_val)
969 {
970 if (working_val
971 && !svn_string_compare(working_val, old_val))
972 {
973 /* We are trying to delete a locally-added prop. */
974 *conflict_remains = TRUE;
975 }
976 else
977 {
978 *result_val = NULL;
979 if (old_val)
980 /* This is a merge, merging a delete into non-existent
981 property or a local addition of same prop value. */
982 *did_merge = TRUE;
983 }
984 }
985
986 else if (svn_string_compare(base_val, old_val))
987 {
988 if (working_val)
989 {
990 if (svn_string_compare(working_val, old_val))
991 /* they have the same values, so it's an update */
992 *result_val = NULL;
993 else
994 *conflict_remains = TRUE;
995 }
996 else
997 /* The property is locally deleted from the same value, so it's
998 a merge */
999 *did_merge = TRUE;
1000 }
1001
1002 else
1003 *conflict_remains = TRUE;
1004
1005 return SVN_NO_ERROR;
1006 }
1007
1008
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
1015 the remainder. */
1016 static svn_error_t *
apply_single_mergeinfo_prop_change(const svn_string_t ** result_val,svn_boolean_t * conflict_remains,svn_boolean_t * did_merge,const svn_string_t * base_val,const svn_string_t * old_val,const svn_string_t * new_val,const svn_string_t * working_val,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
1026 {
1027 if ((working_val && ! base_val)
1028 || (! working_val && base_val)
1029 || (working_val && base_val
1030 && !svn_string_compare(working_val, base_val)))
1031 {
1032 /* Locally changed property */
1033 if (working_val)
1034 {
1035 if (svn_string_compare(working_val, new_val))
1036 /* The new value equals the changed value: a no-op merge */
1037 *did_merge = TRUE;
1038 else
1039 {
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,
1045 working_val,
1046 new_val,
1047 result_pool,
1048 scratch_pool));
1049 *result_val = new_val;
1050 *did_merge = TRUE;
1051 }
1052 }
1053 else
1054 {
1055 /* There is a base_val but no working_val */
1056 *conflict_remains = TRUE;
1057 }
1058 }
1059
1060 else if (! working_val) /* means !working_val && !base_val due
1061 to conditions above: no prop at all */
1062 {
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;
1068
1069 SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
1070 &added_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;
1075 }
1076
1077 else /* means working && base && svn_string_compare(working, base) */
1078 {
1079 if (svn_string_compare(old_val, base_val))
1080 *result_val = new_val;
1081 else
1082 {
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,
1088 working_val,
1089 new_val, result_pool,
1090 scratch_pool));
1091 *result_val = new_val;
1092 *did_merge = TRUE;
1093 }
1094 }
1095
1096 return SVN_NO_ERROR;
1097 }
1098
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 *
apply_single_generic_prop_change(const svn_string_t ** result_val,svn_boolean_t * conflict_remains,svn_boolean_t * did_merge,const svn_string_t * old_val,const svn_string_t * new_val,const svn_string_t * working_val)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)
1112 {
1113 SVN_ERR_ASSERT(old_val != NULL);
1114
1115 /* If working_val is the same as new_val already then there is
1116 * nothing to do */
1117 if (working_val && new_val
1118 && svn_string_compare(working_val, new_val))
1119 {
1120 /* All values identical is a trivial, non-notifiable merge */
1121 if (! old_val || ! svn_string_compare(old_val, new_val))
1122 *did_merge = TRUE;
1123 }
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))
1127 {
1128 /* A trivial update: change it to new_val. */
1129 *result_val = new_val;
1130 }
1131 else
1132 {
1133 /* Merge the change. */
1134 *conflict_remains = TRUE;
1135 }
1136
1137 return SVN_NO_ERROR;
1138 }
1139
1140 /* Change the property with name PROPNAME, setting *RESULT_VAL,
1141 * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
1142 *
1143 * BASE_VAL contains the working copy base property value. (May be null.)
1144 *
1145 * OLD_VAL contains the value of the property the server
1146 * thinks it's overwriting. (Not null.)
1147 *
1148 * NEW_VAL contains the value to be set. (Not null.)
1149 *
1150 * WORKING_VAL contains the working copy actual value. (May be null.)
1151 */
1152 static svn_error_t *
apply_single_prop_change(const svn_string_t ** result_val,svn_boolean_t * conflict_remains,svn_boolean_t * did_merge,const char * propname,const svn_string_t * base_val,const svn_string_t * old_val,const svn_string_t * new_val,const svn_string_t * working_val,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
1163 {
1164 svn_boolean_t merged_prop = FALSE;
1165
1166 *conflict_remains = FALSE;
1167
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. */
1172
1173 /* Decide how to merge, based on whether we know anything special about
1174 the property. */
1175 if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1176 {
1177 /* We know how to merge any mergeinfo property change...
1178
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
1182 it instead. */
1183 svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
1184 conflict_remains,
1185 did_merge,
1186 base_val,
1187 old_val,
1188 new_val,
1189 working_val,
1190 result_pool,
1191 scratch_pool);
1192 if (err)
1193 {
1194 if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1195 svn_error_clear(err);
1196 else
1197 return svn_error_trace(err);
1198 }
1199 else
1200 {
1201 merged_prop = TRUE;
1202 }
1203 }
1204
1205 if (!merged_prop)
1206 {
1207 /* The standard method: perform a simple update automatically, but
1208 pass any other kind of merge to maybe_generate_propconflict(). */
1209
1210 SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
1211 did_merge,
1212 old_val, new_val, working_val));
1213 }
1214
1215 return SVN_NO_ERROR;
1216 }
1217
1218
1219 svn_error_t *
svn_wc__merge_props(svn_skel_t ** conflict_skel,svn_wc_notify_state_t * state,apr_hash_t ** new_actual_props,svn_wc__db_t * db,const char * local_abspath,apr_hash_t * server_baseprops,apr_hash_t * pristine_props,apr_hash_t * actual_props,const apr_array_header_t * propchanges,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1220 svn_wc__merge_props(svn_skel_t **conflict_skel,
1221 svn_wc_notify_state_t *state,
1222 apr_hash_t **new_actual_props,
1223 svn_wc__db_t *db,
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)
1231 {
1232 apr_pool_t *iterpool;
1233 int i;
1234 apr_hash_t *conflict_props = NULL;
1235 apr_hash_t *their_props;
1236
1237 SVN_ERR_ASSERT(pristine_props != NULL);
1238 SVN_ERR_ASSERT(actual_props != NULL);
1239
1240 *new_actual_props = apr_hash_copy(result_pool, actual_props);
1241
1242 if (!server_baseprops)
1243 server_baseprops = pristine_props;
1244
1245 their_props = apr_hash_copy(scratch_pool, server_baseprops);
1246
1247 if (state)
1248 {
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;
1254 }
1255
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++)
1259 {
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;
1274
1275 svn_pool_clear(iterpool);
1276
1277 to_val = svn_string_dup(to_val, result_pool);
1278
1279 svn_hash_sets(their_props, propname, to_val);
1280
1281
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);
1286
1287 result_val = working_val;
1288
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));
1294
1295 else if (! to_val) /* delete an existing property */
1296 SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
1297 &did_merge,
1298 base_val, from_val, working_val));
1299
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));
1305
1306 if (result_val != working_val)
1307 svn_hash_sets(*new_actual_props, propname, result_val);
1308 if (did_merge)
1309 set_prop_merge_state(state, svn_wc_notify_state_merged);
1310
1311 /* merging logic complete, now we need to possibly log conflict
1312 data to tmpfiles. */
1313
1314 if (conflict_remains)
1315 {
1316 set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1317
1318 if (!conflict_props)
1319 conflict_props = apr_hash_make(scratch_pool);
1320
1321 svn_hash_sets(conflict_props, propname, "");
1322 }
1323
1324 } /* foreach propchange ... */
1325 svn_pool_destroy(iterpool);
1326
1327 /* Finished applying all incoming propchanges to our hashes! */
1328
1329 if (conflict_props != NULL)
1330 {
1331 /* Ok, we got some conflict. Lets store all the property knowledge we
1332 have for resolving later */
1333
1334 if (!*conflict_skel)
1335 *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1336
1337 SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
1338 db, local_abspath,
1339 NULL /* reject_path */,
1340 actual_props,
1341 server_baseprops,
1342 their_props,
1343 conflict_props,
1344 result_pool,
1345 scratch_pool));
1346 }
1347
1348 return SVN_NO_ERROR;
1349 }
1350
1351
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 *
wcprop_set(svn_wc__db_t * db,const char * local_abspath,const char * name,const svn_string_t * value,apr_pool_t * scratch_pool)1355 wcprop_set(svn_wc__db_t *db,
1356 const char *local_abspath,
1357 const char *name,
1358 const svn_string_t *value,
1359 apr_pool_t *scratch_pool)
1360 {
1361 apr_hash_t *prophash;
1362
1363 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1364
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. */
1370
1371 SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1372 scratch_pool, scratch_pool));
1373
1374 if (prophash == NULL)
1375 prophash = apr_hash_make(scratch_pool);
1376
1377 svn_hash_sets(prophash, name, value);
1378 return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
1379 prophash,
1380 scratch_pool));
1381 }
1382
1383
1384 svn_error_t *
svn_wc__get_actual_props(apr_hash_t ** props,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1385 svn_wc__get_actual_props(apr_hash_t **props,
1386 svn_wc__db_t *db,
1387 const char *local_abspath,
1388 apr_pool_t *result_pool,
1389 apr_pool_t *scratch_pool)
1390 {
1391 SVN_ERR_ASSERT(props != NULL);
1392 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1393
1394 /* ### perform some state checking. for example, locally-deleted nodes
1395 ### should not have any ACTUAL props. */
1396
1397 return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
1398 result_pool, scratch_pool));
1399 }
1400
1401
1402 svn_error_t *
svn_wc_prop_list2(apr_hash_t ** props,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * 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)
1408 {
1409 return svn_error_trace(svn_wc__get_actual_props(props,
1410 wc_ctx->db,
1411 local_abspath,
1412 result_pool,
1413 scratch_pool));
1414 }
1415
1416 struct propname_filter_baton_t {
1417 svn_wc__proplist_receiver_t receiver_func;
1418 void *receiver_baton;
1419 const char *propname;
1420 };
1421
1422 static svn_error_t *
propname_filter_receiver(void * baton,const char * local_abspath,apr_hash_t * props,apr_pool_t * scratch_pool)1423 propname_filter_receiver(void *baton,
1424 const char *local_abspath,
1425 apr_hash_t *props,
1426 apr_pool_t *scratch_pool)
1427 {
1428 struct propname_filter_baton_t *pfb = baton;
1429 const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
1430
1431 if (propval)
1432 {
1433 props = apr_hash_make(scratch_pool);
1434 svn_hash_sets(props, pfb->propname, propval);
1435
1436 SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
1437 scratch_pool));
1438 }
1439
1440 return SVN_NO_ERROR;
1441 }
1442
1443 svn_error_t *
svn_wc__prop_list_recursive(svn_wc_context_t * wc_ctx,const char * local_abspath,const char * propname,svn_depth_t depth,svn_boolean_t pristine,const apr_array_header_t * changelists,svn_wc__proplist_receiver_t receiver_func,void * receiver_baton,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)1444 svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
1445 const char *local_abspath,
1446 const char *propname,
1447 svn_depth_t depth,
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,
1453 void *cancel_baton,
1454 apr_pool_t *scratch_pool)
1455 {
1456 svn_wc__proplist_receiver_t receiver = receiver_func;
1457 void *baton = receiver_baton;
1458 struct propname_filter_baton_t pfb;
1459
1460 pfb.receiver_func = receiver_func;
1461 pfb.receiver_baton = receiver_baton;
1462 pfb.propname = propname;
1463
1464 SVN_ERR_ASSERT(receiver_func);
1465
1466 if (propname)
1467 {
1468 baton = &pfb;
1469 receiver = propname_filter_receiver;
1470 }
1471
1472 switch (depth)
1473 {
1474 case svn_depth_empty:
1475 {
1476 apr_hash_t *props;
1477 apr_hash_t *changelist_hash = NULL;
1478
1479 if (changelists && changelists->nelts)
1480 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1481 changelists, scratch_pool));
1482
1483 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1484 changelist_hash, scratch_pool))
1485 break;
1486
1487 if (pristine)
1488 SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
1489 local_abspath,
1490 scratch_pool, scratch_pool));
1491 else
1492 SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
1493 scratch_pool, scratch_pool));
1494
1495 if (props && apr_hash_count(props) > 0)
1496 SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
1497 }
1498 break;
1499 case svn_depth_files:
1500 case svn_depth_immediates:
1501 case svn_depth_infinity:
1502 {
1503 SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
1504 depth, pristine,
1505 changelists, receiver, baton,
1506 cancel_func, cancel_baton,
1507 scratch_pool));
1508 }
1509 break;
1510 default:
1511 SVN_ERR_MALFUNCTION();
1512 }
1513
1514 return SVN_NO_ERROR;
1515 }
1516
1517 svn_error_t *
svn_wc__prop_retrieve_recursive(apr_hash_t ** values,svn_wc_context_t * wc_ctx,const char * local_abspath,const char * propname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
1524 {
1525 return svn_error_trace(
1526 svn_wc__db_prop_retrieve_recursive(values,
1527 wc_ctx->db,
1528 local_abspath,
1529 propname,
1530 result_pool, scratch_pool));
1531 }
1532
1533 svn_error_t *
svn_wc_get_pristine_props(apr_hash_t ** props,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * 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)
1539 {
1540 svn_error_t *err;
1541
1542 SVN_ERR_ASSERT(props != NULL);
1543 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1544
1545 /* Certain node stats do not have properties defined on them. Check the
1546 state, and return NULL for these situations. */
1547
1548 err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
1549 result_pool, scratch_pool);
1550
1551 if (err)
1552 {
1553 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1554 return svn_error_trace(err);
1555
1556 svn_error_clear(err);
1557
1558 /* Documented behavior is to set *PROPS to NULL */
1559 *props = NULL;
1560 }
1561
1562 return SVN_NO_ERROR;
1563 }
1564
1565 svn_error_t *
svn_wc_prop_get2(const svn_string_t ** value,svn_wc_context_t * wc_ctx,const char * local_abspath,const char * name,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1566 svn_wc_prop_get2(const svn_string_t **value,
1567 svn_wc_context_t *wc_ctx,
1568 const char *local_abspath,
1569 const char *name,
1570 apr_pool_t *result_pool,
1571 apr_pool_t *scratch_pool)
1572 {
1573 enum svn_prop_kind kind = svn_property_kind2(name);
1574 svn_error_t *err;
1575
1576 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1577
1578 if (kind == svn_prop_entry_kind)
1579 {
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);
1583 }
1584
1585 err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
1586 result_pool, scratch_pool);
1587
1588 if (err)
1589 {
1590 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1591 return svn_error_trace(err);
1592
1593 svn_error_clear(err);
1594 /* Documented behavior is to set *VALUE to NULL */
1595 *value = NULL;
1596 }
1597
1598 return SVN_NO_ERROR;
1599 }
1600
1601 svn_error_t *
svn_wc__internal_propget(const svn_string_t ** value,svn_wc__db_t * db,const char * local_abspath,const char * name,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1602 svn_wc__internal_propget(const svn_string_t **value,
1603 svn_wc__db_t *db,
1604 const char *local_abspath,
1605 const char *name,
1606 apr_pool_t *result_pool,
1607 apr_pool_t *scratch_pool)
1608 {
1609 apr_hash_t *prophash = NULL;
1610 enum svn_prop_kind kind = svn_property_kind2(name);
1611
1612 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1613 SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
1614
1615 if (kind == svn_prop_wc_kind)
1616 {
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"));
1620 }
1621 else
1622 {
1623 /* regular prop */
1624 SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
1625 result_pool, scratch_pool),
1626 _("Failed to load properties"));
1627 }
1628
1629 if (prophash)
1630 *value = svn_hash_gets(prophash, name);
1631 else
1632 *value = NULL;
1633
1634 return SVN_NO_ERROR;
1635 }
1636
1637
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.
1641
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.
1646 */
1647 static svn_error_t *
validate_prop_against_node_kind(const char * name,const char * path,svn_node_kind_t node_kind,apr_pool_t * pool)1648 validate_prop_against_node_kind(const char *name,
1649 const char *path,
1650 svn_node_kind_t node_kind,
1651 apr_pool_t *pool)
1652 {
1653 const char *path_display
1654 = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1655
1656 switch (node_kind)
1657 {
1658 case svn_node_dir:
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);
1664 break;
1665 case svn_node_file:
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')"),
1670 name,
1671 path_display);
1672 break;
1673 default:
1674 return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1675 _("'%s' is not a file or directory"),
1676 path_display);
1677 }
1678
1679 return SVN_NO_ERROR;
1680 }
1681
1682
1683 struct getter_baton {
1684 const svn_string_t *mime_type;
1685 const char *local_abspath;
1686 };
1687
1688
1689 /* Provide the MIME_TYPE and/or push the content to STREAM for the file
1690 * referenced by (getter_baton *) BATON.
1691 *
1692 * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
1693 static svn_error_t *
get_file_for_validation(const svn_string_t ** mime_type,svn_stream_t * stream,void * baton,apr_pool_t * pool)1694 get_file_for_validation(const svn_string_t **mime_type,
1695 svn_stream_t *stream,
1696 void *baton,
1697 apr_pool_t *pool)
1698 {
1699 struct getter_baton *gb = baton;
1700
1701 if (mime_type)
1702 *mime_type = gb->mime_type;
1703
1704 if (stream)
1705 {
1706 svn_stream_t *read_stream;
1707
1708 /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
1709 SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
1710 pool, pool));
1711 SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
1712 NULL, NULL, pool));
1713 }
1714
1715 return SVN_NO_ERROR;
1716 }
1717
1718
1719 /* Validate that a file has a 'non-binary' MIME type and contains
1720 * self-consistent line endings. If not, then return an error.
1721 *
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
1725 * the file content.
1726 *
1727 * Use PATH (a local path or a URL) only for error messages.
1728 */
1729 static svn_error_t *
validate_eol_prop_against_file(const char * path,svn_wc_canonicalize_svn_prop_get_file_t getter,void * getter_baton,apr_pool_t * pool)1730 validate_eol_prop_against_file(const char *path,
1731 svn_wc_canonicalize_svn_prop_get_file_t getter,
1732 void *getter_baton,
1733 apr_pool_t *pool)
1734 {
1735 svn_stream_t *translating_stream;
1736 svn_error_t *err;
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);
1740
1741 /* First just ask the "getter" for the MIME type. */
1742 SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
1743
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);
1751
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,
1759 pool);
1760
1761 err = getter(NULL, translating_stream, getter_baton, pool);
1762
1763 err = svn_error_compose_create(err, svn_stream_close(translating_stream));
1764
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"),
1768 path_display);
1769
1770 return svn_error_trace(err);
1771 }
1772
1773 static svn_error_t *
do_propset(svn_wc__db_t * db,const char * local_abspath,svn_node_kind_t kind,const char * name,const svn_string_t * value,svn_boolean_t skip_checks,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)1774 do_propset(svn_wc__db_t *db,
1775 const char *local_abspath,
1776 svn_node_kind_t kind,
1777 const char *name,
1778 const svn_string_t *value,
1779 svn_boolean_t skip_checks,
1780 svn_wc_notify_func2_t notify_func,
1781 void *notify_baton,
1782 apr_pool_t *scratch_pool)
1783 {
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;
1788
1789 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1790
1791 SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
1792 scratch_pool, scratch_pool),
1793 _("Failed to load current properties"));
1794
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
1799 the first place. */
1800 if (value && svn_prop_is_svn_prop(name))
1801 {
1802 const svn_string_t *new_value;
1803 struct getter_baton gb;
1804
1805 gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
1806 gb.local_abspath = local_abspath;
1807
1808 SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
1809 local_abspath, kind,
1810 skip_checks,
1811 get_file_for_validation, &gb,
1812 scratch_pool));
1813 value = new_value;
1814 }
1815
1816 if (kind == svn_node_file
1817 && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
1818 || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
1819 {
1820 SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
1821 scratch_pool, scratch_pool));
1822 }
1823
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.
1827 *
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
1830 * from the old one.
1831 */
1832 if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
1833 {
1834 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
1835 apr_hash_t *old_keywords, *new_keywords;
1836
1837 if (old_value)
1838 SVN_ERR(svn_wc__expand_keywords(&old_keywords,
1839 db, local_abspath, NULL,
1840 old_value->data, TRUE,
1841 scratch_pool, scratch_pool));
1842 else
1843 old_keywords = apr_hash_make(scratch_pool);
1844
1845 if (value)
1846 SVN_ERR(svn_wc__expand_keywords(&new_keywords,
1847 db, local_abspath, NULL,
1848 value->data, TRUE,
1849 scratch_pool, scratch_pool));
1850 else
1851 new_keywords = apr_hash_make(scratch_pool);
1852
1853 if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
1854 scratch_pool))
1855 {
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.
1859
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.
1864
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;
1870 }
1871 }
1872 else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
1873 {
1874 svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
1875
1876 if (((value == NULL) != (old_value == NULL))
1877 || (value && ! svn_string_compare(value, old_value)))
1878 {
1879 clear_recorded_info = TRUE;
1880 }
1881 }
1882
1883 /* Find out what type of property change we are doing: add, modify, or
1884 delete. */
1885 if (svn_hash_gets(prophash, name) == NULL)
1886 {
1887 if (value == NULL)
1888 /* Deleting a non-existent property. */
1889 notify_action = svn_wc_notify_property_deleted_nonexistent;
1890 else
1891 /* Adding a property. */
1892 notify_action = svn_wc_notify_property_added;
1893 }
1894 else
1895 {
1896 if (value == NULL)
1897 /* Deleting the property. */
1898 notify_action = svn_wc_notify_property_deleted;
1899 else
1900 /* Modifying property. */
1901 notify_action = svn_wc_notify_property_modified;
1902 }
1903
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);
1907
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,
1911 scratch_pool));
1912
1913 /* Run our workqueue item for sync'ing flags with props. */
1914 if (work_item)
1915 SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
1916
1917 if (notify_func)
1918 {
1919 svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
1920 notify_action,
1921 scratch_pool);
1922 notify->prop_name = name;
1923 notify->kind = kind;
1924
1925 (*notify_func)(notify_baton, notify, scratch_pool);
1926 }
1927
1928 return SVN_NO_ERROR;
1929 }
1930
1931 /* A baton for propset_walk_cb. */
1932 struct propset_walk_baton
1933 {
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;
1939 void *notify_baton;
1940 };
1941
1942 /* An node-walk callback for svn_wc_prop_set4().
1943 *
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 *".
1947 */
1948 static svn_error_t *
propset_walk_cb(const char * local_abspath,svn_node_kind_t kind,void * walk_baton,apr_pool_t * scratch_pool)1949 propset_walk_cb(const char *local_abspath,
1950 svn_node_kind_t kind,
1951 void *walk_baton,
1952 apr_pool_t *scratch_pool)
1953 {
1954 struct propset_walk_baton *wb = walk_baton;
1955 svn_error_t *err;
1956
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))
1961 {
1962 svn_error_clear(err);
1963 err = SVN_NO_ERROR;
1964 }
1965
1966 return svn_error_trace(err);
1967 }
1968
1969 svn_error_t *
svn_wc_prop_set4(svn_wc_context_t * wc_ctx,const char * local_abspath,const char * name,const svn_string_t * value,svn_depth_t depth,svn_boolean_t skip_checks,const apr_array_header_t * changelist_filter,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)1970 svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
1971 const char *local_abspath,
1972 const char *name,
1973 const svn_string_t *value,
1974 svn_depth_t depth,
1975 svn_boolean_t skip_checks,
1976 const apr_array_header_t *changelist_filter,
1977 svn_cancel_func_t cancel_func,
1978 void *cancel_baton,
1979 svn_wc_notify_func2_t notify_func,
1980 void *notify_baton,
1981 apr_pool_t *scratch_pool)
1982 {
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;
1987
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);
1992
1993 /* Check to see if we're setting the dav cache. */
1994 if (prop_kind == svn_prop_wc_kind)
1995 {
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));
1999 }
2000
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));
2007
2008 if (status != svn_wc__db_status_normal
2009 && status != svn_wc__db_status_added
2010 && status != svn_wc__db_status_incomplete)
2011 {
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,
2016 scratch_pool));
2017 }
2018
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
2023 the node walker).
2024
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 */
2028 {
2029 const char *dir_abspath;
2030
2031 if (kind == svn_node_dir)
2032 dir_abspath = local_abspath;
2033 else
2034 dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
2035
2036 /* Verify that we're holding this directory's write lock. */
2037 SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
2038 }
2039
2040 if (depth == svn_depth_empty || kind != svn_node_dir)
2041 {
2042 apr_hash_t *changelist_hash = NULL;
2043
2044 if (changelist_filter && changelist_filter->nelts)
2045 SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
2046 scratch_pool));
2047
2048 if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
2049 changelist_hash, scratch_pool))
2050 return SVN_NO_ERROR;
2051
2052 SVN_ERR(do_propset(wc_ctx->db, local_abspath,
2053 kind == svn_node_dir
2054 ? svn_node_dir
2055 : svn_node_file,
2056 name, value, skip_checks,
2057 notify_func, notify_baton, scratch_pool));
2058
2059 }
2060 else
2061 {
2062 struct propset_walk_baton wb;
2063
2064 wb.propname = name;
2065 wb.propval = value;
2066 wb.db = wc_ctx->db;
2067 wb.force = skip_checks;
2068 wb.notify_func = notify_func;
2069 wb.notify_baton = notify_baton;
2070
2071 SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
2072 FALSE, changelist_filter,
2073 propset_walk_cb, &wb,
2074 depth,
2075 cancel_func, cancel_baton,
2076 scratch_pool));
2077 }
2078
2079 return SVN_NO_ERROR;
2080 }
2081
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 *
ensure_prop_is_regular_kind(const char * name)2085 ensure_prop_is_regular_kind(const char *name)
2086 {
2087 enum svn_prop_kind prop_kind = svn_property_kind2(name);
2088
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);
2093
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);
2099
2100 return SVN_NO_ERROR;
2101 }
2102
2103 svn_error_t *
svn_wc__canonicalize_props(apr_hash_t ** prepared_props,const char * local_abspath,svn_node_kind_t node_kind,const apr_hash_t * props,svn_boolean_t skip_some_checks,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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)
2111 {
2112 const svn_string_t *mime_type;
2113 struct getter_baton gb;
2114 apr_hash_index_t *hi;
2115
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);
2119
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);
2123 if (mime_type)
2124 {
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);
2130 }
2131
2132 /* Set up the context for canonicalizing the other properties. */
2133 gb.mime_type = mime_type;
2134 gb.local_abspath = local_abspath;
2135
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))
2139 {
2140 const char *name = apr_hash_this_key(hi);
2141 const svn_string_t *value = apr_hash_this_val(hi);
2142
2143 if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
2144 continue;
2145
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);
2152 }
2153
2154 return SVN_NO_ERROR;
2155 }
2156
2157
2158 svn_error_t *
svn_wc_canonicalize_svn_prop(const svn_string_t ** propval_p,const char * propname,const svn_string_t * propval,const char * path,svn_node_kind_t kind,svn_boolean_t skip_some_checks,svn_wc_canonicalize_svn_prop_get_file_t getter,void * getter_baton,apr_pool_t * pool)2159 svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2160 const char *propname,
2161 const svn_string_t *propval,
2162 const char *path,
2163 svn_node_kind_t kind,
2164 svn_boolean_t skip_some_checks,
2165 svn_wc_canonicalize_svn_prop_get_file_t getter,
2166 void *getter_baton,
2167 apr_pool_t *pool)
2168 {
2169 svn_stringbuf_t *new_value = NULL;
2170
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);
2175
2176 SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2177
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))
2180 {
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'"),
2189 new_value->data,
2190 svn_dirent_local_style(path, pool));
2191 SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2192 pool));
2193 }
2194 else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2195 {
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));
2199 }
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)
2204 {
2205 /* Make sure that the last line ends in a newline */
2206 if (propval->len == 0
2207 || propval->data[propval->len - 1] != '\n')
2208 {
2209 new_value = svn_stringbuf_create_from_string(propval, pool);
2210 svn_stringbuf_appendbyte(new_value, '\n');
2211 }
2212
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)
2218 {
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,
2228 /*scratch_*/pool));
2229 SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2230 externals,
2231 /*scratch_*/pool,
2232 /*scratch_*/pool));
2233 if (duplicate_targets && duplicate_targets->nelts > 0)
2234 {
2235 const char *more_str = "";
2236 if (duplicate_targets->nelts > 1)
2237 {
2238 more_str = apr_psprintf(/*scratch_*/pool,
2239 Q_(" (%d more duplicate target found)",
2240 " (%d more duplicate targets found)",
2241 duplicate_targets->nelts - 1),
2242 duplicate_targets->nelts - 1);
2243 }
2244 return svn_error_createf(
2245 SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2246 _("Invalid %s property on '%s': "
2247 "target '%s' appears more than once%s"),
2248 SVN_PROP_EXTERNALS,
2249 svn_dirent_local_style(path, pool),
2250 APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2251 more_str);
2252 }
2253 }
2254 }
2255 else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2256 {
2257 new_value = svn_stringbuf_create_from_string(propval, pool);
2258 svn_stringbuf_strip_whitespace(new_value);
2259 }
2260 else if (svn_prop_is_boolean(propname))
2261 {
2262 /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2263 propval = &boolean_value;
2264 }
2265 else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2266 {
2267 apr_hash_t *mergeinfo;
2268 svn_string_t *new_value_str;
2269
2270 SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2271
2272 /* Non-inheritable mergeinfo is only valid on directories. */
2273 if (kind != svn_node_dir
2274 && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2275 return svn_error_createf(
2276 SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2277 _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2278 svn_dirent_local_style(path, pool));
2279
2280 SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2281 propval = new_value_str;
2282 }
2283
2284 if (new_value)
2285 *propval_p = svn_stringbuf__morph_into_string(new_value);
2286 else
2287 *propval_p = propval;
2288
2289 return SVN_NO_ERROR;
2290 }
2291
2292
2293 svn_boolean_t
svn_wc_is_normal_prop(const char * name)2294 svn_wc_is_normal_prop(const char *name)
2295 {
2296 enum svn_prop_kind kind = svn_property_kind2(name);
2297 return (kind == svn_prop_regular_kind);
2298 }
2299
2300
2301 svn_boolean_t
svn_wc_is_wc_prop(const char * name)2302 svn_wc_is_wc_prop(const char *name)
2303 {
2304 enum svn_prop_kind kind = svn_property_kind2(name);
2305 return (kind == svn_prop_wc_kind);
2306 }
2307
2308
2309 svn_boolean_t
svn_wc_is_entry_prop(const char * name)2310 svn_wc_is_entry_prop(const char *name)
2311 {
2312 enum svn_prop_kind kind = svn_property_kind2(name);
2313 return (kind == svn_prop_entry_kind);
2314 }
2315
2316
2317 svn_error_t *
svn_wc__props_modified(svn_boolean_t * modified_p,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * scratch_pool)2318 svn_wc__props_modified(svn_boolean_t *modified_p,
2319 svn_wc__db_t *db,
2320 const char *local_abspath,
2321 apr_pool_t *scratch_pool)
2322 {
2323 SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2324 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2325 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2326 NULL, NULL, modified_p, NULL, NULL, NULL,
2327 db, local_abspath,
2328 scratch_pool, scratch_pool));
2329
2330 return SVN_NO_ERROR;
2331 }
2332
2333 svn_error_t *
svn_wc_props_modified_p2(svn_boolean_t * modified_p,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * scratch_pool)2334 svn_wc_props_modified_p2(svn_boolean_t *modified_p,
2335 svn_wc_context_t* wc_ctx,
2336 const char *local_abspath,
2337 apr_pool_t *scratch_pool)
2338 {
2339 return svn_error_trace(
2340 svn_wc__props_modified(modified_p,
2341 wc_ctx->db,
2342 local_abspath,
2343 scratch_pool));
2344 }
2345
2346 svn_error_t *
svn_wc__internal_propdiff(apr_array_header_t ** propchanges,apr_hash_t ** original_props,svn_wc__db_t * db,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2347 svn_wc__internal_propdiff(apr_array_header_t **propchanges,
2348 apr_hash_t **original_props,
2349 svn_wc__db_t *db,
2350 const char *local_abspath,
2351 apr_pool_t *result_pool,
2352 apr_pool_t *scratch_pool)
2353 {
2354 apr_hash_t *baseprops;
2355
2356 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2357
2358 /* ### if pristines are not defined, then should this raise an error,
2359 ### or use an empty set? */
2360 SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2361 result_pool, scratch_pool));
2362
2363 if (original_props != NULL)
2364 *original_props = baseprops;
2365
2366 if (propchanges != NULL)
2367 {
2368 apr_hash_t *actual_props;
2369
2370 /* Some nodes do not have pristine props, so let's just use an empty
2371 set here. Thus, any ACTUAL props are additions. */
2372 if (baseprops == NULL)
2373 baseprops = apr_hash_make(scratch_pool);
2374
2375 SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2376 result_pool, scratch_pool));
2377 /* ### be wary. certain nodes don't have ACTUAL props either. we
2378 ### may want to raise an error. or maybe that is a deletion of
2379 ### any potential pristine props? */
2380
2381 SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2382 result_pool));
2383 }
2384
2385 return SVN_NO_ERROR;
2386 }
2387
2388 svn_error_t *
svn_wc_get_prop_diffs2(apr_array_header_t ** propchanges,apr_hash_t ** original_props,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2389 svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2390 apr_hash_t **original_props,
2391 svn_wc_context_t *wc_ctx,
2392 const char *local_abspath,
2393 apr_pool_t *result_pool,
2394 apr_pool_t *scratch_pool)
2395 {
2396 return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2397 original_props, wc_ctx->db, local_abspath,
2398 result_pool, scratch_pool));
2399 }
2400
2401 svn_boolean_t
svn_wc__has_magic_property(const apr_array_header_t * properties)2402 svn_wc__has_magic_property(const apr_array_header_t *properties)
2403 {
2404 int i;
2405
2406 for (i = 0; i < properties->nelts; i++)
2407 {
2408 const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2409
2410 if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2411 || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2412 || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2413 || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2414 || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2415 return TRUE;
2416 }
2417 return FALSE;
2418 }
2419
2420 svn_error_t *
svn_wc__get_iprops(apr_array_header_t ** inherited_props,svn_wc_context_t * wc_ctx,const char * local_abspath,const char * propname,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2421 svn_wc__get_iprops(apr_array_header_t **inherited_props,
2422 svn_wc_context_t *wc_ctx,
2423 const char *local_abspath,
2424 const char *propname,
2425 apr_pool_t *result_pool,
2426 apr_pool_t *scratch_pool)
2427 {
2428 return svn_error_trace(
2429 svn_wc__db_read_inherited_props(inherited_props, NULL,
2430 wc_ctx->db, local_abspath,
2431 propname,
2432 result_pool, scratch_pool));
2433 }
2434
2435 svn_error_t *
svn_wc__get_cached_iprop_children(apr_hash_t ** iprop_paths,svn_depth_t depth,svn_wc_context_t * wc_ctx,const char * local_abspath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)2436 svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2437 svn_depth_t depth,
2438 svn_wc_context_t *wc_ctx,
2439 const char *local_abspath,
2440 apr_pool_t *result_pool,
2441 apr_pool_t *scratch_pool)
2442 {
2443 SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2444 depth,
2445 local_abspath,
2446 wc_ctx->db,
2447 result_pool,
2448 scratch_pool));
2449 return SVN_NO_ERROR;
2450 }
2451