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