]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_wc/props.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.git] / contrib / subversion / subversion / libsvn_wc / props.c
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 \f
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 \f
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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     {
2175       SVN_PROP_BOOLEAN_TRUE,
2176       sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
2177     };
2178
2179   SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2180
2181   /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
2182   if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2183     {
2184       svn_subst_eol_style_t eol_style;
2185       const char *ignored_eol;
2186       new_value = svn_stringbuf_create_from_string(propval, pool);
2187       svn_stringbuf_strip_whitespace(new_value);
2188       svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2189       if (eol_style == svn_subst_eol_style_unknown)
2190         return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2191                                  _("Unrecognized line ending style '%s' for '%s'"),
2192                                  new_value->data,
2193                                  svn_dirent_local_style(path, pool));
2194       SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2195                                              pool));
2196     }
2197   else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2198     {
2199       new_value = svn_stringbuf_create_from_string(propval, pool);
2200       svn_stringbuf_strip_whitespace(new_value);
2201       SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2202     }
2203   else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2204            || strcmp(propname, SVN_PROP_EXTERNALS) == 0
2205            || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
2206            || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
2207     {
2208       /* Make sure that the last line ends in a newline */
2209       if (propval->len == 0
2210           || propval->data[propval->len - 1] != '\n')
2211         {
2212           new_value = svn_stringbuf_create_from_string(propval, pool);
2213           svn_stringbuf_appendbyte(new_value, '\n');
2214         }
2215
2216       /* Make sure this is a valid externals property.  Do not
2217          allow 'skip_some_checks' to override, as there is no circumstance in
2218          which this is proper (because there is no circumstance in
2219          which Subversion can handle it). */
2220       if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2221         {
2222           /* We don't allow "." nor ".." as target directories in
2223              an svn:externals line.  As it happens, our parse code
2224              checks for this, so all we have to is invoke it --
2225              we're not interested in the parsed result, only in
2226              whether or not the parsing errored. */
2227           apr_array_header_t *externals = NULL;
2228           apr_array_header_t *duplicate_targets = NULL;
2229           SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
2230                                                       propval->data, FALSE,
2231                                                       /*scratch_*/pool));
2232           SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2233                                                      externals,
2234                                                      /*scratch_*/pool,
2235                                                      /*scratch_*/pool));
2236           if (duplicate_targets && duplicate_targets->nelts > 0)
2237             {
2238               const char *more_str = "";
2239               if (duplicate_targets->nelts > 1)
2240                 {
2241                   more_str = apr_psprintf(/*scratch_*/pool,
2242                                _(" (%d more duplicate targets found)"),
2243                                duplicate_targets->nelts - 1);
2244                 }
2245               return svn_error_createf(
2246                 SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2247                 _("Invalid %s property on '%s': "
2248                   "target '%s' appears more than once%s"),
2249                 SVN_PROP_EXTERNALS,
2250                 svn_dirent_local_style(path, pool),
2251                 APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2252                 more_str);
2253             }
2254         }
2255     }
2256   else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2257     {
2258       new_value = svn_stringbuf_create_from_string(propval, pool);
2259       svn_stringbuf_strip_whitespace(new_value);
2260     }
2261   else if (svn_prop_is_boolean(propname))
2262     {
2263       /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2264       propval = &boolean_value;
2265     }
2266   else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2267     {
2268       apr_hash_t *mergeinfo;
2269       svn_string_t *new_value_str;
2270
2271       SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2272
2273       /* Non-inheritable mergeinfo is only valid on directories. */
2274       if (kind != svn_node_dir
2275           && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2276         return svn_error_createf(
2277           SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2278           _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2279           svn_dirent_local_style(path, pool));
2280
2281       SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2282       propval = new_value_str;
2283     }
2284
2285   if (new_value)
2286     *propval_p = svn_stringbuf__morph_into_string(new_value);
2287   else
2288     *propval_p = propval;
2289
2290   return SVN_NO_ERROR;
2291 }
2292
2293
2294 svn_boolean_t
2295 svn_wc_is_normal_prop(const char *name)
2296 {
2297   enum svn_prop_kind kind = svn_property_kind2(name);
2298   return (kind == svn_prop_regular_kind);
2299 }
2300
2301
2302 svn_boolean_t
2303 svn_wc_is_wc_prop(const char *name)
2304 {
2305   enum svn_prop_kind kind = svn_property_kind2(name);
2306   return (kind == svn_prop_wc_kind);
2307 }
2308
2309
2310 svn_boolean_t
2311 svn_wc_is_entry_prop(const char *name)
2312 {
2313   enum svn_prop_kind kind = svn_property_kind2(name);
2314   return (kind == svn_prop_entry_kind);
2315 }
2316
2317
2318 svn_error_t *
2319 svn_wc__props_modified(svn_boolean_t *modified_p,
2320                        svn_wc__db_t *db,
2321                        const char *local_abspath,
2322                        apr_pool_t *scratch_pool)
2323 {
2324   SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2325                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2326                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2327                                NULL, NULL, modified_p, NULL, NULL, NULL,
2328                                db, local_abspath,
2329                                scratch_pool, scratch_pool));
2330
2331   return SVN_NO_ERROR;
2332 }
2333
2334 svn_error_t *
2335 svn_wc_props_modified_p2(svn_boolean_t *modified_p,
2336                          svn_wc_context_t* wc_ctx,
2337                          const char *local_abspath,
2338                          apr_pool_t *scratch_pool)
2339 {
2340   return svn_error_trace(
2341              svn_wc__props_modified(modified_p,
2342                                     wc_ctx->db,
2343                                     local_abspath,
2344                                     scratch_pool));
2345 }
2346
2347 svn_error_t *
2348 svn_wc__internal_propdiff(apr_array_header_t **propchanges,
2349                           apr_hash_t **original_props,
2350                           svn_wc__db_t *db,
2351                           const char *local_abspath,
2352                           apr_pool_t *result_pool,
2353                           apr_pool_t *scratch_pool)
2354 {
2355   apr_hash_t *baseprops;
2356
2357   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2358
2359   /* ### if pristines are not defined, then should this raise an error,
2360      ### or use an empty set?  */
2361   SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2362                                          result_pool, scratch_pool));
2363
2364   if (original_props != NULL)
2365     *original_props = baseprops;
2366
2367   if (propchanges != NULL)
2368     {
2369       apr_hash_t *actual_props;
2370
2371       /* Some nodes do not have pristine props, so let's just use an empty
2372          set here. Thus, any ACTUAL props are additions.  */
2373       if (baseprops == NULL)
2374         baseprops = apr_hash_make(scratch_pool);
2375
2376       SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2377                                     result_pool, scratch_pool));
2378       /* ### be wary. certain nodes don't have ACTUAL props either. we
2379          ### may want to raise an error. or maybe that is a deletion of
2380          ### any potential pristine props?  */
2381
2382       SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2383                              result_pool));
2384     }
2385
2386   return SVN_NO_ERROR;
2387 }
2388
2389 svn_error_t *
2390 svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2391                        apr_hash_t **original_props,
2392                        svn_wc_context_t *wc_ctx,
2393                        const char *local_abspath,
2394                        apr_pool_t *result_pool,
2395                        apr_pool_t *scratch_pool)
2396 {
2397   return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2398                                     original_props, wc_ctx->db, local_abspath,
2399                                     result_pool, scratch_pool));
2400 }
2401
2402 svn_boolean_t
2403 svn_wc__has_magic_property(const apr_array_header_t *properties)
2404 {
2405   int i;
2406
2407   for (i = 0; i < properties->nelts; i++)
2408     {
2409       const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2410
2411       if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2412           || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2413           || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2414           || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2415           || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2416         return TRUE;
2417     }
2418   return FALSE;
2419 }
2420
2421 svn_error_t *
2422 svn_wc__get_iprops(apr_array_header_t **inherited_props,
2423                    svn_wc_context_t *wc_ctx,
2424                    const char *local_abspath,
2425                    const char *propname,
2426                    apr_pool_t *result_pool,
2427                    apr_pool_t *scratch_pool)
2428 {
2429   return svn_error_trace(
2430             svn_wc__db_read_inherited_props(inherited_props, NULL,
2431                                             wc_ctx->db, local_abspath,
2432                                             propname,
2433                                             result_pool, scratch_pool));
2434 }
2435
2436 svn_error_t *
2437 svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2438                                   svn_depth_t depth,
2439                                   svn_wc_context_t *wc_ctx,
2440                                   const char *local_abspath,
2441                                   apr_pool_t *result_pool,
2442                                   apr_pool_t *scratch_pool)
2443 {
2444   SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2445                                                      depth,
2446                                                      local_abspath,
2447                                                      wc_ctx->db,
2448                                                      result_pool,
2449                                                      scratch_pool));
2450   return SVN_NO_ERROR;
2451 }