]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_wc/props.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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 /* Forward declaration.  */
66 static svn_error_t *
67 prop_conflict_from_skel(const svn_string_t **conflict_desc,
68                         const svn_skel_t *skel,
69                         apr_pool_t *result_pool,
70                         apr_pool_t *scratch_pool);
71
72 /* Given a *SINGLE* property conflict in PROP_SKEL, generate a description
73    for it, and write it to STREAM, along with a trailing EOL sequence.
74
75    See prop_conflict_from_skel() for details on PROP_SKEL.  */
76 static svn_error_t *
77 append_prop_conflict(svn_stream_t *stream,
78                      const svn_skel_t *prop_skel,
79                      apr_pool_t *pool)
80 {
81   /* TODO:  someday, perhaps prefix each conflict_description with a
82      timestamp or something? */
83   const svn_string_t *conflict_desc;
84
85   SVN_ERR(prop_conflict_from_skel(&conflict_desc, prop_skel, pool, pool));
86
87   return svn_stream_puts(stream, conflict_desc->data);
88 }
89
90 /*---------------------------------------------------------------------*/
91 \f
92 /*** Merging propchanges into the working copy ***/
93
94
95 /* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
96    calculate the deltas between them. */
97 static svn_error_t *
98 diff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
99                      const svn_string_t *from_prop_val,
100                      const svn_string_t *to_prop_val, apr_pool_t *pool)
101 {
102   if (svn_string_compare(from_prop_val, to_prop_val))
103     {
104       /* Don't bothering parsing identical mergeinfo. */
105       *deleted = apr_hash_make(pool);
106       *added = apr_hash_make(pool);
107     }
108   else
109     {
110       svn_mergeinfo_t from, to;
111       SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
112       SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
113       SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
114                                   TRUE, pool, pool));
115     }
116   return SVN_NO_ERROR;
117 }
118
119 /* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
120    reconstitute it into *OUTPUT.  Call when the WC's mergeinfo has
121    been modified to combine it with incoming mergeinfo from the
122    repos. */
123 static svn_error_t *
124 combine_mergeinfo_props(const svn_string_t **output,
125                         const svn_string_t *prop_val1,
126                         const svn_string_t *prop_val2,
127                         apr_pool_t *result_pool,
128                         apr_pool_t *scratch_pool)
129 {
130   svn_mergeinfo_t mergeinfo1, mergeinfo2;
131   svn_string_t *mergeinfo_string;
132
133   SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
134   SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
135   SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
136                                scratch_pool));
137   SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
138   *output = mergeinfo_string;
139   return SVN_NO_ERROR;
140 }
141
142 /* Perform a 3-way merge operation on mergeinfo.  FROM_PROP_VAL is
143    the "base" property value, WORKING_PROP_VAL is the current value,
144    and TO_PROP_VAL is the new value. */
145 static svn_error_t *
146 combine_forked_mergeinfo_props(const svn_string_t **output,
147                                const svn_string_t *from_prop_val,
148                                const svn_string_t *working_prop_val,
149                                const svn_string_t *to_prop_val,
150                                apr_pool_t *result_pool,
151                                apr_pool_t *scratch_pool)
152 {
153   svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
154   svn_string_t *mergeinfo_string;
155
156   /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
157   SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
158                                working_prop_val, scratch_pool));
159   SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
160                                to_prop_val, scratch_pool));
161   SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
162                                scratch_pool, scratch_pool));
163   SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
164                                scratch_pool, scratch_pool));
165
166   /* Apply the combined deltas to the base. */
167   SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
168                               scratch_pool));
169   SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
170                                scratch_pool, scratch_pool));
171
172   SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
173                                 TRUE, scratch_pool, scratch_pool));
174
175   SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
176                                   result_pool));
177   *output = mergeinfo_string;
178   return SVN_NO_ERROR;
179 }
180
181
182 svn_error_t *
183 svn_wc_merge_props3(svn_wc_notify_state_t *state,
184                     svn_wc_context_t *wc_ctx,
185                     const char *local_abspath,
186                     const svn_wc_conflict_version_t *left_version,
187                     const svn_wc_conflict_version_t *right_version,
188                     apr_hash_t *baseprops,
189                     const apr_array_header_t *propchanges,
190                     svn_boolean_t dry_run,
191                     svn_wc_conflict_resolver_func2_t conflict_func,
192                     void *conflict_baton,
193                     svn_cancel_func_t cancel_func,
194                     void *cancel_baton,
195                     apr_pool_t *scratch_pool)
196 {
197   int i;
198   svn_wc__db_status_t status;
199   svn_node_kind_t kind;
200   apr_hash_t *pristine_props = NULL;
201   apr_hash_t *actual_props;
202   apr_hash_t *new_actual_props;
203   svn_boolean_t had_props, props_mod;
204   svn_boolean_t have_base;
205   svn_boolean_t conflicted;
206   svn_skel_t *work_items = NULL;
207   svn_skel_t *conflict_skel = NULL;
208   svn_wc__db_t *db = wc_ctx->db;
209
210   /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
211      may be NULL. */
212
213   SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
214                                NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
215                                NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
216                                &had_props, &props_mod, &have_base, NULL, NULL,
217                                db, local_abspath,
218                                scratch_pool, scratch_pool));
219
220   /* Checks whether the node exists and returns the hidden flag */
221   if (status == svn_wc__db_status_not_present
222       || status == svn_wc__db_status_server_excluded
223       || status == svn_wc__db_status_excluded)
224     {
225       return svn_error_createf(
226                     SVN_ERR_WC_PATH_NOT_FOUND, NULL,
227                     _("The node '%s' was not found."),
228                     svn_dirent_local_style(local_abspath, scratch_pool));
229     }
230   else if (status != svn_wc__db_status_normal
231            && status != svn_wc__db_status_added
232            && status != svn_wc__db_status_incomplete)
233     {
234       return svn_error_createf(
235                     SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
236                     _("The node '%s' does not have properties in this state."),
237                     svn_dirent_local_style(local_abspath, scratch_pool));
238     }
239   else if (conflicted)
240       {
241         svn_boolean_t text_conflicted;
242         svn_boolean_t prop_conflicted;
243         svn_boolean_t tree_conflicted;
244
245         SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
246                                               &prop_conflicted,
247                                               &tree_conflicted,
248                                               db, local_abspath,
249                                               scratch_pool));
250
251         /* We can't install two text/prop conflicts on a single node, so
252            avoid even checking that we have to merge it */
253         if (text_conflicted || prop_conflicted || tree_conflicted)
254           {
255             return svn_error_createf(
256                             SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
257                             _("Can't merge into conflicted node '%s'"),
258                             svn_dirent_local_style(local_abspath,
259                                                    scratch_pool));
260           }
261         /* else: Conflict was resolved by removing markers */
262       }
263
264   /* The PROPCHANGES may not have non-"normal" properties in it. If entry
265      or wc props were allowed, then the following code would install them
266      into the BASE and/or WORKING properties(!).  */
267   for (i = propchanges->nelts; i--; )
268     {
269       const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
270
271       if (!svn_wc_is_normal_prop(change->name))
272         return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
273                                  _("The property '%s' may not be merged "
274                                    "into '%s'."),
275                                  change->name,
276                                  svn_dirent_local_style(local_abspath,
277                                                         scratch_pool));
278     }
279
280   if (had_props)
281     SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
282                                            scratch_pool, scratch_pool));
283   if (pristine_props == NULL)
284     pristine_props = apr_hash_make(scratch_pool);
285
286   if (props_mod)
287     SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
288                                      scratch_pool, scratch_pool));
289   else
290     actual_props = pristine_props;
291
292   /* Note that while this routine does the "real" work, it's only
293      prepping tempfiles and writing log commands.  */
294   SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
295                               &new_actual_props,
296                               db, local_abspath,
297                               baseprops /* server_baseprops */,
298                               pristine_props,
299                               actual_props,
300                               propchanges,
301                               scratch_pool, scratch_pool));
302
303   if (dry_run)
304     {
305       return SVN_NO_ERROR;
306     }
307
308   {
309     const char *dir_abspath;
310
311     if (kind == svn_node_dir)
312       dir_abspath = local_abspath;
313     else
314       dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
315
316     /* Verify that we're holding this directory's write lock.  */
317     SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
318   }
319
320   if (conflict_skel)
321     {
322       svn_skel_t *work_item;
323       SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
324                                                  left_version,
325                                                  right_version,
326                                                  scratch_pool,
327                                                  scratch_pool));
328
329       SVN_ERR(svn_wc__conflict_create_markers(&work_item,
330                                               db, local_abspath,
331                                               conflict_skel,
332                                               scratch_pool, scratch_pool));
333
334       work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
335     }
336
337   /* After a (not-dry-run) merge, we ALWAYS have props to save.  */
338   SVN_ERR_ASSERT(new_actual_props != NULL);
339
340   SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
341                                   svn_wc__has_magic_property(propchanges),
342                                   conflict_skel,
343                                   work_items,
344                                   scratch_pool));
345
346   if (work_items != NULL)
347     SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
348                            scratch_pool));
349
350   /* If there is a conflict, try to resolve it. */
351   if (conflict_skel && conflict_func)
352     {
353       svn_boolean_t prop_conflicted;
354
355       SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, conflict_skel,
356                                                NULL /* merge_options */,
357                                                conflict_func, conflict_baton,
358                                                cancel_func, cancel_baton,
359                                                scratch_pool));
360
361       /* Reset *STATE if all prop conflicts were resolved. */
362       SVN_ERR(svn_wc__internal_conflicted_p(
363                 NULL, &prop_conflicted, NULL,
364                 wc_ctx->db, local_abspath, scratch_pool));
365       if (! prop_conflicted)
366         *state = svn_wc_notify_state_merged;
367     }
368
369   return SVN_NO_ERROR;
370 }
371
372
373 /* Generate a message to describe the property conflict among these four
374    values.
375
376    Note that this function (currently) interprets the property values as
377    strings, but they could actually be binary values. We'll keep the
378    types as svn_string_t in case we fix this in the future.  */
379 static svn_stringbuf_t *
380 generate_conflict_message(const char *propname,
381                           const svn_string_t *original,
382                           const svn_string_t *mine,
383                           const svn_string_t *incoming,
384                           const svn_string_t *incoming_base,
385                           apr_pool_t *result_pool)
386 {
387   if (incoming_base == NULL)
388     {
389       /* Attempting to add the value INCOMING.  */
390       SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
391
392       if (mine)
393         {
394           /* To have a conflict, these must be different.  */
395           SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
396
397           /* Note that we don't care whether MINE is locally-added or
398              edited, or just something different that is a copy of the
399              pristine ORIGINAL.  */
400           return svn_stringbuf_createf(result_pool,
401                                        _("Trying to add new property '%s'\n"
402                                          "but the property already exists.\n"),
403                                        propname);
404         }
405
406       /* To have a conflict, we must have an ORIGINAL which has been
407          locally-deleted.  */
408       SVN_ERR_ASSERT_NO_RETURN(original != NULL);
409       return svn_stringbuf_createf(result_pool,
410                                    _("Trying to add new property '%s'\n"
411                                      "but the property has been locally "
412                                      "deleted.\n"),
413                                    propname);
414     }
415
416   if (incoming == NULL)
417     {
418       /* Attempting to delete the value INCOMING_BASE.  */
419       SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
420
421       /* Are we trying to delete a local addition? */
422       if (original == NULL && mine != NULL)
423         return svn_stringbuf_createf(result_pool,
424                                      _("Trying to delete property '%s'\n"
425                                        "but the property has been locally "
426                                        "added.\n"),
427                                      propname);
428
429       /* A conflict can only occur if we originally had the property;
430          otherwise, we would have merged the property-delete into the
431          non-existent property.  */
432       SVN_ERR_ASSERT_NO_RETURN(original != NULL);
433
434       if (svn_string_compare(original, incoming_base))
435         {
436           if (mine)
437             /* We were trying to delete the correct property, but an edit
438                caused the conflict.  */
439             return svn_stringbuf_createf(result_pool,
440                                          _("Trying to delete property '%s'\n"
441                                            "but the property has been locally "
442                                            "modified.\n"),
443                                          propname);
444         }
445       else if (mine == NULL)
446         {
447           /* We were trying to delete the property, but we have locally
448              deleted the same property, but with a different value. */
449           return svn_stringbuf_createf(result_pool,
450                                        _("Trying to delete property '%s'\n"
451                                          "but the property has been locally "
452                                          "deleted and had a different "
453                                          "value.\n"),
454                                        propname);
455         }
456
457       /* We were trying to delete INCOMING_BASE but our ORIGINAL is
458          something else entirely.  */
459       SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
460
461       return svn_stringbuf_createf(result_pool,
462                                    _("Trying to delete property '%s'\n"
463                                      "but the local property value is "
464                                      "different.\n"),
465                                    propname);
466     }
467
468   /* Attempting to change the property from INCOMING_BASE to INCOMING.  */
469
470   /* If we have a (current) property value, then it should be different
471      from the INCOMING_BASE; otherwise, the incoming change would have
472      been applied to it.  */
473   SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
474
475   if (original && mine && svn_string_compare(original, mine))
476     {
477       /* We have an unchanged property, so the original values must
478          have been different.  */
479       SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
480       return svn_stringbuf_createf(result_pool,
481                                    _("Trying to change property '%s'\n"
482                                      "but the local property value conflicts "
483                                      "with the incoming change.\n"),
484                                    propname);
485     }
486
487   if (original && mine)
488     return svn_stringbuf_createf(result_pool,
489                                  _("Trying to change property '%s'\n"
490                                    "but the property has already been locally "
491                                    "changed to a different value.\n"),
492                                  propname);
493
494   if (original)
495     return svn_stringbuf_createf(result_pool,
496                                  _("Trying to change property '%s'\nbut "
497                                    "the property has been locally deleted.\n"),
498                                  propname);
499
500   if (mine)
501     return svn_stringbuf_createf(result_pool,
502                                  _("Trying to change property '%s'\nbut the "
503                                    "property has been locally added with a "
504                                    "different value.\n"),
505                                  propname);
506
507   return svn_stringbuf_createf(result_pool,
508                                _("Trying to change property '%s'\nbut "
509                                  "the property does not exist locally.\n"),
510                                propname);
511 }
512
513
514 /* SKEL will be one of:
515
516    ()
517    (VALUE)
518
519    Return NULL for the former (the particular property value was not
520    present), and VALUE for the second.  */
521 static const svn_string_t *
522 maybe_prop_value(const svn_skel_t *skel,
523                  apr_pool_t *result_pool)
524 {
525   if (skel->children == NULL)
526     return NULL;
527
528   return svn_string_ncreate(skel->children->data,
529                             skel->children->len,
530                             result_pool);
531 }
532
533
534 /* Parse a property conflict description from the provided SKEL.
535    The result includes a descriptive message (see generate_conflict_message)
536    and maybe a diff of property values containing conflict markers.
537    The result will be allocated in RESULT_POOL.
538
539    Note: SKEL is a single property conflict of the form:
540
541    ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
542
543    See notes/wc-ng/conflict-storage for more information.  */
544 static svn_error_t *
545 prop_conflict_from_skel(const svn_string_t **conflict_desc,
546                         const svn_skel_t *skel,
547                         apr_pool_t *result_pool,
548                         apr_pool_t *scratch_pool)
549 {
550   const svn_string_t *original;
551   const svn_string_t *mine;
552   const svn_string_t *incoming;
553   const svn_string_t *incoming_base;
554   const char *propname;
555   svn_diff_t *diff;
556   svn_diff_file_options_t *diff_opts;
557   svn_stringbuf_t *buf;
558   svn_boolean_t original_is_binary;
559   svn_boolean_t mine_is_binary;
560   svn_boolean_t incoming_is_binary;
561
562   /* Navigate to the property name.  */
563   skel = skel->children->next;
564
565   /* We need to copy these into SCRATCH_POOL in order to nul-terminate
566      the values.  */
567   propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
568   original = maybe_prop_value(skel->next, scratch_pool);
569   mine = maybe_prop_value(skel->next->next, scratch_pool);
570   incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
571   incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
572
573   buf = generate_conflict_message(propname, original, mine, incoming,
574                                   incoming_base, scratch_pool);
575
576   if (mine == NULL)
577     mine = svn_string_create_empty(scratch_pool);
578   if (incoming == NULL)
579     incoming = svn_string_create_empty(scratch_pool);
580
581   /* Pick a suitable base for the conflict diff.
582    * The incoming value is always a change,
583    * but the local value might not have changed. */
584   if (original == NULL)
585     {
586       if (incoming_base)
587         original = incoming_base;
588       else
589         original = svn_string_create_empty(scratch_pool);
590     }
591   else if (incoming_base && svn_string_compare(original, mine))
592     original = incoming_base;
593
594   /* If any of the property values involved in the diff is binary data,
595    * do not generate a diff. */
596   original_is_binary = svn_io_is_binary_data(original->data, original->len);
597   mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
598   incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
599
600   if (!(original_is_binary || mine_is_binary || incoming_is_binary))
601     {
602       diff_opts = svn_diff_file_options_create(scratch_pool);
603       diff_opts->ignore_space = svn_diff_file_ignore_space_none;
604       diff_opts->ignore_eol_style = FALSE;
605       diff_opts->show_c_function = FALSE;
606       SVN_ERR(svn_diff_mem_string_diff3(&diff, original, mine, incoming,
607                                         diff_opts, scratch_pool));
608       if (svn_diff_contains_conflicts(diff))
609         {
610           svn_stream_t *stream;
611           svn_diff_conflict_display_style_t style;
612           const char *mine_marker = _("<<<<<<< (local property value)");
613           const char *incoming_marker = _(">>>>>>> (incoming property value)");
614           const char *separator = "=======";
615           svn_string_t *original_ascii =
616             svn_string_create(svn_utf_cstring_from_utf8_fuzzy(original->data,
617                                                               scratch_pool),
618                               scratch_pool);
619           svn_string_t *mine_ascii =
620             svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
621                                                               scratch_pool),
622                               scratch_pool);
623           svn_string_t *incoming_ascii =
624             svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
625                                                               scratch_pool),
626                               scratch_pool);
627
628           style = svn_diff_conflict_display_modified_latest;
629           stream = svn_stream_from_stringbuf(buf, scratch_pool);
630           SVN_ERR(svn_stream_skip(stream, buf->len));
631           SVN_ERR(svn_diff_mem_string_output_merge2(stream, diff,
632                                                     original_ascii,
633                                                     mine_ascii,
634                                                     incoming_ascii,
635                                                     NULL, mine_marker,
636                                                     incoming_marker, separator,
637                                                     style, scratch_pool));
638           SVN_ERR(svn_stream_close(stream));
639
640           *conflict_desc = svn_string_create_from_buf(buf, result_pool);
641           return SVN_NO_ERROR;
642         }
643     }
644
645   /* If we could not print a conflict diff just print full values . */
646   if (mine->len > 0)
647     {
648       svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
649       if (mine_is_binary)
650         svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
651                                         "binary data\n"));
652       else
653         svn_stringbuf_appendbytes(buf, mine->data, mine->len);
654       svn_stringbuf_appendcstr(buf, "\n");
655     }
656
657   if (incoming->len > 0)
658     {
659       svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
660       if (incoming_is_binary)
661         svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
662                                         "binary data\n"));
663       else
664         svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
665       svn_stringbuf_appendcstr(buf, "\n");
666     }
667
668   *conflict_desc = svn_string_create_from_buf(buf, result_pool);
669   return SVN_NO_ERROR;
670 }
671
672
673 /* Create a property conflict file at PREJFILE based on the property
674    conflicts in CONFLICT_SKEL.  */
675 svn_error_t *
676 svn_wc__create_prejfile(const char **tmp_prejfile_abspath,
677                         svn_wc__db_t *db,
678                         const char *local_abspath,
679                         const svn_skel_t *conflict_skel,
680                         apr_pool_t *result_pool,
681                         apr_pool_t *scratch_pool)
682 {
683   const char *tempdir_abspath;
684   svn_stream_t *stream;
685   const char *temp_abspath;
686   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
687   const svn_skel_t *scan;
688
689   SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
690                                          db, local_abspath,
691                                          iterpool, iterpool));
692
693   SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
694                                  tempdir_abspath, svn_io_file_del_none,
695                                  scratch_pool, iterpool));
696
697   for (scan = conflict_skel->children->next; scan != NULL; scan = scan->next)
698     {
699       svn_pool_clear(iterpool);
700
701       SVN_ERR(append_prop_conflict(stream, scan, iterpool));
702     }
703
704   SVN_ERR(svn_stream_close(stream));
705
706   svn_pool_destroy(iterpool);
707
708   *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
709   return SVN_NO_ERROR;
710 }
711
712
713 /* Set the value of *STATE to NEW_VALUE if STATE is not NULL
714  * and NEW_VALUE is a higer order value than *STATE's current value
715  * using this ordering (lower order first):
716  *
717  * - unknown, unchanged, inapplicable
718  * - changed
719  * - merged
720  * - missing
721  * - obstructed
722  * - conflicted
723  *
724  */
725 static void
726 set_prop_merge_state(svn_wc_notify_state_t *state,
727                      svn_wc_notify_state_t new_value)
728 {
729   static char ordering[] =
730     { svn_wc_notify_state_unknown,
731       svn_wc_notify_state_unchanged,
732       svn_wc_notify_state_inapplicable,
733       svn_wc_notify_state_changed,
734       svn_wc_notify_state_merged,
735       svn_wc_notify_state_obstructed,
736       svn_wc_notify_state_conflicted };
737   int state_pos = 0, i;
738
739   if (! state)
740     return;
741
742   /* Find *STATE in our ordering */
743   for (i = 0; i < sizeof(ordering); i++)
744     {
745       if (*state == ordering[i])
746         {
747           state_pos = i;
748           break;
749         }
750     }
751
752   /* Find NEW_VALUE in our ordering
753    * We don't need to look further than where we found *STATE though:
754    * If we find our value, it's order is too low.
755    * If we don't find it, we'll want to set it, no matter its order.
756    */
757
758   for (i = 0; i <= state_pos; i++)
759     {
760       if (new_value == ordering[i])
761         return;
762     }
763
764   *state = new_value;
765 }
766
767 /* Apply the addition of a property with name PROPNAME and value NEW_VAL to
768  * the existing property with value WORKING_VAL, that originally had value
769  * PRISTINE_VAL.
770  *
771  * Sets *RESULT_VAL to the resulting value.
772  * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
773  * Sets *DID_MERGE to true if the result is caused by a merge
774  */
775 static svn_error_t *
776 apply_single_prop_add(const svn_string_t **result_val,
777                       svn_boolean_t *conflict_remains,
778                       svn_boolean_t *did_merge,
779                       const char *propname,
780                       const svn_string_t *pristine_val,
781                       const svn_string_t *new_val,
782                       const svn_string_t *working_val,
783                       apr_pool_t *result_pool,
784                       apr_pool_t *scratch_pool)
785
786 {
787   *conflict_remains = FALSE;
788
789   if (working_val)
790     {
791       /* the property already exists in actual_props... */
792
793       if (svn_string_compare(working_val, new_val))
794         /* The value we want is already there, so it's a merge. */
795         *did_merge = TRUE;
796
797       else
798         {
799           svn_boolean_t merged_prop = FALSE;
800
801           /* The WC difference doesn't match the new value.
802            We only merge mergeinfo;  other props conflict */
803           if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
804             {
805               const svn_string_t *merged_val;
806               svn_error_t *err = combine_mergeinfo_props(&merged_val,
807                                                          working_val,
808                                                          new_val,
809                                                          result_pool,
810                                                          scratch_pool);
811
812               /* Issue #3896 'mergeinfo syntax errors should be treated
813                  gracefully': If bogus mergeinfo is present we can't
814                  merge intelligently, so raise a conflict instead. */
815               if (err)
816                 {
817                   if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
818                     svn_error_clear(err);
819                   else
820                     return svn_error_trace(err);
821                   }
822               else
823                 {
824                   merged_prop = TRUE;
825                   *result_val = merged_val;
826                   *did_merge = TRUE;
827                 }
828             }
829
830           if (!merged_prop)
831             *conflict_remains = TRUE;
832         }
833     }
834   else if (pristine_val)
835     *conflict_remains = TRUE;
836   else  /* property doesn't yet exist in actual_props...  */
837     /* so just set it */
838     *result_val = new_val;
839
840   return SVN_NO_ERROR;
841 }
842
843
844 /* Apply the deletion of a property to the existing
845  * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
846  *
847  * Sets *RESULT_VAL to the resulting value.
848  * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
849  * Sets *DID_MERGE to true if the result is caused by a merge
850  */
851 static svn_error_t *
852 apply_single_prop_delete(const svn_string_t **result_val,
853                          svn_boolean_t *conflict_remains,
854                          svn_boolean_t *did_merge,
855                          const svn_string_t *base_val,
856                          const svn_string_t *old_val,
857                          const svn_string_t *working_val)
858 {
859   *conflict_remains = FALSE;
860
861   if (! base_val)
862     {
863       if (working_val
864           && !svn_string_compare(working_val, old_val))
865         {
866           /* We are trying to delete a locally-added prop. */
867           *conflict_remains = TRUE;
868         }
869       else
870         {
871           *result_val = NULL;
872           if (old_val)
873             /* This is a merge, merging a delete into non-existent
874                property or a local addition of same prop value. */
875             *did_merge = TRUE;
876         }
877     }
878
879   else if (svn_string_compare(base_val, old_val))
880     {
881        if (working_val)
882          {
883            if (svn_string_compare(working_val, old_val))
884              /* they have the same values, so it's an update */
885              *result_val = NULL;
886            else
887              *conflict_remains = TRUE;
888          }
889        else
890          /* The property is locally deleted from the same value, so it's
891             a merge */
892          *did_merge = TRUE;
893     }
894
895   else
896     *conflict_remains = TRUE;
897
898   return SVN_NO_ERROR;
899 }
900
901
902 /* Merge a change to the mergeinfo property. Similar to
903    apply_single_prop_change(), except that the property name is always
904    SVN_PROP_MERGEINFO. */
905 /* ### This function is extracted straight from the previous all-in-one
906    version of apply_single_prop_change() by removing the code paths that
907    were not followed for this property, but with no attempt to rationalize
908    the remainder. */
909 static svn_error_t *
910 apply_single_mergeinfo_prop_change(const svn_string_t **result_val,
911                                    svn_boolean_t *conflict_remains,
912                                    svn_boolean_t *did_merge,
913                                    const svn_string_t *base_val,
914                                    const svn_string_t *old_val,
915                                    const svn_string_t *new_val,
916                                    const svn_string_t *working_val,
917                                    apr_pool_t *result_pool,
918                                    apr_pool_t *scratch_pool)
919 {
920   if ((working_val && ! base_val)
921       || (! working_val && base_val)
922       || (working_val && base_val
923           && !svn_string_compare(working_val, base_val)))
924     {
925       /* Locally changed property */
926       if (working_val)
927         {
928           if (svn_string_compare(working_val, new_val))
929             /* The new value equals the changed value: a no-op merge */
930             *did_merge = TRUE;
931           else
932             {
933               /* We have base, WC, and new values.  Discover
934                  deltas between base <-> WC, and base <->
935                  incoming.  Combine those deltas, and apply
936                  them to base to get the new value. */
937               SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
938                                                      working_val,
939                                                      new_val,
940                                                      result_pool,
941                                                      scratch_pool));
942               *result_val = new_val;
943               *did_merge = TRUE;
944             }
945         }
946       else
947         {
948           /* There is a base_val but no working_val */
949           *conflict_remains = TRUE;
950         }
951     }
952
953   else if (! working_val) /* means !working_val && !base_val due
954                              to conditions above: no prop at all */
955     {
956       /* Discover any mergeinfo additions in the
957          incoming value relative to the base, and
958          "combine" those with the empty WC value. */
959       svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
960       svn_string_t *mergeinfo_string;
961
962       SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
963                                    &added_mergeinfo,
964                                    old_val, new_val, scratch_pool));
965       SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
966                                       added_mergeinfo, result_pool));
967       *result_val = mergeinfo_string;
968     }
969
970   else /* means working && base && svn_string_compare(working, base) */
971     {
972       if (svn_string_compare(old_val, base_val))
973         *result_val = new_val;
974       else
975         {
976           /* We have base, WC, and new values.  Discover
977              deltas between base <-> WC, and base <->
978              incoming.  Combine those deltas, and apply
979              them to base to get the new value. */
980           SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
981                                                  working_val,
982                                                  new_val, result_pool,
983                                                  scratch_pool));
984           *result_val = new_val;
985           *did_merge = TRUE;
986         }
987     }
988
989   return SVN_NO_ERROR;
990 }
991
992 /* Merge a change to a property, using the rule that if the working value
993    is equal to the new value then there is nothing we need to do. Else, if
994    the working value is the same as the old value then apply the change as a
995    simple update (replacement), otherwise invoke maybe_generate_propconflict().
996    The definition of the arguments and behaviour is the same as
997    apply_single_prop_change(). */
998 static svn_error_t *
999 apply_single_generic_prop_change(const svn_string_t **result_val,
1000                                  svn_boolean_t *conflict_remains,
1001                                  svn_boolean_t *did_merge,
1002                                  const svn_string_t *old_val,
1003                                  const svn_string_t *new_val,
1004                                  const svn_string_t *working_val)
1005 {
1006   SVN_ERR_ASSERT(old_val != NULL);
1007
1008   /* If working_val is the same as new_val already then there is
1009    * nothing to do */
1010   if (working_val && new_val
1011       && svn_string_compare(working_val, new_val))
1012     {
1013       /* All values identical is a trivial, non-notifiable merge */
1014       if (! old_val || ! svn_string_compare(old_val, new_val))
1015         *did_merge = TRUE;
1016     }
1017   /* If working_val is the same as old_val... */
1018   else if (working_val && old_val
1019       && svn_string_compare(working_val, old_val))
1020     {
1021       /* A trivial update: change it to new_val. */
1022       *result_val = new_val;
1023     }
1024   else
1025     {
1026       /* Merge the change. */
1027       *conflict_remains = TRUE;
1028     }
1029
1030   return SVN_NO_ERROR;
1031 }
1032
1033 /* Change the property with name PROPNAME, setting *RESULT_VAL,
1034  * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
1035  *
1036  * BASE_VAL contains the working copy base property value. (May be null.)
1037  *
1038  * OLD_VAL contains the value of the property the server
1039  * thinks it's overwriting. (Not null.)
1040  *
1041  * NEW_VAL contains the value to be set. (Not null.)
1042  *
1043  * WORKING_VAL contains the working copy actual value. (May be null.)
1044  */
1045 static svn_error_t *
1046 apply_single_prop_change(const svn_string_t **result_val,
1047                          svn_boolean_t *conflict_remains,
1048                          svn_boolean_t *did_merge,
1049                          const char *propname,
1050                          const svn_string_t *base_val,
1051                          const svn_string_t *old_val,
1052                          const svn_string_t *new_val,
1053                          const svn_string_t *working_val,
1054                          apr_pool_t *result_pool,
1055                          apr_pool_t *scratch_pool)
1056 {
1057   svn_boolean_t merged_prop = FALSE;
1058
1059   *conflict_remains = FALSE;
1060
1061   /* Note: The purpose is to apply the change (old_val -> new_val) onto
1062      (working_val). There is no need for base_val to be involved in the
1063      process except as a bit of context to help the user understand and
1064      resolve any conflict. */
1065
1066   /* Decide how to merge, based on whether we know anything special about
1067      the property. */
1068   if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1069     {
1070       /* We know how to merge any mergeinfo property change...
1071
1072          ...But Issue #3896 'mergeinfo syntax errors should be treated
1073          gracefully' might thwart us.  If bogus mergeinfo is present we
1074          can't merge intelligently, so let the standard method deal with
1075          it instead. */
1076       svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
1077                                                             conflict_remains,
1078                                                             did_merge,
1079                                                             base_val,
1080                                                             old_val,
1081                                                             new_val,
1082                                                             working_val,
1083                                                             result_pool,
1084                                                             scratch_pool);
1085        if (err)
1086          {
1087            if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1088              svn_error_clear(err);
1089            else
1090              return svn_error_trace(err);
1091            }
1092        else
1093          {
1094            merged_prop = TRUE;
1095          }
1096     }
1097
1098   if (!merged_prop)
1099     {
1100       /* The standard method: perform a simple update automatically, but
1101          pass any other kind of merge to maybe_generate_propconflict(). */
1102
1103       SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
1104                                                did_merge,
1105                                                old_val, new_val, working_val));
1106     }
1107
1108   return SVN_NO_ERROR;
1109 }
1110
1111
1112 svn_error_t *
1113 svn_wc__merge_props(svn_skel_t **conflict_skel,
1114                     svn_wc_notify_state_t *state,
1115                     apr_hash_t **new_actual_props,
1116                     svn_wc__db_t *db,
1117                     const char *local_abspath,
1118                     apr_hash_t *server_baseprops,
1119                     apr_hash_t *pristine_props,
1120                     apr_hash_t *actual_props,
1121                     const apr_array_header_t *propchanges,
1122                     apr_pool_t *result_pool,
1123                     apr_pool_t *scratch_pool)
1124 {
1125   apr_pool_t *iterpool;
1126   int i;
1127   apr_hash_t *conflict_props = NULL;
1128   apr_hash_t *their_props;
1129
1130   SVN_ERR_ASSERT(pristine_props != NULL);
1131   SVN_ERR_ASSERT(actual_props != NULL);
1132
1133   *new_actual_props = apr_hash_copy(result_pool, actual_props);
1134
1135   if (!server_baseprops)
1136     server_baseprops = pristine_props;
1137
1138   their_props = apr_hash_copy(scratch_pool, server_baseprops);
1139
1140   if (state)
1141     {
1142       /* Start out assuming no changes or conflicts.  Don't bother to
1143          examine propchanges->nelts yet; even if we knew there were
1144          propchanges, we wouldn't yet know if they are "normal" props,
1145          as opposed wc or entry props.  */
1146       *state = svn_wc_notify_state_unchanged;
1147     }
1148
1149   /* Looping over the array of incoming propchanges we want to apply: */
1150   iterpool = svn_pool_create(scratch_pool);
1151   for (i = 0; i < propchanges->nelts; i++)
1152     {
1153       const svn_prop_t *incoming_change
1154         = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1155       const char *propname = incoming_change->name;
1156       const svn_string_t *base_val  /* Pristine in WC */
1157         = svn_hash_gets(pristine_props, propname);
1158       const svn_string_t *from_val  /* Merge left */
1159         = svn_hash_gets(server_baseprops, propname);
1160       const svn_string_t *to_val    /* Merge right */
1161         = incoming_change->value;
1162       const svn_string_t *working_val  /* Mine */
1163         = svn_hash_gets(actual_props, propname);
1164       const svn_string_t *result_val;
1165       svn_boolean_t conflict_remains;
1166       svn_boolean_t did_merge = FALSE;
1167
1168       svn_pool_clear(iterpool);
1169
1170       to_val = to_val ? svn_string_dup(to_val, result_pool) : NULL;
1171
1172       svn_hash_sets(their_props, propname, to_val);
1173
1174
1175       /* We already know that state is at least `changed', so mark
1176          that, but remember that we may later upgrade to `merged' or
1177          even `conflicted'. */
1178       set_prop_merge_state(state, svn_wc_notify_state_changed);
1179
1180       result_val = working_val;
1181
1182       if (! from_val)  /* adding a new property */
1183         SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
1184                                       &did_merge, propname,
1185                                       base_val, to_val, working_val,
1186                                       result_pool, iterpool));
1187
1188       else if (! to_val) /* delete an existing property */
1189         SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
1190                                          &did_merge,
1191                                          base_val, from_val, working_val));
1192
1193       else  /* changing an existing property */
1194         SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
1195                                          &did_merge, propname,
1196                                          base_val, from_val, to_val, working_val,
1197                                          result_pool, iterpool));
1198
1199       if (result_val != working_val)
1200         svn_hash_sets(*new_actual_props, propname, result_val);
1201       if (did_merge)
1202         set_prop_merge_state(state, svn_wc_notify_state_merged);
1203
1204       /* merging logic complete, now we need to possibly log conflict
1205          data to tmpfiles.  */
1206
1207       if (conflict_remains)
1208         {
1209           set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1210
1211           if (!conflict_props)
1212             conflict_props = apr_hash_make(scratch_pool);
1213
1214           svn_hash_sets(conflict_props, propname, "");
1215         }
1216
1217     }  /* foreach propchange ... */
1218   svn_pool_destroy(iterpool);
1219
1220   /* Finished applying all incoming propchanges to our hashes! */
1221
1222   if (conflict_props != NULL)
1223     {
1224       /* Ok, we got some conflict. Lets store all the property knowledge we
1225          have for resolving later */
1226
1227       if (!*conflict_skel)
1228         *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1229
1230       SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
1231                                                       db, local_abspath,
1232                                                       NULL /* reject_path */,
1233                                                       actual_props,
1234                                                       server_baseprops,
1235                                                       their_props,
1236                                                       conflict_props,
1237                                                       result_pool,
1238                                                       scratch_pool));
1239     }
1240
1241   return SVN_NO_ERROR;
1242 }
1243
1244
1245 /* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
1246    If VALUE is null, remove property NAME.  */
1247 static svn_error_t *
1248 wcprop_set(svn_wc__db_t *db,
1249            const char *local_abspath,
1250            const char *name,
1251            const svn_string_t *value,
1252            apr_pool_t *scratch_pool)
1253 {
1254   apr_hash_t *prophash;
1255
1256   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1257
1258   /* Note: this is not well-transacted. But... meh. This is merely a cache,
1259      and if two processes are trying to modify this one entry at the same
1260      time, then fine: we can let one be a winner, and one a loser. Of course,
1261      if there are *other* state changes afoot, then the lack of a txn could
1262      be a real issue, but we cannot solve that here.  */
1263
1264   SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1265                                         scratch_pool, scratch_pool));
1266
1267   if (prophash == NULL)
1268     prophash = apr_hash_make(scratch_pool);
1269
1270   svn_hash_sets(prophash, name, value);
1271   return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
1272                                                        prophash,
1273                                                        scratch_pool));
1274 }
1275
1276
1277 svn_error_t *
1278 svn_wc__get_actual_props(apr_hash_t **props,
1279                          svn_wc__db_t *db,
1280                          const char *local_abspath,
1281                          apr_pool_t *result_pool,
1282                          apr_pool_t *scratch_pool)
1283 {
1284   SVN_ERR_ASSERT(props != NULL);
1285   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1286
1287   /* ### perform some state checking. for example, locally-deleted nodes
1288      ### should not have any ACTUAL props.  */
1289
1290   return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
1291                                                result_pool, scratch_pool));
1292 }
1293
1294
1295 svn_error_t *
1296 svn_wc_prop_list2(apr_hash_t **props,
1297                   svn_wc_context_t *wc_ctx,
1298                   const char *local_abspath,
1299                   apr_pool_t *result_pool,
1300                   apr_pool_t *scratch_pool)
1301 {
1302   return svn_error_trace(svn_wc__get_actual_props(props,
1303                                                   wc_ctx->db,
1304                                                   local_abspath,
1305                                                   result_pool,
1306                                                   scratch_pool));
1307 }
1308
1309 struct propname_filter_baton_t {
1310   svn_wc__proplist_receiver_t receiver_func;
1311   void *receiver_baton;
1312   const char *propname;
1313 };
1314
1315 static svn_error_t *
1316 propname_filter_receiver(void *baton,
1317                          const char *local_abspath,
1318                          apr_hash_t *props,
1319                          apr_pool_t *scratch_pool)
1320 {
1321   struct propname_filter_baton_t *pfb = baton;
1322   const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
1323
1324   if (propval)
1325     {
1326       props = apr_hash_make(scratch_pool);
1327       svn_hash_sets(props, pfb->propname, propval);
1328
1329       SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
1330                                  scratch_pool));
1331     }
1332
1333   return SVN_NO_ERROR;
1334 }
1335
1336 svn_error_t *
1337 svn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
1338                             const char *local_abspath,
1339                             const char *propname,
1340                             svn_depth_t depth,
1341                             svn_boolean_t pristine,
1342                             const apr_array_header_t *changelists,
1343                             svn_wc__proplist_receiver_t receiver_func,
1344                             void *receiver_baton,
1345                             svn_cancel_func_t cancel_func,
1346                             void *cancel_baton,
1347                             apr_pool_t *scratch_pool)
1348 {
1349   svn_wc__proplist_receiver_t receiver = receiver_func;
1350   void *baton = receiver_baton;
1351   struct propname_filter_baton_t pfb;
1352
1353   pfb.receiver_func = receiver_func;
1354   pfb.receiver_baton = receiver_baton;
1355   pfb.propname = propname;
1356
1357   SVN_ERR_ASSERT(receiver_func);
1358
1359   if (propname)
1360     {
1361       baton = &pfb;
1362       receiver = propname_filter_receiver;
1363     }
1364
1365   switch (depth)
1366     {
1367     case svn_depth_empty:
1368       {
1369         apr_hash_t *props;
1370         apr_hash_t *changelist_hash = NULL;
1371
1372         if (changelists && changelists->nelts)
1373           SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1374                                              changelists, scratch_pool));
1375
1376         if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1377                                                changelist_hash, scratch_pool))
1378           break;
1379
1380         if (pristine)
1381           SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
1382                                                  local_abspath,
1383                                                  scratch_pool, scratch_pool));
1384         else
1385           SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
1386                                         scratch_pool, scratch_pool));
1387
1388         if (props && apr_hash_count(props) > 0)
1389           SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
1390       }
1391       break;
1392     case svn_depth_files:
1393     case svn_depth_immediates:
1394     case svn_depth_infinity:
1395       {
1396         SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
1397                                                 depth, pristine,
1398                                                 changelists, receiver, baton,
1399                                                 cancel_func, cancel_baton,
1400                                                 scratch_pool));
1401       }
1402       break;
1403     default:
1404       SVN_ERR_MALFUNCTION();
1405     }
1406
1407   return SVN_NO_ERROR;
1408 }
1409
1410 svn_error_t *
1411 svn_wc__prop_retrieve_recursive(apr_hash_t **values,
1412                                 svn_wc_context_t *wc_ctx,
1413                                 const char *local_abspath,
1414                                 const char *propname,
1415                                 apr_pool_t *result_pool,
1416                                 apr_pool_t *scratch_pool)
1417 {
1418   return svn_error_trace(
1419             svn_wc__db_prop_retrieve_recursive(values,
1420                                                wc_ctx->db,
1421                                                local_abspath,
1422                                                propname,
1423                                                result_pool, scratch_pool));
1424 }
1425
1426 svn_error_t *
1427 svn_wc_get_pristine_props(apr_hash_t **props,
1428                           svn_wc_context_t *wc_ctx,
1429                           const char *local_abspath,
1430                           apr_pool_t *result_pool,
1431                           apr_pool_t *scratch_pool)
1432 {
1433   svn_error_t *err;
1434
1435   SVN_ERR_ASSERT(props != NULL);
1436   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1437
1438   /* Certain node stats do not have properties defined on them. Check the
1439      state, and return NULL for these situations.  */
1440
1441   err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
1442                                        result_pool, scratch_pool);
1443
1444   if (err)
1445     {
1446       if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1447         return svn_error_trace(err);
1448
1449       svn_error_clear(err);
1450
1451       /* Documented behavior is to set *PROPS to NULL */
1452       *props = NULL;
1453     }
1454
1455   return SVN_NO_ERROR;
1456 }
1457
1458 svn_error_t *
1459 svn_wc_prop_get2(const svn_string_t **value,
1460                  svn_wc_context_t *wc_ctx,
1461                  const char *local_abspath,
1462                  const char *name,
1463                  apr_pool_t *result_pool,
1464                  apr_pool_t *scratch_pool)
1465 {
1466   enum svn_prop_kind kind = svn_property_kind2(name);
1467   svn_error_t *err;
1468
1469   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1470
1471   if (kind == svn_prop_entry_kind)
1472     {
1473       /* we don't do entry properties here */
1474       return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1475                                _("Property '%s' is an entry property"), name);
1476     }
1477
1478   err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
1479                                  result_pool, scratch_pool);
1480
1481   if (err)
1482     {
1483       if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1484         return svn_error_trace(err);
1485
1486       svn_error_clear(err);
1487       /* Documented behavior is to set *VALUE to NULL */
1488       *value = NULL;
1489     }
1490
1491   return SVN_NO_ERROR;
1492 }
1493
1494 svn_error_t *
1495 svn_wc__internal_propget(const svn_string_t **value,
1496                          svn_wc__db_t *db,
1497                          const char *local_abspath,
1498                          const char *name,
1499                          apr_pool_t *result_pool,
1500                          apr_pool_t *scratch_pool)
1501 {
1502   apr_hash_t *prophash = NULL;
1503   enum svn_prop_kind kind = svn_property_kind2(name);
1504
1505   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1506   SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
1507
1508   if (kind == svn_prop_wc_kind)
1509     {
1510       SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1511                                               result_pool, scratch_pool),
1512                 _("Failed to load properties"));
1513     }
1514   else
1515     {
1516       /* regular prop */
1517       SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
1518                                          result_pool, scratch_pool),
1519                 _("Failed to load properties"));
1520     }
1521
1522   if (prophash)
1523     *value = svn_hash_gets(prophash, name);
1524   else
1525     *value = NULL;
1526
1527   return SVN_NO_ERROR;
1528 }
1529
1530
1531 /* The special Subversion properties are not valid for all node kinds.
1532    Return an error if NAME is an invalid Subversion property for PATH which
1533    is of kind NODE_KIND.  NAME must be in the "svn:" name space.
1534
1535    Note that we only disallow the property if we're sure it's one that
1536    already has a meaning for a different node kind.  We don't disallow
1537    setting an *unknown* svn: prop here, at this level; a higher level
1538    should disallow that if desired.
1539   */
1540 static svn_error_t *
1541 validate_prop_against_node_kind(const char *name,
1542                                 const char *path,
1543                                 svn_node_kind_t node_kind,
1544                                 apr_pool_t *pool)
1545 {
1546   const char *path_display
1547     = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1548
1549   switch (node_kind)
1550     {
1551     case svn_node_dir:
1552       if (! svn_prop_is_known_svn_dir_prop(name)
1553           && svn_prop_is_known_svn_file_prop(name))
1554         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1555                                  _("Cannot set '%s' on a directory ('%s')"),
1556                                  name, path_display);
1557       break;
1558     case svn_node_file:
1559       if (! svn_prop_is_known_svn_file_prop(name)
1560           && svn_prop_is_known_svn_dir_prop(name))
1561         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1562                                  _("Cannot set '%s' on a file ('%s')"),
1563                                  name,
1564                                  path_display);
1565       break;
1566     default:
1567       return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1568                                _("'%s' is not a file or directory"),
1569                                path_display);
1570     }
1571
1572   return SVN_NO_ERROR;
1573 }
1574
1575
1576 struct getter_baton {
1577   const svn_string_t *mime_type;
1578   const char *local_abspath;
1579 };
1580
1581
1582 /* Provide the MIME_TYPE and/or push the content to STREAM for the file
1583  * referenced by (getter_baton *) BATON.
1584  *
1585  * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
1586 static svn_error_t *
1587 get_file_for_validation(const svn_string_t **mime_type,
1588                         svn_stream_t *stream,
1589                         void *baton,
1590                         apr_pool_t *pool)
1591 {
1592   struct getter_baton *gb = baton;
1593
1594   if (mime_type)
1595     *mime_type = gb->mime_type;
1596
1597   if (stream)
1598     {
1599       svn_stream_t *read_stream;
1600
1601       /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
1602       SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
1603                                        pool, pool));
1604       SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
1605                                NULL, NULL, pool));
1606     }
1607
1608   return SVN_NO_ERROR;
1609 }
1610
1611
1612 /* Validate that a file has a 'non-binary' MIME type and contains
1613  * self-consistent line endings.  If not, then return an error.
1614  *
1615  * Call GETTER (which must not be NULL) with GETTER_BATON to get the
1616  * file's MIME type and/or content.  If the MIME type is non-null and
1617  * is categorized as 'binary' then return an error and do not request
1618  * the file content.
1619  *
1620  * Use PATH (a local path or a URL) only for error messages.
1621  */
1622 static svn_error_t *
1623 validate_eol_prop_against_file(const char *path,
1624                                svn_wc_canonicalize_svn_prop_get_file_t getter,
1625                                void *getter_baton,
1626                                apr_pool_t *pool)
1627 {
1628   svn_stream_t *translating_stream;
1629   svn_error_t *err;
1630   const svn_string_t *mime_type;
1631   const char *path_display
1632     = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1633
1634   /* First just ask the "getter" for the MIME type. */
1635   SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
1636
1637   /* See if this file has been determined to be binary. */
1638   if (mime_type && svn_mime_type_is_binary(mime_type->data))
1639     return svn_error_createf
1640       (SVN_ERR_ILLEGAL_TARGET, NULL,
1641        _("Can't set '%s': "
1642          "file '%s' has binary mime type property"),
1643        SVN_PROP_EOL_STYLE, path_display);
1644
1645   /* Now ask the getter for the contents of the file; this will do a
1646      newline translation.  All we really care about here is whether or
1647      not the function fails on inconsistent line endings.  The
1648      function is "translating" to an empty stream.  This is
1649      sneeeeeeeeeeeaky. */
1650   translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
1651                                                    "", FALSE, NULL, FALSE,
1652                                                    pool);
1653
1654   err = getter(NULL, translating_stream, getter_baton, pool);
1655
1656   err = svn_error_compose_create(err, svn_stream_close(translating_stream));
1657
1658   if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1659     return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
1660                              _("File '%s' has inconsistent newlines"),
1661                              path_display);
1662
1663   return svn_error_trace(err);
1664 }
1665
1666 static svn_error_t *
1667 do_propset(svn_wc__db_t *db,
1668            const char *local_abspath,
1669            svn_node_kind_t kind,
1670            const char *name,
1671            const svn_string_t *value,
1672            svn_boolean_t skip_checks,
1673            svn_wc_notify_func2_t notify_func,
1674            void *notify_baton,
1675            apr_pool_t *scratch_pool)
1676 {
1677   apr_hash_t *prophash;
1678   svn_wc_notify_action_t notify_action;
1679   svn_skel_t *work_item = NULL;
1680   svn_boolean_t clear_recorded_info = FALSE;
1681
1682   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1683
1684   SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
1685                                   scratch_pool, scratch_pool),
1686             _("Failed to load current properties"));
1687
1688   /* Setting an inappropriate property is not allowed (unless
1689      overridden by 'skip_checks', in some circumstances).  Deleting an
1690      inappropriate property is allowed, however, since older clients
1691      allowed (and other clients possibly still allow) setting it in
1692      the first place. */
1693   if (value && svn_prop_is_svn_prop(name))
1694     {
1695       const svn_string_t *new_value;
1696       struct getter_baton gb;
1697
1698       gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
1699       gb.local_abspath = local_abspath;
1700
1701       SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
1702                                            local_abspath, kind,
1703                                            skip_checks,
1704                                            get_file_for_validation, &gb,
1705                                            scratch_pool));
1706       value = new_value;
1707     }
1708
1709   if (kind == svn_node_file
1710         && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
1711             || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
1712     {
1713       SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
1714                                                scratch_pool, scratch_pool));
1715     }
1716
1717   /* If we're changing this file's list of expanded keywords, then
1718    * we'll need to invalidate its text timestamp, since keyword
1719    * expansion affects the comparison of working file to text base.
1720    *
1721    * Here we retrieve the old list of expanded keywords; after the
1722    * property is set, we'll grab the new list and see if it differs
1723    * from the old one.
1724    */
1725   if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
1726     {
1727       svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
1728       apr_hash_t *old_keywords, *new_keywords;
1729
1730       if (old_value)
1731         SVN_ERR(svn_wc__expand_keywords(&old_keywords,
1732                                         db, local_abspath, NULL,
1733                                         old_value->data, TRUE,
1734                                         scratch_pool, scratch_pool));
1735       else
1736         old_keywords = apr_hash_make(scratch_pool);
1737
1738       if (value)
1739         SVN_ERR(svn_wc__expand_keywords(&new_keywords,
1740                                         db, local_abspath, NULL,
1741                                         value->data, TRUE,
1742                                         scratch_pool, scratch_pool));
1743       else
1744         new_keywords = apr_hash_make(scratch_pool);
1745
1746       if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
1747                                      scratch_pool))
1748         {
1749           /* If the keywords have changed, then the translation of the file
1750              may be different. We should invalidate the RECORDED_SIZE
1751              and RECORDED_TIME on this node.
1752
1753              Note that we don't immediately re-translate the file. But a
1754              "has it changed?" check in the future will do a translation
1755              from the pristine, and it will want to compare the (new)
1756              resulting RECORDED_SIZE against the working copy file.
1757
1758              Also, when this file is (de)translated with the new keywords,
1759              then it could be different, relative to the pristine. We want
1760              to ensure the RECORDED_TIME is different, to indicate that
1761              a full detranslate/compare is performed.  */
1762           clear_recorded_info = TRUE;
1763         }
1764     }
1765   else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
1766     {
1767       svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
1768
1769       if (((value == NULL) != (old_value == NULL))
1770           || (value && ! svn_string_compare(value, old_value)))
1771         {
1772           clear_recorded_info = TRUE;
1773         }
1774     }
1775
1776   /* Find out what type of property change we are doing: add, modify, or
1777      delete. */
1778   if (svn_hash_gets(prophash, name) == NULL)
1779     {
1780       if (value == NULL)
1781         /* Deleting a non-existent property. */
1782         notify_action = svn_wc_notify_property_deleted_nonexistent;
1783       else
1784         /* Adding a property. */
1785         notify_action = svn_wc_notify_property_added;
1786     }
1787   else
1788     {
1789       if (value == NULL)
1790         /* Deleting the property. */
1791         notify_action = svn_wc_notify_property_deleted;
1792       else
1793         /* Modifying property. */
1794         notify_action = svn_wc_notify_property_modified;
1795     }
1796
1797   /* Now we have all the properties in our hash.  Simply merge the new
1798      property into it. */
1799   svn_hash_sets(prophash, name, value);
1800
1801   /* Drop it right into the db..  */
1802   SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
1803                                   clear_recorded_info, NULL, work_item,
1804                                   scratch_pool));
1805
1806   /* Run our workqueue item for sync'ing flags with props. */
1807   if (work_item)
1808     SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
1809
1810   if (notify_func)
1811     {
1812       svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
1813                                                      notify_action,
1814                                                      scratch_pool);
1815       notify->prop_name = name;
1816       notify->kind = kind;
1817
1818       (*notify_func)(notify_baton, notify, scratch_pool);
1819     }
1820
1821   return SVN_NO_ERROR;
1822 }
1823
1824 /* A baton for propset_walk_cb. */
1825 struct propset_walk_baton
1826 {
1827   const char *propname;  /* The name of the property to set. */
1828   const svn_string_t *propval;  /* The value to set. */
1829   svn_wc__db_t *db;  /* Database for the tree being walked. */
1830   svn_boolean_t force;  /* True iff force was passed. */
1831   svn_wc_notify_func2_t notify_func;
1832   void *notify_baton;
1833 };
1834
1835 /* An node-walk callback for svn_wc_prop_set4().
1836  *
1837  * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
1838  * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
1839  * propset_walk_baton *".
1840  */
1841 static svn_error_t *
1842 propset_walk_cb(const char *local_abspath,
1843                 svn_node_kind_t kind,
1844                 void *walk_baton,
1845                 apr_pool_t *scratch_pool)
1846 {
1847   struct propset_walk_baton *wb = walk_baton;
1848   svn_error_t *err;
1849
1850   err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
1851                    wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
1852   if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
1853               || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
1854     {
1855       svn_error_clear(err);
1856       err = SVN_NO_ERROR;
1857     }
1858
1859   return svn_error_trace(err);
1860 }
1861
1862 svn_error_t *
1863 svn_wc_prop_set4(svn_wc_context_t *wc_ctx,
1864                  const char *local_abspath,
1865                  const char *name,
1866                  const svn_string_t *value,
1867                  svn_depth_t depth,
1868                  svn_boolean_t skip_checks,
1869                  const apr_array_header_t *changelist_filter,
1870                  svn_cancel_func_t cancel_func,
1871                  void *cancel_baton,
1872                  svn_wc_notify_func2_t notify_func,
1873                  void *notify_baton,
1874                  apr_pool_t *scratch_pool)
1875 {
1876   enum svn_prop_kind prop_kind = svn_property_kind2(name);
1877   svn_wc__db_status_t status;
1878   svn_node_kind_t kind;
1879   svn_wc__db_t *db = wc_ctx->db;
1880
1881   /* we don't do entry properties here */
1882   if (prop_kind == svn_prop_entry_kind)
1883     return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1884                              _("Property '%s' is an entry property"), name);
1885
1886   /* Check to see if we're setting the dav cache. */
1887   if (prop_kind == svn_prop_wc_kind)
1888     {
1889       SVN_ERR_ASSERT(depth == svn_depth_empty);
1890       return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
1891                                         name, value, scratch_pool));
1892     }
1893
1894   SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
1895                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1896                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
1897                                NULL, NULL, NULL, NULL, NULL, NULL,
1898                                wc_ctx->db, local_abspath,
1899                                scratch_pool, scratch_pool));
1900
1901   if (status != svn_wc__db_status_normal
1902       && status != svn_wc__db_status_added
1903       && status != svn_wc__db_status_incomplete)
1904     {
1905       return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
1906                                   _("Can't set properties on '%s':"
1907                                   " invalid status for updating properties."),
1908                                   svn_dirent_local_style(local_abspath,
1909                                                          scratch_pool));
1910     }
1911
1912   /* We have to do this little DIR_ABSPATH dance for backwards compat.
1913      But from 1.7 onwards, all locks are of infinite depth, and from 1.6
1914      backward we never call this API with depth > empty, so we only need
1915      to do the write check once per call, here (and not for every node in
1916      the node walker).
1917
1918      ### Note that we could check for a write lock on local_abspath first
1919      ### if we would want to. And then justy check for kind if that fails.
1920      ### ... but we need kind for the "svn:" property checks anyway */
1921   {
1922     const char *dir_abspath;
1923
1924     if (kind == svn_node_dir)
1925       dir_abspath = local_abspath;
1926     else
1927       dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
1928
1929     /* Verify that we're holding this directory's write lock.  */
1930     SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
1931   }
1932
1933   if (depth == svn_depth_empty || kind != svn_node_dir)
1934     {
1935       apr_hash_t *changelist_hash = NULL;
1936
1937       if (changelist_filter && changelist_filter->nelts)
1938         SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
1939                                            scratch_pool));
1940
1941       if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1942                                              changelist_hash, scratch_pool))
1943         return SVN_NO_ERROR;
1944
1945       SVN_ERR(do_propset(wc_ctx->db, local_abspath,
1946                          kind == svn_node_dir
1947                             ? svn_node_dir
1948                             : svn_node_file,
1949                          name, value, skip_checks,
1950                          notify_func, notify_baton, scratch_pool));
1951
1952     }
1953   else
1954     {
1955       struct propset_walk_baton wb;
1956
1957       wb.propname = name;
1958       wb.propval = value;
1959       wb.db = wc_ctx->db;
1960       wb.force = skip_checks;
1961       wb.notify_func = notify_func;
1962       wb.notify_baton = notify_baton;
1963
1964       SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
1965                                              FALSE, changelist_filter,
1966                                              propset_walk_cb, &wb,
1967                                              depth,
1968                                              cancel_func, cancel_baton,
1969                                              scratch_pool));
1970     }
1971
1972   return SVN_NO_ERROR;
1973 }
1974
1975 /* Check that NAME names a regular prop. Return an error if it names an
1976  * entry prop or a WC prop. */
1977 static svn_error_t *
1978 ensure_prop_is_regular_kind(const char *name)
1979 {
1980   enum svn_prop_kind prop_kind = svn_property_kind2(name);
1981
1982   /* we don't do entry properties here */
1983   if (prop_kind == svn_prop_entry_kind)
1984     return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1985                              _("Property '%s' is an entry property"), name);
1986
1987   /* Check to see if we're setting the dav cache. */
1988   if (prop_kind == svn_prop_wc_kind)
1989     return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1990                              _("Property '%s' is a WC property, not "
1991                                "a regular property"), name);
1992
1993   return SVN_NO_ERROR;
1994 }
1995
1996 svn_error_t *
1997 svn_wc__canonicalize_props(apr_hash_t **prepared_props,
1998                            const char *local_abspath,
1999                            svn_node_kind_t node_kind,
2000                            const apr_hash_t *props,
2001                            svn_boolean_t skip_some_checks,
2002                            apr_pool_t *result_pool,
2003                            apr_pool_t *scratch_pool)
2004 {
2005   const svn_string_t *mime_type;
2006   struct getter_baton gb;
2007   apr_hash_index_t *hi;
2008
2009   /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
2010      don't promise to deep-copy the unchanged keys and values. */
2011   *prepared_props = apr_hash_make(result_pool);
2012
2013   /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
2014    * so process that first. */
2015   mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
2016   if (mime_type)
2017     {
2018       SVN_ERR(svn_wc_canonicalize_svn_prop(
2019                 &mime_type, SVN_PROP_MIME_TYPE, mime_type,
2020                 local_abspath, node_kind, skip_some_checks,
2021                 NULL, NULL, scratch_pool));
2022       svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
2023     }
2024
2025   /* Set up the context for canonicalizing the other properties. */
2026   gb.mime_type = mime_type;
2027   gb.local_abspath = local_abspath;
2028
2029   /* Check and canonicalize the other properties. */
2030   for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
2031        hi = apr_hash_next(hi))
2032     {
2033       const char *name = svn__apr_hash_index_key(hi);
2034       const svn_string_t *value = svn__apr_hash_index_val(hi);
2035
2036       if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
2037         continue;
2038
2039       SVN_ERR(ensure_prop_is_regular_kind(name));
2040       SVN_ERR(svn_wc_canonicalize_svn_prop(
2041                 &value, name, value,
2042                 local_abspath, node_kind, skip_some_checks,
2043                 get_file_for_validation, &gb, scratch_pool));
2044       svn_hash_sets(*prepared_props, name, value);
2045     }
2046
2047   return SVN_NO_ERROR;
2048 }
2049
2050
2051 svn_error_t *
2052 svn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2053                              const char *propname,
2054                              const svn_string_t *propval,
2055                              const char *path,
2056                              svn_node_kind_t kind,
2057                              svn_boolean_t skip_some_checks,
2058                              svn_wc_canonicalize_svn_prop_get_file_t getter,
2059                              void *getter_baton,
2060                              apr_pool_t *pool)
2061 {
2062   svn_stringbuf_t *new_value = NULL;
2063
2064   /* Keep this static, it may get stored (for read-only purposes) in a
2065      hash that outlives this function. */
2066   static const svn_string_t boolean_value =
2067     {
2068       SVN_PROP_BOOLEAN_TRUE,
2069       sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
2070     };
2071
2072   SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2073
2074   /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
2075   if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2076     {
2077       svn_subst_eol_style_t eol_style;
2078       const char *ignored_eol;
2079       new_value = svn_stringbuf_create_from_string(propval, pool);
2080       svn_stringbuf_strip_whitespace(new_value);
2081       svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2082       if (eol_style == svn_subst_eol_style_unknown)
2083         return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2084                                  _("Unrecognized line ending style '%s' for '%s'"),
2085                                  new_value->data,
2086                                  svn_dirent_local_style(path, pool));
2087       SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2088                                              pool));
2089     }
2090   else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2091     {
2092       new_value = svn_stringbuf_create_from_string(propval, pool);
2093       svn_stringbuf_strip_whitespace(new_value);
2094       SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2095     }
2096   else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2097            || strcmp(propname, SVN_PROP_EXTERNALS) == 0
2098            || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
2099            || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
2100     {
2101       /* Make sure that the last line ends in a newline */
2102       if (propval->len == 0
2103           || propval->data[propval->len - 1] != '\n')
2104         {
2105           new_value = svn_stringbuf_create_from_string(propval, pool);
2106           svn_stringbuf_appendbyte(new_value, '\n');
2107         }
2108
2109       /* Make sure this is a valid externals property.  Do not
2110          allow 'skip_some_checks' to override, as there is no circumstance in
2111          which this is proper (because there is no circumstance in
2112          which Subversion can handle it). */
2113       if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2114         {
2115           /* We don't allow "." nor ".." as target directories in
2116              an svn:externals line.  As it happens, our parse code
2117              checks for this, so all we have to is invoke it --
2118              we're not interested in the parsed result, only in
2119              whether or not the parsing errored. */
2120           apr_array_header_t *externals = NULL;
2121           apr_array_header_t *duplicate_targets = NULL;
2122           SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
2123                                                       propval->data, FALSE,
2124                                                       /*scratch_*/pool));
2125           SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2126                                                      externals,
2127                                                      /*scratch_*/pool,
2128                                                      /*scratch_*/pool));
2129           if (duplicate_targets && duplicate_targets->nelts > 0)
2130             {
2131               const char *more_str = "";
2132               if (duplicate_targets->nelts > 1)
2133                 {
2134                   more_str = apr_psprintf(/*scratch_*/pool,
2135                                _(" (%d more duplicate targets found)"),
2136                                duplicate_targets->nelts - 1);
2137                 }
2138               return svn_error_createf(
2139                 SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2140                 _("Invalid %s property on '%s': "
2141                   "target '%s' appears more than once%s"),
2142                 SVN_PROP_EXTERNALS,
2143                 svn_dirent_local_style(path, pool),
2144                 APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2145                 more_str);
2146             }
2147         }
2148     }
2149   else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2150     {
2151       new_value = svn_stringbuf_create_from_string(propval, pool);
2152       svn_stringbuf_strip_whitespace(new_value);
2153     }
2154   else if (svn_prop_is_boolean(propname))
2155     {
2156       /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2157       propval = &boolean_value;
2158     }
2159   else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2160     {
2161       apr_hash_t *mergeinfo;
2162       svn_string_t *new_value_str;
2163
2164       SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2165
2166       /* Non-inheritable mergeinfo is only valid on directories. */
2167       if (kind != svn_node_dir
2168           && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2169         return svn_error_createf(
2170           SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2171           _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2172           svn_dirent_local_style(path, pool));
2173
2174       SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2175       propval = new_value_str;
2176     }
2177
2178   if (new_value)
2179     *propval_p = svn_stringbuf__morph_into_string(new_value);
2180   else
2181     *propval_p = propval;
2182
2183   return SVN_NO_ERROR;
2184 }
2185
2186
2187 svn_boolean_t
2188 svn_wc_is_normal_prop(const char *name)
2189 {
2190   enum svn_prop_kind kind = svn_property_kind2(name);
2191   return (kind == svn_prop_regular_kind);
2192 }
2193
2194
2195 svn_boolean_t
2196 svn_wc_is_wc_prop(const char *name)
2197 {
2198   enum svn_prop_kind kind = svn_property_kind2(name);
2199   return (kind == svn_prop_wc_kind);
2200 }
2201
2202
2203 svn_boolean_t
2204 svn_wc_is_entry_prop(const char *name)
2205 {
2206   enum svn_prop_kind kind = svn_property_kind2(name);
2207   return (kind == svn_prop_entry_kind);
2208 }
2209
2210
2211 svn_error_t *
2212 svn_wc__props_modified(svn_boolean_t *modified_p,
2213                        svn_wc__db_t *db,
2214                        const char *local_abspath,
2215                        apr_pool_t *scratch_pool)
2216 {
2217   SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2218                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2219                                NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2220                                NULL, NULL, modified_p, NULL, NULL, NULL,
2221                                db, local_abspath,
2222                                scratch_pool, scratch_pool));
2223
2224   return SVN_NO_ERROR;
2225 }
2226
2227 svn_error_t *
2228 svn_wc_props_modified_p2(svn_boolean_t *modified_p,
2229                          svn_wc_context_t* wc_ctx,
2230                          const char *local_abspath,
2231                          apr_pool_t *scratch_pool)
2232 {
2233   return svn_error_trace(
2234              svn_wc__props_modified(modified_p,
2235                                     wc_ctx->db,
2236                                     local_abspath,
2237                                     scratch_pool));
2238 }
2239
2240 svn_error_t *
2241 svn_wc__internal_propdiff(apr_array_header_t **propchanges,
2242                           apr_hash_t **original_props,
2243                           svn_wc__db_t *db,
2244                           const char *local_abspath,
2245                           apr_pool_t *result_pool,
2246                           apr_pool_t *scratch_pool)
2247 {
2248   apr_hash_t *baseprops;
2249
2250   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2251
2252   /* ### if pristines are not defined, then should this raise an error,
2253      ### or use an empty set?  */
2254   SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2255                                          result_pool, scratch_pool));
2256
2257   if (original_props != NULL)
2258     *original_props = baseprops;
2259
2260   if (propchanges != NULL)
2261     {
2262       apr_hash_t *actual_props;
2263
2264       /* Some nodes do not have pristine props, so let's just use an empty
2265          set here. Thus, any ACTUAL props are additions.  */
2266       if (baseprops == NULL)
2267         baseprops = apr_hash_make(scratch_pool);
2268
2269       SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2270                                     result_pool, scratch_pool));
2271       /* ### be wary. certain nodes don't have ACTUAL props either. we
2272          ### may want to raise an error. or maybe that is a deletion of
2273          ### any potential pristine props?  */
2274
2275       SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2276                              result_pool));
2277     }
2278
2279   return SVN_NO_ERROR;
2280 }
2281
2282 svn_error_t *
2283 svn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2284                        apr_hash_t **original_props,
2285                        svn_wc_context_t *wc_ctx,
2286                        const char *local_abspath,
2287                        apr_pool_t *result_pool,
2288                        apr_pool_t *scratch_pool)
2289 {
2290   return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2291                                     original_props, wc_ctx->db, local_abspath,
2292                                     result_pool, scratch_pool));
2293 }
2294
2295 svn_boolean_t
2296 svn_wc__has_magic_property(const apr_array_header_t *properties)
2297 {
2298   int i;
2299
2300   for (i = 0; i < properties->nelts; i++)
2301     {
2302       const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2303
2304       if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2305           || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2306           || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2307           || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2308           || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2309         return TRUE;
2310     }
2311   return FALSE;
2312 }
2313
2314 svn_error_t *
2315 svn_wc__get_iprops(apr_array_header_t **inherited_props,
2316                    svn_wc_context_t *wc_ctx,
2317                    const char *local_abspath,
2318                    const char *propname,
2319                    apr_pool_t *result_pool,
2320                    apr_pool_t *scratch_pool)
2321 {
2322   return svn_error_trace(
2323             svn_wc__db_read_inherited_props(inherited_props, NULL,
2324                                             wc_ctx->db, local_abspath,
2325                                             propname,
2326                                             result_pool, scratch_pool));
2327 }
2328
2329 svn_error_t *
2330 svn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2331                                   svn_depth_t depth,
2332                                   svn_wc_context_t *wc_ctx,
2333                                   const char *local_abspath,
2334                                   apr_pool_t *result_pool,
2335                                   apr_pool_t *scratch_pool)
2336 {
2337   SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2338                                                      depth,
2339                                                      local_abspath,
2340                                                      wc_ctx->db,
2341                                                      result_pool,
2342                                                      scratch_pool));
2343   return SVN_NO_ERROR;
2344 }