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