1251881Speter/*
2251881Speter * props.c :  routines dealing with properties in the working copy
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter
25251881Speter
26251881Speter#include <stdlib.h>
27251881Speter#include <string.h>
28251881Speter
29251881Speter#include <apr_pools.h>
30251881Speter#include <apr_hash.h>
31251881Speter#include <apr_tables.h>
32251881Speter#include <apr_file_io.h>
33251881Speter#include <apr_strings.h>
34251881Speter#include <apr_general.h>
35251881Speter
36251881Speter#include "svn_types.h"
37251881Speter#include "svn_string.h"
38251881Speter#include "svn_pools.h"
39251881Speter#include "svn_dirent_uri.h"
40251881Speter#include "svn_path.h"
41251881Speter#include "svn_error.h"
42251881Speter#include "svn_props.h"
43251881Speter#include "svn_io.h"
44251881Speter#include "svn_hash.h"
45251881Speter#include "svn_mergeinfo.h"
46251881Speter#include "svn_wc.h"
47251881Speter#include "svn_utf.h"
48251881Speter#include "svn_diff.h"
49251881Speter#include "svn_sorts.h"
50251881Speter
51251881Speter#include "private/svn_wc_private.h"
52251881Speter#include "private/svn_mergeinfo_private.h"
53251881Speter#include "private/svn_skel.h"
54251881Speter#include "private/svn_string_private.h"
55251881Speter#include "private/svn_subr_private.h"
56251881Speter
57251881Speter#include "wc.h"
58251881Speter#include "props.h"
59251881Speter#include "translate.h"
60251881Speter#include "workqueue.h"
61251881Speter#include "conflicts.h"
62251881Speter
63251881Speter#include "svn_private_config.h"
64251881Speter
65251881Speter/*---------------------------------------------------------------------*/
66251881Speter
67251881Speter/*** Merging propchanges into the working copy ***/
68251881Speter
69251881Speter
70251881Speter/* Parse FROM_PROP_VAL and TO_PROP_VAL into mergeinfo hashes, and
71251881Speter   calculate the deltas between them. */
72251881Speterstatic svn_error_t *
73251881Speterdiff_mergeinfo_props(svn_mergeinfo_t *deleted, svn_mergeinfo_t *added,
74251881Speter                     const svn_string_t *from_prop_val,
75251881Speter                     const svn_string_t *to_prop_val, apr_pool_t *pool)
76251881Speter{
77251881Speter  if (svn_string_compare(from_prop_val, to_prop_val))
78251881Speter    {
79251881Speter      /* Don't bothering parsing identical mergeinfo. */
80251881Speter      *deleted = apr_hash_make(pool);
81251881Speter      *added = apr_hash_make(pool);
82251881Speter    }
83251881Speter  else
84251881Speter    {
85251881Speter      svn_mergeinfo_t from, to;
86251881Speter      SVN_ERR(svn_mergeinfo_parse(&from, from_prop_val->data, pool));
87251881Speter      SVN_ERR(svn_mergeinfo_parse(&to, to_prop_val->data, pool));
88251881Speter      SVN_ERR(svn_mergeinfo_diff2(deleted, added, from, to,
89251881Speter                                  TRUE, pool, pool));
90251881Speter    }
91251881Speter  return SVN_NO_ERROR;
92251881Speter}
93251881Speter
94251881Speter/* Parse the mergeinfo from PROP_VAL1 and PROP_VAL2, combine it, then
95251881Speter   reconstitute it into *OUTPUT.  Call when the WC's mergeinfo has
96251881Speter   been modified to combine it with incoming mergeinfo from the
97251881Speter   repos. */
98251881Speterstatic svn_error_t *
99251881Spetercombine_mergeinfo_props(const svn_string_t **output,
100251881Speter                        const svn_string_t *prop_val1,
101251881Speter                        const svn_string_t *prop_val2,
102251881Speter                        apr_pool_t *result_pool,
103251881Speter                        apr_pool_t *scratch_pool)
104251881Speter{
105251881Speter  svn_mergeinfo_t mergeinfo1, mergeinfo2;
106251881Speter  svn_string_t *mergeinfo_string;
107251881Speter
108251881Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo1, prop_val1->data, scratch_pool));
109251881Speter  SVN_ERR(svn_mergeinfo_parse(&mergeinfo2, prop_val2->data, scratch_pool));
110251881Speter  SVN_ERR(svn_mergeinfo_merge2(mergeinfo1, mergeinfo2, scratch_pool,
111251881Speter                               scratch_pool));
112251881Speter  SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, mergeinfo1, result_pool));
113251881Speter  *output = mergeinfo_string;
114251881Speter  return SVN_NO_ERROR;
115251881Speter}
116251881Speter
117251881Speter/* Perform a 3-way merge operation on mergeinfo.  FROM_PROP_VAL is
118251881Speter   the "base" property value, WORKING_PROP_VAL is the current value,
119251881Speter   and TO_PROP_VAL is the new value. */
120251881Speterstatic svn_error_t *
121251881Spetercombine_forked_mergeinfo_props(const svn_string_t **output,
122251881Speter                               const svn_string_t *from_prop_val,
123251881Speter                               const svn_string_t *working_prop_val,
124251881Speter                               const svn_string_t *to_prop_val,
125251881Speter                               apr_pool_t *result_pool,
126251881Speter                               apr_pool_t *scratch_pool)
127251881Speter{
128251881Speter  svn_mergeinfo_t from_mergeinfo, l_deleted, l_added, r_deleted, r_added;
129251881Speter  svn_string_t *mergeinfo_string;
130251881Speter
131251881Speter  /* ### OPTIMIZE: Use from_mergeinfo when diff'ing. */
132251881Speter  SVN_ERR(diff_mergeinfo_props(&l_deleted, &l_added, from_prop_val,
133251881Speter                               working_prop_val, scratch_pool));
134251881Speter  SVN_ERR(diff_mergeinfo_props(&r_deleted, &r_added, from_prop_val,
135251881Speter                               to_prop_val, scratch_pool));
136251881Speter  SVN_ERR(svn_mergeinfo_merge2(l_deleted, r_deleted,
137251881Speter                               scratch_pool, scratch_pool));
138251881Speter  SVN_ERR(svn_mergeinfo_merge2(l_added, r_added,
139251881Speter                               scratch_pool, scratch_pool));
140251881Speter
141251881Speter  /* Apply the combined deltas to the base. */
142251881Speter  SVN_ERR(svn_mergeinfo_parse(&from_mergeinfo, from_prop_val->data,
143251881Speter                              scratch_pool));
144251881Speter  SVN_ERR(svn_mergeinfo_merge2(from_mergeinfo, l_added,
145251881Speter                               scratch_pool, scratch_pool));
146251881Speter
147251881Speter  SVN_ERR(svn_mergeinfo_remove2(&from_mergeinfo, l_deleted, from_mergeinfo,
148251881Speter                                TRUE, scratch_pool, scratch_pool));
149251881Speter
150251881Speter  SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string, from_mergeinfo,
151251881Speter                                  result_pool));
152251881Speter  *output = mergeinfo_string;
153251881Speter  return SVN_NO_ERROR;
154251881Speter}
155251881Speter
156251881Speter
157251881Spetersvn_error_t *
158251881Spetersvn_wc_merge_props3(svn_wc_notify_state_t *state,
159251881Speter                    svn_wc_context_t *wc_ctx,
160251881Speter                    const char *local_abspath,
161251881Speter                    const svn_wc_conflict_version_t *left_version,
162251881Speter                    const svn_wc_conflict_version_t *right_version,
163251881Speter                    apr_hash_t *baseprops,
164251881Speter                    const apr_array_header_t *propchanges,
165251881Speter                    svn_boolean_t dry_run,
166251881Speter                    svn_wc_conflict_resolver_func2_t conflict_func,
167251881Speter                    void *conflict_baton,
168251881Speter                    svn_cancel_func_t cancel_func,
169251881Speter                    void *cancel_baton,
170251881Speter                    apr_pool_t *scratch_pool)
171251881Speter{
172251881Speter  int i;
173251881Speter  svn_wc__db_status_t status;
174251881Speter  svn_node_kind_t kind;
175251881Speter  apr_hash_t *pristine_props = NULL;
176251881Speter  apr_hash_t *actual_props;
177251881Speter  apr_hash_t *new_actual_props;
178251881Speter  svn_boolean_t had_props, props_mod;
179251881Speter  svn_boolean_t have_base;
180251881Speter  svn_boolean_t conflicted;
181251881Speter  svn_skel_t *work_items = NULL;
182251881Speter  svn_skel_t *conflict_skel = NULL;
183251881Speter  svn_wc__db_t *db = wc_ctx->db;
184251881Speter
185251881Speter  /* IMPORTANT: svn_wc_merge_prop_diffs relies on the fact that baseprops
186251881Speter     may be NULL. */
187251881Speter
188251881Speter  SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
189251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
190251881Speter                               NULL, NULL, NULL, NULL, NULL, &conflicted, NULL,
191251881Speter                               &had_props, &props_mod, &have_base, NULL, NULL,
192251881Speter                               db, local_abspath,
193251881Speter                               scratch_pool, scratch_pool));
194251881Speter
195251881Speter  /* Checks whether the node exists and returns the hidden flag */
196251881Speter  if (status == svn_wc__db_status_not_present
197251881Speter      || status == svn_wc__db_status_server_excluded
198251881Speter      || status == svn_wc__db_status_excluded)
199251881Speter    {
200251881Speter      return svn_error_createf(
201251881Speter                    SVN_ERR_WC_PATH_NOT_FOUND, NULL,
202251881Speter                    _("The node '%s' was not found."),
203251881Speter                    svn_dirent_local_style(local_abspath, scratch_pool));
204251881Speter    }
205251881Speter  else if (status != svn_wc__db_status_normal
206251881Speter           && status != svn_wc__db_status_added
207251881Speter           && status != svn_wc__db_status_incomplete)
208251881Speter    {
209251881Speter      return svn_error_createf(
210251881Speter                    SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
211251881Speter                    _("The node '%s' does not have properties in this state."),
212251881Speter                    svn_dirent_local_style(local_abspath, scratch_pool));
213251881Speter    }
214251881Speter  else if (conflicted)
215251881Speter      {
216251881Speter        svn_boolean_t text_conflicted;
217251881Speter        svn_boolean_t prop_conflicted;
218251881Speter        svn_boolean_t tree_conflicted;
219251881Speter
220251881Speter        SVN_ERR(svn_wc__internal_conflicted_p(&text_conflicted,
221251881Speter                                              &prop_conflicted,
222251881Speter                                              &tree_conflicted,
223251881Speter                                              db, local_abspath,
224251881Speter                                              scratch_pool));
225251881Speter
226251881Speter        /* We can't install two text/prop conflicts on a single node, so
227251881Speter           avoid even checking that we have to merge it */
228251881Speter        if (text_conflicted || prop_conflicted || tree_conflicted)
229251881Speter          {
230251881Speter            return svn_error_createf(
231251881Speter                            SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
232251881Speter                            _("Can't merge into conflicted node '%s'"),
233251881Speter                            svn_dirent_local_style(local_abspath,
234251881Speter                                                   scratch_pool));
235251881Speter          }
236251881Speter        /* else: Conflict was resolved by removing markers */
237251881Speter      }
238251881Speter
239251881Speter  /* The PROPCHANGES may not have non-"normal" properties in it. If entry
240251881Speter     or wc props were allowed, then the following code would install them
241251881Speter     into the BASE and/or WORKING properties(!).  */
242251881Speter  for (i = propchanges->nelts; i--; )
243251881Speter    {
244251881Speter      const svn_prop_t *change = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
245251881Speter
246251881Speter      if (!svn_wc_is_normal_prop(change->name))
247251881Speter        return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
248251881Speter                                 _("The property '%s' may not be merged "
249251881Speter                                   "into '%s'."),
250251881Speter                                 change->name,
251251881Speter                                 svn_dirent_local_style(local_abspath,
252251881Speter                                                        scratch_pool));
253251881Speter    }
254251881Speter
255251881Speter  if (had_props)
256251881Speter    SVN_ERR(svn_wc__db_read_pristine_props(&pristine_props, db, local_abspath,
257251881Speter                                           scratch_pool, scratch_pool));
258251881Speter  if (pristine_props == NULL)
259251881Speter    pristine_props = apr_hash_make(scratch_pool);
260251881Speter
261251881Speter  if (props_mod)
262251881Speter    SVN_ERR(svn_wc__get_actual_props(&actual_props, db, local_abspath,
263251881Speter                                     scratch_pool, scratch_pool));
264251881Speter  else
265251881Speter    actual_props = pristine_props;
266251881Speter
267251881Speter  /* Note that while this routine does the "real" work, it's only
268251881Speter     prepping tempfiles and writing log commands.  */
269251881Speter  SVN_ERR(svn_wc__merge_props(&conflict_skel, state,
270251881Speter                              &new_actual_props,
271251881Speter                              db, local_abspath,
272251881Speter                              baseprops /* server_baseprops */,
273251881Speter                              pristine_props,
274251881Speter                              actual_props,
275251881Speter                              propchanges,
276251881Speter                              scratch_pool, scratch_pool));
277251881Speter
278251881Speter  if (dry_run)
279251881Speter    {
280251881Speter      return SVN_NO_ERROR;
281251881Speter    }
282251881Speter
283251881Speter  {
284251881Speter    const char *dir_abspath;
285251881Speter
286251881Speter    if (kind == svn_node_dir)
287251881Speter      dir_abspath = local_abspath;
288251881Speter    else
289251881Speter      dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
290251881Speter
291251881Speter    /* Verify that we're holding this directory's write lock.  */
292251881Speter    SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
293251881Speter  }
294251881Speter
295251881Speter  if (conflict_skel)
296251881Speter    {
297251881Speter      svn_skel_t *work_item;
298251881Speter      SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel,
299251881Speter                                                 left_version,
300251881Speter                                                 right_version,
301251881Speter                                                 scratch_pool,
302251881Speter                                                 scratch_pool));
303251881Speter
304251881Speter      SVN_ERR(svn_wc__conflict_create_markers(&work_item,
305251881Speter                                              db, local_abspath,
306251881Speter                                              conflict_skel,
307251881Speter                                              scratch_pool, scratch_pool));
308251881Speter
309251881Speter      work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
310251881Speter    }
311251881Speter
312251881Speter  /* After a (not-dry-run) merge, we ALWAYS have props to save.  */
313251881Speter  SVN_ERR_ASSERT(new_actual_props != NULL);
314251881Speter
315251881Speter  SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, new_actual_props,
316251881Speter                                  svn_wc__has_magic_property(propchanges),
317251881Speter                                  conflict_skel,
318251881Speter                                  work_items,
319251881Speter                                  scratch_pool));
320251881Speter
321251881Speter  if (work_items != NULL)
322251881Speter    SVN_ERR(svn_wc__wq_run(db, local_abspath, cancel_func, cancel_baton,
323251881Speter                           scratch_pool));
324251881Speter
325251881Speter  /* If there is a conflict, try to resolve it. */
326251881Speter  if (conflict_skel && conflict_func)
327251881Speter    {
328251881Speter      svn_boolean_t prop_conflicted;
329251881Speter
330299742Sdim      SVN_ERR(svn_wc__conflict_invoke_resolver(db, local_abspath, kind,
331299742Sdim                                               conflict_skel,
332251881Speter                                               NULL /* merge_options */,
333251881Speter                                               conflict_func, conflict_baton,
334251881Speter                                               cancel_func, cancel_baton,
335251881Speter                                               scratch_pool));
336251881Speter
337251881Speter      /* Reset *STATE if all prop conflicts were resolved. */
338251881Speter      SVN_ERR(svn_wc__internal_conflicted_p(
339251881Speter                NULL, &prop_conflicted, NULL,
340251881Speter                wc_ctx->db, local_abspath, scratch_pool));
341251881Speter      if (! prop_conflicted)
342251881Speter        *state = svn_wc_notify_state_merged;
343251881Speter    }
344251881Speter
345251881Speter  return SVN_NO_ERROR;
346251881Speter}
347251881Speter
348251881Speter
349251881Speter/* Generate a message to describe the property conflict among these four
350251881Speter   values.
351251881Speter
352251881Speter   Note that this function (currently) interprets the property values as
353251881Speter   strings, but they could actually be binary values. We'll keep the
354251881Speter   types as svn_string_t in case we fix this in the future.  */
355251881Speterstatic svn_stringbuf_t *
356251881Spetergenerate_conflict_message(const char *propname,
357251881Speter                          const svn_string_t *original,
358251881Speter                          const svn_string_t *mine,
359251881Speter                          const svn_string_t *incoming,
360251881Speter                          const svn_string_t *incoming_base,
361251881Speter                          apr_pool_t *result_pool)
362251881Speter{
363251881Speter  if (incoming_base == NULL)
364251881Speter    {
365251881Speter      /* Attempting to add the value INCOMING.  */
366251881Speter      SVN_ERR_ASSERT_NO_RETURN(incoming != NULL);
367251881Speter
368251881Speter      if (mine)
369251881Speter        {
370251881Speter          /* To have a conflict, these must be different.  */
371251881Speter          SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(mine, incoming));
372251881Speter
373251881Speter          /* Note that we don't care whether MINE is locally-added or
374251881Speter             edited, or just something different that is a copy of the
375251881Speter             pristine ORIGINAL.  */
376251881Speter          return svn_stringbuf_createf(result_pool,
377251881Speter                                       _("Trying to add new property '%s'\n"
378251881Speter                                         "but the property already exists.\n"),
379251881Speter                                       propname);
380251881Speter        }
381251881Speter
382251881Speter      /* To have a conflict, we must have an ORIGINAL which has been
383251881Speter         locally-deleted.  */
384251881Speter      SVN_ERR_ASSERT_NO_RETURN(original != NULL);
385251881Speter      return svn_stringbuf_createf(result_pool,
386251881Speter                                   _("Trying to add new property '%s'\n"
387251881Speter                                     "but the property has been locally "
388251881Speter                                     "deleted.\n"),
389251881Speter                                   propname);
390251881Speter    }
391251881Speter
392251881Speter  if (incoming == NULL)
393251881Speter    {
394251881Speter      /* Attempting to delete the value INCOMING_BASE.  */
395251881Speter      SVN_ERR_ASSERT_NO_RETURN(incoming_base != NULL);
396251881Speter
397251881Speter      /* Are we trying to delete a local addition? */
398251881Speter      if (original == NULL && mine != NULL)
399251881Speter        return svn_stringbuf_createf(result_pool,
400251881Speter                                     _("Trying to delete property '%s'\n"
401251881Speter                                       "but the property has been locally "
402251881Speter                                       "added.\n"),
403251881Speter                                     propname);
404251881Speter
405251881Speter      /* A conflict can only occur if we originally had the property;
406251881Speter         otherwise, we would have merged the property-delete into the
407251881Speter         non-existent property.  */
408251881Speter      SVN_ERR_ASSERT_NO_RETURN(original != NULL);
409251881Speter
410251881Speter      if (svn_string_compare(original, incoming_base))
411251881Speter        {
412251881Speter          if (mine)
413251881Speter            /* We were trying to delete the correct property, but an edit
414251881Speter               caused the conflict.  */
415251881Speter            return svn_stringbuf_createf(result_pool,
416251881Speter                                         _("Trying to delete property '%s'\n"
417251881Speter                                           "but the property has been locally "
418251881Speter                                           "modified.\n"),
419251881Speter                                         propname);
420251881Speter        }
421251881Speter      else if (mine == NULL)
422251881Speter        {
423251881Speter          /* We were trying to delete the property, but we have locally
424251881Speter             deleted the same property, but with a different value. */
425251881Speter          return svn_stringbuf_createf(result_pool,
426251881Speter                                       _("Trying to delete property '%s'\n"
427251881Speter                                         "but the property has been locally "
428251881Speter                                         "deleted and had a different "
429251881Speter                                         "value.\n"),
430251881Speter                                       propname);
431251881Speter        }
432251881Speter
433251881Speter      /* We were trying to delete INCOMING_BASE but our ORIGINAL is
434251881Speter         something else entirely.  */
435251881Speter      SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
436251881Speter
437251881Speter      return svn_stringbuf_createf(result_pool,
438251881Speter                                   _("Trying to delete property '%s'\n"
439251881Speter                                     "but the local property value is "
440251881Speter                                     "different.\n"),
441251881Speter                                   propname);
442251881Speter    }
443251881Speter
444251881Speter  /* Attempting to change the property from INCOMING_BASE to INCOMING.  */
445251881Speter
446251881Speter  /* If we have a (current) property value, then it should be different
447251881Speter     from the INCOMING_BASE; otherwise, the incoming change would have
448251881Speter     been applied to it.  */
449251881Speter  SVN_ERR_ASSERT_NO_RETURN(!mine || !svn_string_compare(mine, incoming_base));
450251881Speter
451251881Speter  if (original && mine && svn_string_compare(original, mine))
452251881Speter    {
453251881Speter      /* We have an unchanged property, so the original values must
454251881Speter         have been different.  */
455251881Speter      SVN_ERR_ASSERT_NO_RETURN(!svn_string_compare(original, incoming_base));
456251881Speter      return svn_stringbuf_createf(result_pool,
457251881Speter                                   _("Trying to change property '%s'\n"
458251881Speter                                     "but the local property value conflicts "
459251881Speter                                     "with the incoming change.\n"),
460251881Speter                                   propname);
461251881Speter    }
462251881Speter
463251881Speter  if (original && mine)
464251881Speter    return svn_stringbuf_createf(result_pool,
465251881Speter                                 _("Trying to change property '%s'\n"
466251881Speter                                   "but the property has already been locally "
467251881Speter                                   "changed to a different value.\n"),
468251881Speter                                 propname);
469251881Speter
470251881Speter  if (original)
471251881Speter    return svn_stringbuf_createf(result_pool,
472251881Speter                                 _("Trying to change property '%s'\nbut "
473251881Speter                                   "the property has been locally deleted.\n"),
474251881Speter                                 propname);
475251881Speter
476251881Speter  if (mine)
477251881Speter    return svn_stringbuf_createf(result_pool,
478251881Speter                                 _("Trying to change property '%s'\nbut the "
479251881Speter                                   "property has been locally added with a "
480251881Speter                                   "different value.\n"),
481251881Speter                                 propname);
482251881Speter
483251881Speter  return svn_stringbuf_createf(result_pool,
484251881Speter                               _("Trying to change property '%s'\nbut "
485251881Speter                                 "the property does not exist locally.\n"),
486251881Speter                               propname);
487251881Speter}
488251881Speter
489251881Speter
490251881Speter/* SKEL will be one of:
491251881Speter
492251881Speter   ()
493251881Speter   (VALUE)
494251881Speter
495251881Speter   Return NULL for the former (the particular property value was not
496251881Speter   present), and VALUE for the second.  */
497251881Speterstatic const svn_string_t *
498251881Spetermaybe_prop_value(const svn_skel_t *skel,
499251881Speter                 apr_pool_t *result_pool)
500251881Speter{
501251881Speter  if (skel->children == NULL)
502251881Speter    return NULL;
503251881Speter
504251881Speter  return svn_string_ncreate(skel->children->data,
505251881Speter                            skel->children->len,
506251881Speter                            result_pool);
507251881Speter}
508251881Speter
509251881Speter
510299742Sdim/* Create a property rejection description for the specified property.
511299742Sdim   The result will be allocated in RESULT_POOL. */
512251881Speterstatic svn_error_t *
513299742Sdimprop_conflict_new(const svn_string_t **conflict_desc,
514299742Sdim                  const char *propname,
515299742Sdim                  const svn_string_t *original,
516299742Sdim                  const svn_string_t *mine,
517299742Sdim                  const svn_string_t *incoming,
518299742Sdim                  const svn_string_t *incoming_base,
519299742Sdim                  svn_cancel_func_t cancel_func,
520299742Sdim                  void *cancel_baton,
521299742Sdim                  apr_pool_t *result_pool,
522299742Sdim                  apr_pool_t *scratch_pool)
523251881Speter{
524251881Speter  svn_diff_t *diff;
525251881Speter  svn_diff_file_options_t *diff_opts;
526251881Speter  svn_stringbuf_t *buf;
527299742Sdim  svn_boolean_t incoming_base_is_binary;
528251881Speter  svn_boolean_t mine_is_binary;
529251881Speter  svn_boolean_t incoming_is_binary;
530251881Speter
531251881Speter  buf = generate_conflict_message(propname, original, mine, incoming,
532251881Speter                                  incoming_base, scratch_pool);
533251881Speter
534299742Sdim  /* Convert deleted or not-yet-added values to empty-string values, for the
535299742Sdim     purposes of diff generation and binary detection. */
536251881Speter  if (mine == NULL)
537251881Speter    mine = svn_string_create_empty(scratch_pool);
538251881Speter  if (incoming == NULL)
539251881Speter    incoming = svn_string_create_empty(scratch_pool);
540299742Sdim  if (incoming_base == NULL)
541299742Sdim    incoming_base = svn_string_create_empty(scratch_pool);
542251881Speter
543299742Sdim  /* How we render the conflict:
544251881Speter
545299742Sdim     We have four sides: original, mine, incoming_base, incoming.
546299742Sdim     We render the conflict as a 3-way diff.  A diff3 API has three parts,
547299742Sdim     called:
548299742Sdim
549299742Sdim       <<< - original
550299742Sdim       ||| - modified (or "older")
551299742Sdim       === - latest (or "theirs")
552299742Sdim       >>>
553299742Sdim
554299742Sdim     We fill those parts as follows:
555299742Sdim
556299742Sdim       PART      FILLED BY SKEL MEMBER  USER-FACING ROLE
557299742Sdim       ====      =====================  ================
558299742Sdim       original  mine                   was WORKING tree at conflict creation
559299742Sdim       modified  incoming_base          left-hand side of merge
560299742Sdim       latest    incoming               right-hand side of merge
561299742Sdim       (none)    original               was BASE tree at conflict creation
562299742Sdim
563299742Sdim     An 'update -r rN' is treated like a 'merge -r BASE:rN', i.e., in an
564299742Sdim     'update' operation skel->original and skel->incoming_base coincide.
565299742Sdim
566299742Sdim     Note that the term "original" is used both in the skel and in diff3
567299742Sdim     with different meanings.  Note also that the skel's ORIGINAL value was
568299742Sdim     at some point in the BASE tree, but the BASE tree need not have contained
569299742Sdim     the INCOMING_BASE value.
570299742Sdim
571299742Sdim     Yes, it's confusing. */
572299742Sdim
573251881Speter  /* If any of the property values involved in the diff is binary data,
574251881Speter   * do not generate a diff. */
575299742Sdim  incoming_base_is_binary = svn_io_is_binary_data(incoming_base->data,
576299742Sdim                                                  incoming_base->len);
577251881Speter  mine_is_binary = svn_io_is_binary_data(mine->data, mine->len);
578251881Speter  incoming_is_binary = svn_io_is_binary_data(incoming->data, incoming->len);
579251881Speter
580299742Sdim  if (!(incoming_base_is_binary || mine_is_binary || incoming_is_binary))
581251881Speter    {
582251881Speter      diff_opts = svn_diff_file_options_create(scratch_pool);
583251881Speter      diff_opts->ignore_space = svn_diff_file_ignore_space_none;
584251881Speter      diff_opts->ignore_eol_style = FALSE;
585251881Speter      diff_opts->show_c_function = FALSE;
586299742Sdim      /* Pass skel member INCOMING_BASE into the formal parameter ORIGINAL.
587299742Sdim         Ignore the skel member ORIGINAL. */
588299742Sdim      SVN_ERR(svn_diff_mem_string_diff3(&diff, incoming_base, mine, incoming,
589251881Speter                                        diff_opts, scratch_pool));
590251881Speter      if (svn_diff_contains_conflicts(diff))
591251881Speter        {
592251881Speter          svn_stream_t *stream;
593251881Speter          svn_diff_conflict_display_style_t style;
594251881Speter          const char *mine_marker = _("<<<<<<< (local property value)");
595299742Sdim          const char *incoming_marker = _(">>>>>>> (incoming 'changed to' value)");
596299742Sdim          const char *incoming_base_marker = _("||||||| (incoming 'changed from' value)");
597251881Speter          const char *separator = "=======";
598299742Sdim          svn_string_t *incoming_base_ascii =
599299742Sdim            svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming_base->data,
600251881Speter                                                              scratch_pool),
601251881Speter                              scratch_pool);
602251881Speter          svn_string_t *mine_ascii =
603251881Speter            svn_string_create(svn_utf_cstring_from_utf8_fuzzy(mine->data,
604251881Speter                                                              scratch_pool),
605251881Speter                              scratch_pool);
606251881Speter          svn_string_t *incoming_ascii =
607251881Speter            svn_string_create(svn_utf_cstring_from_utf8_fuzzy(incoming->data,
608251881Speter                                                              scratch_pool),
609251881Speter                              scratch_pool);
610251881Speter
611299742Sdim          style = svn_diff_conflict_display_modified_original_latest;
612251881Speter          stream = svn_stream_from_stringbuf(buf, scratch_pool);
613251881Speter          SVN_ERR(svn_stream_skip(stream, buf->len));
614299742Sdim          SVN_ERR(svn_diff_mem_string_output_merge3(stream, diff,
615299742Sdim                                                    incoming_base_ascii,
616251881Speter                                                    mine_ascii,
617251881Speter                                                    incoming_ascii,
618299742Sdim                                                    incoming_base_marker, mine_marker,
619251881Speter                                                    incoming_marker, separator,
620299742Sdim                                                    style,
621299742Sdim                                                    cancel_func, cancel_baton,
622299742Sdim                                                    scratch_pool));
623251881Speter          SVN_ERR(svn_stream_close(stream));
624251881Speter
625251881Speter          *conflict_desc = svn_string_create_from_buf(buf, result_pool);
626251881Speter          return SVN_NO_ERROR;
627251881Speter        }
628251881Speter    }
629251881Speter
630251881Speter  /* If we could not print a conflict diff just print full values . */
631251881Speter  if (mine->len > 0)
632251881Speter    {
633251881Speter      svn_stringbuf_appendcstr(buf, _("Local property value:\n"));
634251881Speter      if (mine_is_binary)
635251881Speter        svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
636251881Speter                                        "binary data\n"));
637251881Speter      else
638251881Speter        svn_stringbuf_appendbytes(buf, mine->data, mine->len);
639251881Speter      svn_stringbuf_appendcstr(buf, "\n");
640251881Speter    }
641251881Speter
642251881Speter  if (incoming->len > 0)
643251881Speter    {
644251881Speter      svn_stringbuf_appendcstr(buf, _("Incoming property value:\n"));
645251881Speter      if (incoming_is_binary)
646251881Speter        svn_stringbuf_appendcstr(buf, _("Cannot display: property value is "
647251881Speter                                        "binary data\n"));
648251881Speter      else
649251881Speter        svn_stringbuf_appendbytes(buf, incoming->data, incoming->len);
650251881Speter      svn_stringbuf_appendcstr(buf, "\n");
651251881Speter    }
652251881Speter
653251881Speter  *conflict_desc = svn_string_create_from_buf(buf, result_pool);
654251881Speter  return SVN_NO_ERROR;
655251881Speter}
656251881Speter
657299742Sdim/* Parse a property conflict description from the provided SKEL.
658299742Sdim   The result includes a descriptive message (see generate_conflict_message)
659299742Sdim   and maybe a diff of property values containing conflict markers.
660299742Sdim   The result will be allocated in RESULT_POOL.
661251881Speter
662299742Sdim   Note: SKEL is a single property conflict of the form:
663299742Sdim
664299742Sdim   ("prop" ([ORIGINAL]) ([MINE]) ([INCOMING]) ([INCOMING_BASE]))
665299742Sdim
666299742Sdim   Note: This is not the same format as the property conflicts we store in
667299742Sdim   wc.db since 1.8. This is the legacy format used in the Workqueue in 1.7-1.8 */
668299742Sdimstatic svn_error_t *
669299742Sdimprop_conflict_from_skel(const svn_string_t **conflict_desc,
670299742Sdim                        const svn_skel_t *skel,
671299742Sdim                        svn_cancel_func_t cancel_func,
672299742Sdim                        void *cancel_baton,
673299742Sdim                        apr_pool_t *result_pool,
674299742Sdim                        apr_pool_t *scratch_pool)
675299742Sdim{
676299742Sdim  const svn_string_t *original;
677299742Sdim  const svn_string_t *mine;
678299742Sdim  const svn_string_t *incoming;
679299742Sdim  const svn_string_t *incoming_base;
680299742Sdim  const char *propname;
681299742Sdim
682299742Sdim  /* Navigate to the property name.  */
683299742Sdim  skel = skel->children->next;
684299742Sdim
685299742Sdim  /* We need to copy these into SCRATCH_POOL in order to nul-terminate
686299742Sdim     the values.  */
687299742Sdim  propname = apr_pstrmemdup(scratch_pool, skel->data, skel->len);
688299742Sdim  original = maybe_prop_value(skel->next, scratch_pool);
689299742Sdim  mine = maybe_prop_value(skel->next->next, scratch_pool);
690299742Sdim  incoming = maybe_prop_value(skel->next->next->next, scratch_pool);
691299742Sdim  incoming_base = maybe_prop_value(skel->next->next->next->next, scratch_pool);
692299742Sdim
693299742Sdim  return svn_error_trace(prop_conflict_new(conflict_desc,
694299742Sdim                                           propname,
695299742Sdim                                           original, mine,
696299742Sdim                                           incoming, incoming_base,
697299742Sdim                                           cancel_func, cancel_baton,
698299742Sdim                                           result_pool, scratch_pool));
699299742Sdim}
700299742Sdim
701251881Speter/* Create a property conflict file at PREJFILE based on the property
702251881Speter   conflicts in CONFLICT_SKEL.  */
703251881Spetersvn_error_t *
704251881Spetersvn_wc__create_prejfile(const char **tmp_prejfile_abspath,
705251881Speter                        svn_wc__db_t *db,
706251881Speter                        const char *local_abspath,
707299742Sdim                        const svn_skel_t *prop_conflict_data,
708299742Sdim                        svn_cancel_func_t cancel_func,
709299742Sdim                        void *cancel_baton,
710251881Speter                        apr_pool_t *result_pool,
711251881Speter                        apr_pool_t *scratch_pool)
712251881Speter{
713251881Speter  const char *tempdir_abspath;
714251881Speter  svn_stream_t *stream;
715251881Speter  const char *temp_abspath;
716251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
717251881Speter  const svn_skel_t *scan;
718251881Speter
719251881Speter  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tempdir_abspath,
720251881Speter                                         db, local_abspath,
721251881Speter                                         iterpool, iterpool));
722251881Speter
723251881Speter  SVN_ERR(svn_stream_open_unique(&stream, &temp_abspath,
724251881Speter                                 tempdir_abspath, svn_io_file_del_none,
725251881Speter                                 scratch_pool, iterpool));
726251881Speter
727299742Sdim  if (prop_conflict_data)
728251881Speter    {
729299742Sdim      for (scan = prop_conflict_data->children->next;
730299742Sdim            scan != NULL; scan = scan->next)
731299742Sdim        {
732299742Sdim          const svn_string_t *conflict_desc;
733251881Speter
734299742Sdim          svn_pool_clear(iterpool);
735299742Sdim
736299742Sdim          SVN_ERR(prop_conflict_from_skel(&conflict_desc, scan,
737299742Sdim                                          cancel_func, cancel_baton,
738299742Sdim                                          iterpool, iterpool));
739299742Sdim
740299742Sdim          SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
741299742Sdim        }
742251881Speter    }
743299742Sdim  else
744299742Sdim    {
745299742Sdim      svn_wc_operation_t operation;
746299742Sdim      apr_hash_index_t *hi;
747299742Sdim      apr_hash_t *old_props;
748299742Sdim      apr_hash_t *mine_props;
749299742Sdim      apr_hash_t *their_original_props;
750299742Sdim      apr_hash_t *their_props;
751299742Sdim      apr_hash_t *conflicted_props;
752299742Sdim      svn_skel_t *conflicts;
753251881Speter
754299742Sdim      SVN_ERR(svn_wc__db_read_conflict(&conflicts, NULL, NULL,
755299742Sdim                                       db, local_abspath,
756299742Sdim                                      scratch_pool, scratch_pool));
757299742Sdim
758299742Sdim      SVN_ERR(svn_wc__conflict_read_info(&operation, NULL, NULL, NULL, NULL,
759299742Sdim                                         db, local_abspath,
760299742Sdim                                         conflicts,
761299742Sdim                                         scratch_pool, scratch_pool));
762299742Sdim
763299742Sdim      SVN_ERR(svn_wc__conflict_read_prop_conflict(NULL,
764299742Sdim                                                  &mine_props,
765299742Sdim                                                  &their_original_props,
766299742Sdim                                                  &their_props,
767299742Sdim                                                  &conflicted_props,
768299742Sdim                                                  db, local_abspath,
769299742Sdim                                                  conflicts,
770299742Sdim                                                  scratch_pool,
771299742Sdim                                                  scratch_pool));
772299742Sdim
773299742Sdim      if (operation == svn_wc_operation_merge)
774299742Sdim        SVN_ERR(svn_wc__db_read_pristine_props(&old_props, db, local_abspath,
775299742Sdim                                                scratch_pool, scratch_pool));
776299742Sdim      else
777299742Sdim        old_props = their_original_props;
778299742Sdim
779299742Sdim      /* ### TODO: Sort conflicts? */
780299742Sdim      for (hi = apr_hash_first(scratch_pool, conflicted_props);
781299742Sdim           hi;
782299742Sdim           hi = apr_hash_next(hi))
783299742Sdim        {
784299742Sdim          const svn_string_t *conflict_desc;
785299742Sdim          const char *propname = apr_hash_this_key(hi);
786299742Sdim          const svn_string_t *old_value;
787299742Sdim          const svn_string_t *mine_value;
788299742Sdim          const svn_string_t *their_value;
789299742Sdim          const svn_string_t *their_original_value;
790299742Sdim
791299742Sdim          svn_pool_clear(iterpool);
792299742Sdim
793299742Sdim          old_value = old_props ? svn_hash_gets(old_props, propname) : NULL;
794299742Sdim          mine_value = mine_props ? svn_hash_gets(mine_props, propname) : NULL;
795299742Sdim          their_value = their_props ? svn_hash_gets(their_props, propname)
796299742Sdim                                    : NULL;
797299742Sdim          their_original_value = their_original_props
798299742Sdim                                    ? svn_hash_gets(their_original_props, propname)
799299742Sdim                                    : NULL;
800299742Sdim
801299742Sdim          SVN_ERR(prop_conflict_new(&conflict_desc,
802299742Sdim                                    propname, old_value, mine_value,
803299742Sdim                                    their_value, their_original_value,
804299742Sdim                                    cancel_func, cancel_baton,
805299742Sdim                                    iterpool, iterpool));
806299742Sdim
807299742Sdim          SVN_ERR(svn_stream_puts(stream, conflict_desc->data));
808299742Sdim        }
809299742Sdim    }
810299742Sdim
811251881Speter  SVN_ERR(svn_stream_close(stream));
812251881Speter
813251881Speter  svn_pool_destroy(iterpool);
814251881Speter
815251881Speter  *tmp_prejfile_abspath = apr_pstrdup(result_pool, temp_abspath);
816251881Speter  return SVN_NO_ERROR;
817251881Speter}
818251881Speter
819251881Speter
820251881Speter/* Set the value of *STATE to NEW_VALUE if STATE is not NULL
821251881Speter * and NEW_VALUE is a higer order value than *STATE's current value
822251881Speter * using this ordering (lower order first):
823251881Speter *
824251881Speter * - unknown, unchanged, inapplicable
825251881Speter * - changed
826251881Speter * - merged
827251881Speter * - missing
828251881Speter * - obstructed
829251881Speter * - conflicted
830251881Speter *
831251881Speter */
832251881Speterstatic void
833251881Speterset_prop_merge_state(svn_wc_notify_state_t *state,
834251881Speter                     svn_wc_notify_state_t new_value)
835251881Speter{
836251881Speter  static char ordering[] =
837251881Speter    { svn_wc_notify_state_unknown,
838251881Speter      svn_wc_notify_state_unchanged,
839251881Speter      svn_wc_notify_state_inapplicable,
840251881Speter      svn_wc_notify_state_changed,
841251881Speter      svn_wc_notify_state_merged,
842251881Speter      svn_wc_notify_state_obstructed,
843251881Speter      svn_wc_notify_state_conflicted };
844251881Speter  int state_pos = 0, i;
845251881Speter
846251881Speter  if (! state)
847251881Speter    return;
848251881Speter
849251881Speter  /* Find *STATE in our ordering */
850251881Speter  for (i = 0; i < sizeof(ordering); i++)
851251881Speter    {
852251881Speter      if (*state == ordering[i])
853251881Speter        {
854251881Speter          state_pos = i;
855251881Speter          break;
856251881Speter        }
857251881Speter    }
858251881Speter
859251881Speter  /* Find NEW_VALUE in our ordering
860251881Speter   * We don't need to look further than where we found *STATE though:
861251881Speter   * If we find our value, it's order is too low.
862251881Speter   * If we don't find it, we'll want to set it, no matter its order.
863251881Speter   */
864251881Speter
865251881Speter  for (i = 0; i <= state_pos; i++)
866251881Speter    {
867251881Speter      if (new_value == ordering[i])
868251881Speter        return;
869251881Speter    }
870251881Speter
871251881Speter  *state = new_value;
872251881Speter}
873251881Speter
874251881Speter/* Apply the addition of a property with name PROPNAME and value NEW_VAL to
875251881Speter * the existing property with value WORKING_VAL, that originally had value
876251881Speter * PRISTINE_VAL.
877251881Speter *
878251881Speter * Sets *RESULT_VAL to the resulting value.
879251881Speter * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
880251881Speter * Sets *DID_MERGE to true if the result is caused by a merge
881251881Speter */
882251881Speterstatic svn_error_t *
883251881Speterapply_single_prop_add(const svn_string_t **result_val,
884251881Speter                      svn_boolean_t *conflict_remains,
885251881Speter                      svn_boolean_t *did_merge,
886251881Speter                      const char *propname,
887251881Speter                      const svn_string_t *pristine_val,
888251881Speter                      const svn_string_t *new_val,
889251881Speter                      const svn_string_t *working_val,
890251881Speter                      apr_pool_t *result_pool,
891251881Speter                      apr_pool_t *scratch_pool)
892251881Speter
893251881Speter{
894251881Speter  *conflict_remains = FALSE;
895251881Speter
896251881Speter  if (working_val)
897251881Speter    {
898251881Speter      /* the property already exists in actual_props... */
899251881Speter
900251881Speter      if (svn_string_compare(working_val, new_val))
901251881Speter        /* The value we want is already there, so it's a merge. */
902251881Speter        *did_merge = TRUE;
903251881Speter
904251881Speter      else
905251881Speter        {
906251881Speter          svn_boolean_t merged_prop = FALSE;
907251881Speter
908251881Speter          /* The WC difference doesn't match the new value.
909251881Speter           We only merge mergeinfo;  other props conflict */
910251881Speter          if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
911251881Speter            {
912251881Speter              const svn_string_t *merged_val;
913251881Speter              svn_error_t *err = combine_mergeinfo_props(&merged_val,
914251881Speter                                                         working_val,
915251881Speter                                                         new_val,
916251881Speter                                                         result_pool,
917251881Speter                                                         scratch_pool);
918251881Speter
919251881Speter              /* Issue #3896 'mergeinfo syntax errors should be treated
920251881Speter                 gracefully': If bogus mergeinfo is present we can't
921251881Speter                 merge intelligently, so raise a conflict instead. */
922251881Speter              if (err)
923251881Speter                {
924251881Speter                  if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
925251881Speter                    svn_error_clear(err);
926251881Speter                  else
927251881Speter                    return svn_error_trace(err);
928251881Speter                  }
929251881Speter              else
930251881Speter                {
931251881Speter                  merged_prop = TRUE;
932251881Speter                  *result_val = merged_val;
933251881Speter                  *did_merge = TRUE;
934251881Speter                }
935251881Speter            }
936251881Speter
937251881Speter          if (!merged_prop)
938251881Speter            *conflict_remains = TRUE;
939251881Speter        }
940251881Speter    }
941251881Speter  else if (pristine_val)
942251881Speter    *conflict_remains = TRUE;
943251881Speter  else  /* property doesn't yet exist in actual_props...  */
944251881Speter    /* so just set it */
945251881Speter    *result_val = new_val;
946251881Speter
947251881Speter  return SVN_NO_ERROR;
948251881Speter}
949251881Speter
950251881Speter
951251881Speter/* Apply the deletion of a property to the existing
952251881Speter * property with value WORKING_VAL, that originally had value PRISTINE_VAL.
953251881Speter *
954251881Speter * Sets *RESULT_VAL to the resulting value.
955251881Speter * Sets *CONFLICT_REMAINS to TRUE if the change caused a conflict.
956251881Speter * Sets *DID_MERGE to true if the result is caused by a merge
957251881Speter */
958251881Speterstatic svn_error_t *
959251881Speterapply_single_prop_delete(const svn_string_t **result_val,
960251881Speter                         svn_boolean_t *conflict_remains,
961251881Speter                         svn_boolean_t *did_merge,
962251881Speter                         const svn_string_t *base_val,
963251881Speter                         const svn_string_t *old_val,
964251881Speter                         const svn_string_t *working_val)
965251881Speter{
966251881Speter  *conflict_remains = FALSE;
967251881Speter
968251881Speter  if (! base_val)
969251881Speter    {
970251881Speter      if (working_val
971251881Speter          && !svn_string_compare(working_val, old_val))
972251881Speter        {
973251881Speter          /* We are trying to delete a locally-added prop. */
974251881Speter          *conflict_remains = TRUE;
975251881Speter        }
976251881Speter      else
977251881Speter        {
978251881Speter          *result_val = NULL;
979251881Speter          if (old_val)
980251881Speter            /* This is a merge, merging a delete into non-existent
981251881Speter               property or a local addition of same prop value. */
982251881Speter            *did_merge = TRUE;
983251881Speter        }
984251881Speter    }
985251881Speter
986251881Speter  else if (svn_string_compare(base_val, old_val))
987251881Speter    {
988251881Speter       if (working_val)
989251881Speter         {
990251881Speter           if (svn_string_compare(working_val, old_val))
991251881Speter             /* they have the same values, so it's an update */
992251881Speter             *result_val = NULL;
993251881Speter           else
994251881Speter             *conflict_remains = TRUE;
995251881Speter         }
996251881Speter       else
997251881Speter         /* The property is locally deleted from the same value, so it's
998251881Speter            a merge */
999251881Speter         *did_merge = TRUE;
1000251881Speter    }
1001251881Speter
1002251881Speter  else
1003251881Speter    *conflict_remains = TRUE;
1004251881Speter
1005251881Speter  return SVN_NO_ERROR;
1006251881Speter}
1007251881Speter
1008251881Speter
1009251881Speter/* Merge a change to the mergeinfo property. Similar to
1010251881Speter   apply_single_prop_change(), except that the property name is always
1011251881Speter   SVN_PROP_MERGEINFO. */
1012251881Speter/* ### This function is extracted straight from the previous all-in-one
1013251881Speter   version of apply_single_prop_change() by removing the code paths that
1014251881Speter   were not followed for this property, but with no attempt to rationalize
1015251881Speter   the remainder. */
1016251881Speterstatic svn_error_t *
1017251881Speterapply_single_mergeinfo_prop_change(const svn_string_t **result_val,
1018251881Speter                                   svn_boolean_t *conflict_remains,
1019251881Speter                                   svn_boolean_t *did_merge,
1020251881Speter                                   const svn_string_t *base_val,
1021251881Speter                                   const svn_string_t *old_val,
1022251881Speter                                   const svn_string_t *new_val,
1023251881Speter                                   const svn_string_t *working_val,
1024251881Speter                                   apr_pool_t *result_pool,
1025251881Speter                                   apr_pool_t *scratch_pool)
1026251881Speter{
1027251881Speter  if ((working_val && ! base_val)
1028251881Speter      || (! working_val && base_val)
1029251881Speter      || (working_val && base_val
1030251881Speter          && !svn_string_compare(working_val, base_val)))
1031251881Speter    {
1032251881Speter      /* Locally changed property */
1033251881Speter      if (working_val)
1034251881Speter        {
1035251881Speter          if (svn_string_compare(working_val, new_val))
1036251881Speter            /* The new value equals the changed value: a no-op merge */
1037251881Speter            *did_merge = TRUE;
1038251881Speter          else
1039251881Speter            {
1040251881Speter              /* We have base, WC, and new values.  Discover
1041251881Speter                 deltas between base <-> WC, and base <->
1042251881Speter                 incoming.  Combine those deltas, and apply
1043251881Speter                 them to base to get the new value. */
1044251881Speter              SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1045251881Speter                                                     working_val,
1046251881Speter                                                     new_val,
1047251881Speter                                                     result_pool,
1048251881Speter                                                     scratch_pool));
1049251881Speter              *result_val = new_val;
1050251881Speter              *did_merge = TRUE;
1051251881Speter            }
1052251881Speter        }
1053251881Speter      else
1054251881Speter        {
1055251881Speter          /* There is a base_val but no working_val */
1056251881Speter          *conflict_remains = TRUE;
1057251881Speter        }
1058251881Speter    }
1059251881Speter
1060251881Speter  else if (! working_val) /* means !working_val && !base_val due
1061251881Speter                             to conditions above: no prop at all */
1062251881Speter    {
1063251881Speter      /* Discover any mergeinfo additions in the
1064251881Speter         incoming value relative to the base, and
1065251881Speter         "combine" those with the empty WC value. */
1066251881Speter      svn_mergeinfo_t deleted_mergeinfo, added_mergeinfo;
1067251881Speter      svn_string_t *mergeinfo_string;
1068251881Speter
1069251881Speter      SVN_ERR(diff_mergeinfo_props(&deleted_mergeinfo,
1070251881Speter                                   &added_mergeinfo,
1071251881Speter                                   old_val, new_val, scratch_pool));
1072251881Speter      SVN_ERR(svn_mergeinfo_to_string(&mergeinfo_string,
1073251881Speter                                      added_mergeinfo, result_pool));
1074251881Speter      *result_val = mergeinfo_string;
1075251881Speter    }
1076251881Speter
1077251881Speter  else /* means working && base && svn_string_compare(working, base) */
1078251881Speter    {
1079251881Speter      if (svn_string_compare(old_val, base_val))
1080251881Speter        *result_val = new_val;
1081251881Speter      else
1082251881Speter        {
1083251881Speter          /* We have base, WC, and new values.  Discover
1084251881Speter             deltas between base <-> WC, and base <->
1085251881Speter             incoming.  Combine those deltas, and apply
1086251881Speter             them to base to get the new value. */
1087251881Speter          SVN_ERR(combine_forked_mergeinfo_props(&new_val, old_val,
1088251881Speter                                                 working_val,
1089251881Speter                                                 new_val, result_pool,
1090251881Speter                                                 scratch_pool));
1091251881Speter          *result_val = new_val;
1092251881Speter          *did_merge = TRUE;
1093251881Speter        }
1094251881Speter    }
1095251881Speter
1096251881Speter  return SVN_NO_ERROR;
1097251881Speter}
1098251881Speter
1099251881Speter/* Merge a change to a property, using the rule that if the working value
1100251881Speter   is equal to the new value then there is nothing we need to do. Else, if
1101251881Speter   the working value is the same as the old value then apply the change as a
1102251881Speter   simple update (replacement), otherwise invoke maybe_generate_propconflict().
1103251881Speter   The definition of the arguments and behaviour is the same as
1104251881Speter   apply_single_prop_change(). */
1105251881Speterstatic svn_error_t *
1106251881Speterapply_single_generic_prop_change(const svn_string_t **result_val,
1107251881Speter                                 svn_boolean_t *conflict_remains,
1108251881Speter                                 svn_boolean_t *did_merge,
1109251881Speter                                 const svn_string_t *old_val,
1110251881Speter                                 const svn_string_t *new_val,
1111251881Speter                                 const svn_string_t *working_val)
1112251881Speter{
1113251881Speter  SVN_ERR_ASSERT(old_val != NULL);
1114251881Speter
1115251881Speter  /* If working_val is the same as new_val already then there is
1116251881Speter   * nothing to do */
1117251881Speter  if (working_val && new_val
1118251881Speter      && svn_string_compare(working_val, new_val))
1119251881Speter    {
1120251881Speter      /* All values identical is a trivial, non-notifiable merge */
1121251881Speter      if (! old_val || ! svn_string_compare(old_val, new_val))
1122251881Speter        *did_merge = TRUE;
1123251881Speter    }
1124251881Speter  /* If working_val is the same as old_val... */
1125251881Speter  else if (working_val && old_val
1126251881Speter      && svn_string_compare(working_val, old_val))
1127251881Speter    {
1128251881Speter      /* A trivial update: change it to new_val. */
1129251881Speter      *result_val = new_val;
1130251881Speter    }
1131251881Speter  else
1132251881Speter    {
1133251881Speter      /* Merge the change. */
1134251881Speter      *conflict_remains = TRUE;
1135251881Speter    }
1136251881Speter
1137251881Speter  return SVN_NO_ERROR;
1138251881Speter}
1139251881Speter
1140251881Speter/* Change the property with name PROPNAME, setting *RESULT_VAL,
1141251881Speter * *CONFLICT_REMAINS and *DID_MERGE according to the merge outcome.
1142251881Speter *
1143251881Speter * BASE_VAL contains the working copy base property value. (May be null.)
1144251881Speter *
1145251881Speter * OLD_VAL contains the value of the property the server
1146251881Speter * thinks it's overwriting. (Not null.)
1147251881Speter *
1148251881Speter * NEW_VAL contains the value to be set. (Not null.)
1149251881Speter *
1150251881Speter * WORKING_VAL contains the working copy actual value. (May be null.)
1151251881Speter */
1152251881Speterstatic svn_error_t *
1153251881Speterapply_single_prop_change(const svn_string_t **result_val,
1154251881Speter                         svn_boolean_t *conflict_remains,
1155251881Speter                         svn_boolean_t *did_merge,
1156251881Speter                         const char *propname,
1157251881Speter                         const svn_string_t *base_val,
1158251881Speter                         const svn_string_t *old_val,
1159251881Speter                         const svn_string_t *new_val,
1160251881Speter                         const svn_string_t *working_val,
1161251881Speter                         apr_pool_t *result_pool,
1162251881Speter                         apr_pool_t *scratch_pool)
1163251881Speter{
1164251881Speter  svn_boolean_t merged_prop = FALSE;
1165251881Speter
1166251881Speter  *conflict_remains = FALSE;
1167251881Speter
1168251881Speter  /* Note: The purpose is to apply the change (old_val -> new_val) onto
1169251881Speter     (working_val). There is no need for base_val to be involved in the
1170251881Speter     process except as a bit of context to help the user understand and
1171251881Speter     resolve any conflict. */
1172251881Speter
1173251881Speter  /* Decide how to merge, based on whether we know anything special about
1174251881Speter     the property. */
1175251881Speter  if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
1176251881Speter    {
1177251881Speter      /* We know how to merge any mergeinfo property change...
1178251881Speter
1179251881Speter         ...But Issue #3896 'mergeinfo syntax errors should be treated
1180251881Speter         gracefully' might thwart us.  If bogus mergeinfo is present we
1181251881Speter         can't merge intelligently, so let the standard method deal with
1182251881Speter         it instead. */
1183251881Speter      svn_error_t *err = apply_single_mergeinfo_prop_change(result_val,
1184251881Speter                                                            conflict_remains,
1185251881Speter                                                            did_merge,
1186251881Speter                                                            base_val,
1187251881Speter                                                            old_val,
1188251881Speter                                                            new_val,
1189251881Speter                                                            working_val,
1190251881Speter                                                            result_pool,
1191251881Speter                                                            scratch_pool);
1192251881Speter       if (err)
1193251881Speter         {
1194251881Speter           if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
1195251881Speter             svn_error_clear(err);
1196251881Speter           else
1197251881Speter             return svn_error_trace(err);
1198251881Speter           }
1199251881Speter       else
1200251881Speter         {
1201251881Speter           merged_prop = TRUE;
1202251881Speter         }
1203251881Speter    }
1204251881Speter
1205251881Speter  if (!merged_prop)
1206251881Speter    {
1207251881Speter      /* The standard method: perform a simple update automatically, but
1208251881Speter         pass any other kind of merge to maybe_generate_propconflict(). */
1209251881Speter
1210251881Speter      SVN_ERR(apply_single_generic_prop_change(result_val, conflict_remains,
1211251881Speter                                               did_merge,
1212251881Speter                                               old_val, new_val, working_val));
1213251881Speter    }
1214251881Speter
1215251881Speter  return SVN_NO_ERROR;
1216251881Speter}
1217251881Speter
1218251881Speter
1219251881Spetersvn_error_t *
1220251881Spetersvn_wc__merge_props(svn_skel_t **conflict_skel,
1221251881Speter                    svn_wc_notify_state_t *state,
1222251881Speter                    apr_hash_t **new_actual_props,
1223251881Speter                    svn_wc__db_t *db,
1224251881Speter                    const char *local_abspath,
1225251881Speter                    apr_hash_t *server_baseprops,
1226251881Speter                    apr_hash_t *pristine_props,
1227251881Speter                    apr_hash_t *actual_props,
1228251881Speter                    const apr_array_header_t *propchanges,
1229251881Speter                    apr_pool_t *result_pool,
1230251881Speter                    apr_pool_t *scratch_pool)
1231251881Speter{
1232251881Speter  apr_pool_t *iterpool;
1233251881Speter  int i;
1234251881Speter  apr_hash_t *conflict_props = NULL;
1235251881Speter  apr_hash_t *their_props;
1236251881Speter
1237251881Speter  SVN_ERR_ASSERT(pristine_props != NULL);
1238251881Speter  SVN_ERR_ASSERT(actual_props != NULL);
1239251881Speter
1240251881Speter  *new_actual_props = apr_hash_copy(result_pool, actual_props);
1241251881Speter
1242251881Speter  if (!server_baseprops)
1243251881Speter    server_baseprops = pristine_props;
1244251881Speter
1245251881Speter  their_props = apr_hash_copy(scratch_pool, server_baseprops);
1246251881Speter
1247251881Speter  if (state)
1248251881Speter    {
1249251881Speter      /* Start out assuming no changes or conflicts.  Don't bother to
1250251881Speter         examine propchanges->nelts yet; even if we knew there were
1251251881Speter         propchanges, we wouldn't yet know if they are "normal" props,
1252251881Speter         as opposed wc or entry props.  */
1253251881Speter      *state = svn_wc_notify_state_unchanged;
1254251881Speter    }
1255251881Speter
1256251881Speter  /* Looping over the array of incoming propchanges we want to apply: */
1257251881Speter  iterpool = svn_pool_create(scratch_pool);
1258251881Speter  for (i = 0; i < propchanges->nelts; i++)
1259251881Speter    {
1260251881Speter      const svn_prop_t *incoming_change
1261251881Speter        = &APR_ARRAY_IDX(propchanges, i, svn_prop_t);
1262251881Speter      const char *propname = incoming_change->name;
1263251881Speter      const svn_string_t *base_val  /* Pristine in WC */
1264251881Speter        = svn_hash_gets(pristine_props, propname);
1265251881Speter      const svn_string_t *from_val  /* Merge left */
1266251881Speter        = svn_hash_gets(server_baseprops, propname);
1267251881Speter      const svn_string_t *to_val    /* Merge right */
1268251881Speter        = incoming_change->value;
1269251881Speter      const svn_string_t *working_val  /* Mine */
1270251881Speter        = svn_hash_gets(actual_props, propname);
1271251881Speter      const svn_string_t *result_val;
1272251881Speter      svn_boolean_t conflict_remains;
1273251881Speter      svn_boolean_t did_merge = FALSE;
1274251881Speter
1275251881Speter      svn_pool_clear(iterpool);
1276251881Speter
1277299742Sdim      to_val = svn_string_dup(to_val, result_pool);
1278251881Speter
1279251881Speter      svn_hash_sets(their_props, propname, to_val);
1280251881Speter
1281251881Speter
1282251881Speter      /* We already know that state is at least `changed', so mark
1283251881Speter         that, but remember that we may later upgrade to `merged' or
1284251881Speter         even `conflicted'. */
1285251881Speter      set_prop_merge_state(state, svn_wc_notify_state_changed);
1286251881Speter
1287251881Speter      result_val = working_val;
1288251881Speter
1289251881Speter      if (! from_val)  /* adding a new property */
1290251881Speter        SVN_ERR(apply_single_prop_add(&result_val, &conflict_remains,
1291251881Speter                                      &did_merge, propname,
1292251881Speter                                      base_val, to_val, working_val,
1293251881Speter                                      result_pool, iterpool));
1294251881Speter
1295251881Speter      else if (! to_val) /* delete an existing property */
1296251881Speter        SVN_ERR(apply_single_prop_delete(&result_val, &conflict_remains,
1297251881Speter                                         &did_merge,
1298251881Speter                                         base_val, from_val, working_val));
1299251881Speter
1300251881Speter      else  /* changing an existing property */
1301251881Speter        SVN_ERR(apply_single_prop_change(&result_val, &conflict_remains,
1302251881Speter                                         &did_merge, propname,
1303251881Speter                                         base_val, from_val, to_val, working_val,
1304251881Speter                                         result_pool, iterpool));
1305251881Speter
1306251881Speter      if (result_val != working_val)
1307251881Speter        svn_hash_sets(*new_actual_props, propname, result_val);
1308251881Speter      if (did_merge)
1309251881Speter        set_prop_merge_state(state, svn_wc_notify_state_merged);
1310251881Speter
1311251881Speter      /* merging logic complete, now we need to possibly log conflict
1312251881Speter         data to tmpfiles.  */
1313251881Speter
1314251881Speter      if (conflict_remains)
1315251881Speter        {
1316251881Speter          set_prop_merge_state(state, svn_wc_notify_state_conflicted);
1317251881Speter
1318251881Speter          if (!conflict_props)
1319251881Speter            conflict_props = apr_hash_make(scratch_pool);
1320251881Speter
1321251881Speter          svn_hash_sets(conflict_props, propname, "");
1322251881Speter        }
1323251881Speter
1324251881Speter    }  /* foreach propchange ... */
1325251881Speter  svn_pool_destroy(iterpool);
1326251881Speter
1327251881Speter  /* Finished applying all incoming propchanges to our hashes! */
1328251881Speter
1329251881Speter  if (conflict_props != NULL)
1330251881Speter    {
1331251881Speter      /* Ok, we got some conflict. Lets store all the property knowledge we
1332251881Speter         have for resolving later */
1333251881Speter
1334251881Speter      if (!*conflict_skel)
1335251881Speter        *conflict_skel = svn_wc__conflict_skel_create(result_pool);
1336251881Speter
1337251881Speter      SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(*conflict_skel,
1338251881Speter                                                      db, local_abspath,
1339251881Speter                                                      NULL /* reject_path */,
1340251881Speter                                                      actual_props,
1341251881Speter                                                      server_baseprops,
1342251881Speter                                                      their_props,
1343251881Speter                                                      conflict_props,
1344251881Speter                                                      result_pool,
1345251881Speter                                                      scratch_pool));
1346251881Speter    }
1347251881Speter
1348251881Speter  return SVN_NO_ERROR;
1349251881Speter}
1350251881Speter
1351251881Speter
1352251881Speter/* Set a single 'wcprop' NAME to VALUE for versioned object LOCAL_ABSPATH.
1353251881Speter   If VALUE is null, remove property NAME.  */
1354251881Speterstatic svn_error_t *
1355251881Speterwcprop_set(svn_wc__db_t *db,
1356251881Speter           const char *local_abspath,
1357251881Speter           const char *name,
1358251881Speter           const svn_string_t *value,
1359251881Speter           apr_pool_t *scratch_pool)
1360251881Speter{
1361251881Speter  apr_hash_t *prophash;
1362251881Speter
1363251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1364251881Speter
1365251881Speter  /* Note: this is not well-transacted. But... meh. This is merely a cache,
1366251881Speter     and if two processes are trying to modify this one entry at the same
1367251881Speter     time, then fine: we can let one be a winner, and one a loser. Of course,
1368251881Speter     if there are *other* state changes afoot, then the lack of a txn could
1369251881Speter     be a real issue, but we cannot solve that here.  */
1370251881Speter
1371251881Speter  SVN_ERR(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1372251881Speter                                        scratch_pool, scratch_pool));
1373251881Speter
1374251881Speter  if (prophash == NULL)
1375251881Speter    prophash = apr_hash_make(scratch_pool);
1376251881Speter
1377251881Speter  svn_hash_sets(prophash, name, value);
1378251881Speter  return svn_error_trace(svn_wc__db_base_set_dav_cache(db, local_abspath,
1379251881Speter                                                       prophash,
1380251881Speter                                                       scratch_pool));
1381251881Speter}
1382251881Speter
1383251881Speter
1384251881Spetersvn_error_t *
1385251881Spetersvn_wc__get_actual_props(apr_hash_t **props,
1386251881Speter                         svn_wc__db_t *db,
1387251881Speter                         const char *local_abspath,
1388251881Speter                         apr_pool_t *result_pool,
1389251881Speter                         apr_pool_t *scratch_pool)
1390251881Speter{
1391251881Speter  SVN_ERR_ASSERT(props != NULL);
1392251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1393251881Speter
1394251881Speter  /* ### perform some state checking. for example, locally-deleted nodes
1395251881Speter     ### should not have any ACTUAL props.  */
1396251881Speter
1397251881Speter  return svn_error_trace(svn_wc__db_read_props(props, db, local_abspath,
1398251881Speter                                               result_pool, scratch_pool));
1399251881Speter}
1400251881Speter
1401251881Speter
1402251881Spetersvn_error_t *
1403251881Spetersvn_wc_prop_list2(apr_hash_t **props,
1404251881Speter                  svn_wc_context_t *wc_ctx,
1405251881Speter                  const char *local_abspath,
1406251881Speter                  apr_pool_t *result_pool,
1407251881Speter                  apr_pool_t *scratch_pool)
1408251881Speter{
1409251881Speter  return svn_error_trace(svn_wc__get_actual_props(props,
1410251881Speter                                                  wc_ctx->db,
1411251881Speter                                                  local_abspath,
1412251881Speter                                                  result_pool,
1413251881Speter                                                  scratch_pool));
1414251881Speter}
1415251881Speter
1416251881Speterstruct propname_filter_baton_t {
1417251881Speter  svn_wc__proplist_receiver_t receiver_func;
1418251881Speter  void *receiver_baton;
1419251881Speter  const char *propname;
1420251881Speter};
1421251881Speter
1422251881Speterstatic svn_error_t *
1423251881Speterpropname_filter_receiver(void *baton,
1424251881Speter                         const char *local_abspath,
1425251881Speter                         apr_hash_t *props,
1426251881Speter                         apr_pool_t *scratch_pool)
1427251881Speter{
1428251881Speter  struct propname_filter_baton_t *pfb = baton;
1429251881Speter  const svn_string_t *propval = svn_hash_gets(props, pfb->propname);
1430251881Speter
1431251881Speter  if (propval)
1432251881Speter    {
1433251881Speter      props = apr_hash_make(scratch_pool);
1434251881Speter      svn_hash_sets(props, pfb->propname, propval);
1435251881Speter
1436251881Speter      SVN_ERR(pfb->receiver_func(pfb->receiver_baton, local_abspath, props,
1437251881Speter                                 scratch_pool));
1438251881Speter    }
1439251881Speter
1440251881Speter  return SVN_NO_ERROR;
1441251881Speter}
1442251881Speter
1443251881Spetersvn_error_t *
1444251881Spetersvn_wc__prop_list_recursive(svn_wc_context_t *wc_ctx,
1445251881Speter                            const char *local_abspath,
1446251881Speter                            const char *propname,
1447251881Speter                            svn_depth_t depth,
1448251881Speter                            svn_boolean_t pristine,
1449251881Speter                            const apr_array_header_t *changelists,
1450251881Speter                            svn_wc__proplist_receiver_t receiver_func,
1451251881Speter                            void *receiver_baton,
1452251881Speter                            svn_cancel_func_t cancel_func,
1453251881Speter                            void *cancel_baton,
1454251881Speter                            apr_pool_t *scratch_pool)
1455251881Speter{
1456251881Speter  svn_wc__proplist_receiver_t receiver = receiver_func;
1457251881Speter  void *baton = receiver_baton;
1458251881Speter  struct propname_filter_baton_t pfb;
1459251881Speter
1460251881Speter  pfb.receiver_func = receiver_func;
1461251881Speter  pfb.receiver_baton = receiver_baton;
1462251881Speter  pfb.propname = propname;
1463251881Speter
1464251881Speter  SVN_ERR_ASSERT(receiver_func);
1465251881Speter
1466251881Speter  if (propname)
1467251881Speter    {
1468251881Speter      baton = &pfb;
1469251881Speter      receiver = propname_filter_receiver;
1470251881Speter    }
1471251881Speter
1472251881Speter  switch (depth)
1473251881Speter    {
1474251881Speter    case svn_depth_empty:
1475251881Speter      {
1476251881Speter        apr_hash_t *props;
1477251881Speter        apr_hash_t *changelist_hash = NULL;
1478251881Speter
1479251881Speter        if (changelists && changelists->nelts)
1480251881Speter          SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1481251881Speter                                             changelists, scratch_pool));
1482251881Speter
1483251881Speter        if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
1484251881Speter                                               changelist_hash, scratch_pool))
1485251881Speter          break;
1486251881Speter
1487251881Speter        if (pristine)
1488251881Speter          SVN_ERR(svn_wc__db_read_pristine_props(&props, wc_ctx->db,
1489251881Speter                                                 local_abspath,
1490251881Speter                                                 scratch_pool, scratch_pool));
1491251881Speter        else
1492251881Speter          SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
1493251881Speter                                        scratch_pool, scratch_pool));
1494251881Speter
1495251881Speter        if (props && apr_hash_count(props) > 0)
1496251881Speter          SVN_ERR(receiver(baton, local_abspath, props, scratch_pool));
1497251881Speter      }
1498251881Speter      break;
1499251881Speter    case svn_depth_files:
1500251881Speter    case svn_depth_immediates:
1501251881Speter    case svn_depth_infinity:
1502251881Speter      {
1503251881Speter        SVN_ERR(svn_wc__db_read_props_streamily(wc_ctx->db, local_abspath,
1504251881Speter                                                depth, pristine,
1505251881Speter                                                changelists, receiver, baton,
1506251881Speter                                                cancel_func, cancel_baton,
1507251881Speter                                                scratch_pool));
1508251881Speter      }
1509251881Speter      break;
1510251881Speter    default:
1511251881Speter      SVN_ERR_MALFUNCTION();
1512251881Speter    }
1513251881Speter
1514251881Speter  return SVN_NO_ERROR;
1515251881Speter}
1516251881Speter
1517251881Spetersvn_error_t *
1518251881Spetersvn_wc__prop_retrieve_recursive(apr_hash_t **values,
1519251881Speter                                svn_wc_context_t *wc_ctx,
1520251881Speter                                const char *local_abspath,
1521251881Speter                                const char *propname,
1522251881Speter                                apr_pool_t *result_pool,
1523251881Speter                                apr_pool_t *scratch_pool)
1524251881Speter{
1525251881Speter  return svn_error_trace(
1526251881Speter            svn_wc__db_prop_retrieve_recursive(values,
1527251881Speter                                               wc_ctx->db,
1528251881Speter                                               local_abspath,
1529251881Speter                                               propname,
1530251881Speter                                               result_pool, scratch_pool));
1531251881Speter}
1532251881Speter
1533251881Spetersvn_error_t *
1534251881Spetersvn_wc_get_pristine_props(apr_hash_t **props,
1535251881Speter                          svn_wc_context_t *wc_ctx,
1536251881Speter                          const char *local_abspath,
1537251881Speter                          apr_pool_t *result_pool,
1538251881Speter                          apr_pool_t *scratch_pool)
1539251881Speter{
1540251881Speter  svn_error_t *err;
1541251881Speter
1542251881Speter  SVN_ERR_ASSERT(props != NULL);
1543251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1544251881Speter
1545251881Speter  /* Certain node stats do not have properties defined on them. Check the
1546251881Speter     state, and return NULL for these situations.  */
1547251881Speter
1548251881Speter  err = svn_wc__db_read_pristine_props(props, wc_ctx->db, local_abspath,
1549251881Speter                                       result_pool, scratch_pool);
1550251881Speter
1551251881Speter  if (err)
1552251881Speter    {
1553251881Speter      if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1554251881Speter        return svn_error_trace(err);
1555251881Speter
1556251881Speter      svn_error_clear(err);
1557251881Speter
1558251881Speter      /* Documented behavior is to set *PROPS to NULL */
1559251881Speter      *props = NULL;
1560251881Speter    }
1561251881Speter
1562251881Speter  return SVN_NO_ERROR;
1563251881Speter}
1564251881Speter
1565251881Spetersvn_error_t *
1566251881Spetersvn_wc_prop_get2(const svn_string_t **value,
1567251881Speter                 svn_wc_context_t *wc_ctx,
1568251881Speter                 const char *local_abspath,
1569251881Speter                 const char *name,
1570251881Speter                 apr_pool_t *result_pool,
1571251881Speter                 apr_pool_t *scratch_pool)
1572251881Speter{
1573251881Speter  enum svn_prop_kind kind = svn_property_kind2(name);
1574251881Speter  svn_error_t *err;
1575251881Speter
1576251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1577251881Speter
1578251881Speter  if (kind == svn_prop_entry_kind)
1579251881Speter    {
1580251881Speter      /* we don't do entry properties here */
1581251881Speter      return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1582251881Speter                               _("Property '%s' is an entry property"), name);
1583251881Speter    }
1584251881Speter
1585251881Speter  err = svn_wc__internal_propget(value, wc_ctx->db, local_abspath, name,
1586251881Speter                                 result_pool, scratch_pool);
1587251881Speter
1588251881Speter  if (err)
1589251881Speter    {
1590251881Speter      if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1591251881Speter        return svn_error_trace(err);
1592251881Speter
1593251881Speter      svn_error_clear(err);
1594251881Speter      /* Documented behavior is to set *VALUE to NULL */
1595251881Speter      *value = NULL;
1596251881Speter    }
1597251881Speter
1598251881Speter  return SVN_NO_ERROR;
1599251881Speter}
1600251881Speter
1601251881Spetersvn_error_t *
1602251881Spetersvn_wc__internal_propget(const svn_string_t **value,
1603251881Speter                         svn_wc__db_t *db,
1604251881Speter                         const char *local_abspath,
1605251881Speter                         const char *name,
1606251881Speter                         apr_pool_t *result_pool,
1607251881Speter                         apr_pool_t *scratch_pool)
1608251881Speter{
1609251881Speter  apr_hash_t *prophash = NULL;
1610251881Speter  enum svn_prop_kind kind = svn_property_kind2(name);
1611251881Speter
1612251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1613251881Speter  SVN_ERR_ASSERT(kind != svn_prop_entry_kind);
1614251881Speter
1615251881Speter  if (kind == svn_prop_wc_kind)
1616251881Speter    {
1617251881Speter      SVN_ERR_W(svn_wc__db_base_get_dav_cache(&prophash, db, local_abspath,
1618251881Speter                                              result_pool, scratch_pool),
1619251881Speter                _("Failed to load properties"));
1620251881Speter    }
1621251881Speter  else
1622251881Speter    {
1623251881Speter      /* regular prop */
1624251881Speter      SVN_ERR_W(svn_wc__get_actual_props(&prophash, db, local_abspath,
1625251881Speter                                         result_pool, scratch_pool),
1626251881Speter                _("Failed to load properties"));
1627251881Speter    }
1628251881Speter
1629251881Speter  if (prophash)
1630251881Speter    *value = svn_hash_gets(prophash, name);
1631251881Speter  else
1632251881Speter    *value = NULL;
1633251881Speter
1634251881Speter  return SVN_NO_ERROR;
1635251881Speter}
1636251881Speter
1637251881Speter
1638251881Speter/* The special Subversion properties are not valid for all node kinds.
1639251881Speter   Return an error if NAME is an invalid Subversion property for PATH which
1640251881Speter   is of kind NODE_KIND.  NAME must be in the "svn:" name space.
1641251881Speter
1642251881Speter   Note that we only disallow the property if we're sure it's one that
1643251881Speter   already has a meaning for a different node kind.  We don't disallow
1644251881Speter   setting an *unknown* svn: prop here, at this level; a higher level
1645251881Speter   should disallow that if desired.
1646251881Speter  */
1647251881Speterstatic svn_error_t *
1648251881Spetervalidate_prop_against_node_kind(const char *name,
1649251881Speter                                const char *path,
1650251881Speter                                svn_node_kind_t node_kind,
1651251881Speter                                apr_pool_t *pool)
1652251881Speter{
1653251881Speter  const char *path_display
1654251881Speter    = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1655251881Speter
1656251881Speter  switch (node_kind)
1657251881Speter    {
1658251881Speter    case svn_node_dir:
1659251881Speter      if (! svn_prop_is_known_svn_dir_prop(name)
1660251881Speter          && svn_prop_is_known_svn_file_prop(name))
1661251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1662251881Speter                                 _("Cannot set '%s' on a directory ('%s')"),
1663251881Speter                                 name, path_display);
1664251881Speter      break;
1665251881Speter    case svn_node_file:
1666251881Speter      if (! svn_prop_is_known_svn_file_prop(name)
1667251881Speter          && svn_prop_is_known_svn_dir_prop(name))
1668251881Speter        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
1669251881Speter                                 _("Cannot set '%s' on a file ('%s')"),
1670251881Speter                                 name,
1671251881Speter                                 path_display);
1672251881Speter      break;
1673251881Speter    default:
1674251881Speter      return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL,
1675251881Speter                               _("'%s' is not a file or directory"),
1676251881Speter                               path_display);
1677251881Speter    }
1678251881Speter
1679251881Speter  return SVN_NO_ERROR;
1680251881Speter}
1681251881Speter
1682251881Speter
1683251881Speterstruct getter_baton {
1684251881Speter  const svn_string_t *mime_type;
1685251881Speter  const char *local_abspath;
1686251881Speter};
1687251881Speter
1688251881Speter
1689251881Speter/* Provide the MIME_TYPE and/or push the content to STREAM for the file
1690251881Speter * referenced by (getter_baton *) BATON.
1691251881Speter *
1692251881Speter * Implements svn_wc_canonicalize_svn_prop_get_file_t. */
1693251881Speterstatic svn_error_t *
1694251881Speterget_file_for_validation(const svn_string_t **mime_type,
1695251881Speter                        svn_stream_t *stream,
1696251881Speter                        void *baton,
1697251881Speter                        apr_pool_t *pool)
1698251881Speter{
1699251881Speter  struct getter_baton *gb = baton;
1700251881Speter
1701251881Speter  if (mime_type)
1702251881Speter    *mime_type = gb->mime_type;
1703251881Speter
1704251881Speter  if (stream)
1705251881Speter    {
1706251881Speter      svn_stream_t *read_stream;
1707251881Speter
1708251881Speter      /* Copy the text of GB->LOCAL_ABSPATH into STREAM. */
1709251881Speter      SVN_ERR(svn_stream_open_readonly(&read_stream, gb->local_abspath,
1710251881Speter                                       pool, pool));
1711251881Speter      SVN_ERR(svn_stream_copy3(read_stream, svn_stream_disown(stream, pool),
1712251881Speter                               NULL, NULL, pool));
1713251881Speter    }
1714251881Speter
1715251881Speter  return SVN_NO_ERROR;
1716251881Speter}
1717251881Speter
1718251881Speter
1719251881Speter/* Validate that a file has a 'non-binary' MIME type and contains
1720251881Speter * self-consistent line endings.  If not, then return an error.
1721251881Speter *
1722251881Speter * Call GETTER (which must not be NULL) with GETTER_BATON to get the
1723251881Speter * file's MIME type and/or content.  If the MIME type is non-null and
1724251881Speter * is categorized as 'binary' then return an error and do not request
1725251881Speter * the file content.
1726251881Speter *
1727251881Speter * Use PATH (a local path or a URL) only for error messages.
1728251881Speter */
1729251881Speterstatic svn_error_t *
1730251881Spetervalidate_eol_prop_against_file(const char *path,
1731251881Speter                               svn_wc_canonicalize_svn_prop_get_file_t getter,
1732251881Speter                               void *getter_baton,
1733251881Speter                               apr_pool_t *pool)
1734251881Speter{
1735251881Speter  svn_stream_t *translating_stream;
1736251881Speter  svn_error_t *err;
1737251881Speter  const svn_string_t *mime_type;
1738251881Speter  const char *path_display
1739251881Speter    = svn_path_is_url(path) ? path : svn_dirent_local_style(path, pool);
1740251881Speter
1741251881Speter  /* First just ask the "getter" for the MIME type. */
1742251881Speter  SVN_ERR(getter(&mime_type, NULL, getter_baton, pool));
1743251881Speter
1744251881Speter  /* See if this file has been determined to be binary. */
1745251881Speter  if (mime_type && svn_mime_type_is_binary(mime_type->data))
1746251881Speter    return svn_error_createf
1747251881Speter      (SVN_ERR_ILLEGAL_TARGET, NULL,
1748251881Speter       _("Can't set '%s': "
1749251881Speter         "file '%s' has binary mime type property"),
1750251881Speter       SVN_PROP_EOL_STYLE, path_display);
1751251881Speter
1752251881Speter  /* Now ask the getter for the contents of the file; this will do a
1753251881Speter     newline translation.  All we really care about here is whether or
1754251881Speter     not the function fails on inconsistent line endings.  The
1755251881Speter     function is "translating" to an empty stream.  This is
1756251881Speter     sneeeeeeeeeeeaky. */
1757251881Speter  translating_stream = svn_subst_stream_translated(svn_stream_empty(pool),
1758251881Speter                                                   "", FALSE, NULL, FALSE,
1759251881Speter                                                   pool);
1760251881Speter
1761251881Speter  err = getter(NULL, translating_stream, getter_baton, pool);
1762251881Speter
1763251881Speter  err = svn_error_compose_create(err, svn_stream_close(translating_stream));
1764251881Speter
1765251881Speter  if (err && err->apr_err == SVN_ERR_IO_INCONSISTENT_EOL)
1766251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, err,
1767251881Speter                             _("File '%s' has inconsistent newlines"),
1768251881Speter                             path_display);
1769251881Speter
1770251881Speter  return svn_error_trace(err);
1771251881Speter}
1772251881Speter
1773251881Speterstatic svn_error_t *
1774251881Speterdo_propset(svn_wc__db_t *db,
1775251881Speter           const char *local_abspath,
1776251881Speter           svn_node_kind_t kind,
1777251881Speter           const char *name,
1778251881Speter           const svn_string_t *value,
1779251881Speter           svn_boolean_t skip_checks,
1780251881Speter           svn_wc_notify_func2_t notify_func,
1781251881Speter           void *notify_baton,
1782251881Speter           apr_pool_t *scratch_pool)
1783251881Speter{
1784251881Speter  apr_hash_t *prophash;
1785251881Speter  svn_wc_notify_action_t notify_action;
1786251881Speter  svn_skel_t *work_item = NULL;
1787251881Speter  svn_boolean_t clear_recorded_info = FALSE;
1788251881Speter
1789251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
1790251881Speter
1791251881Speter  SVN_ERR_W(svn_wc__db_read_props(&prophash, db, local_abspath,
1792251881Speter                                  scratch_pool, scratch_pool),
1793251881Speter            _("Failed to load current properties"));
1794251881Speter
1795251881Speter  /* Setting an inappropriate property is not allowed (unless
1796251881Speter     overridden by 'skip_checks', in some circumstances).  Deleting an
1797251881Speter     inappropriate property is allowed, however, since older clients
1798251881Speter     allowed (and other clients possibly still allow) setting it in
1799251881Speter     the first place. */
1800251881Speter  if (value && svn_prop_is_svn_prop(name))
1801251881Speter    {
1802251881Speter      const svn_string_t *new_value;
1803251881Speter      struct getter_baton gb;
1804251881Speter
1805251881Speter      gb.mime_type = svn_hash_gets(prophash, SVN_PROP_MIME_TYPE);
1806251881Speter      gb.local_abspath = local_abspath;
1807251881Speter
1808251881Speter      SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, name, value,
1809251881Speter                                           local_abspath, kind,
1810251881Speter                                           skip_checks,
1811251881Speter                                           get_file_for_validation, &gb,
1812251881Speter                                           scratch_pool));
1813251881Speter      value = new_value;
1814251881Speter    }
1815251881Speter
1816251881Speter  if (kind == svn_node_file
1817251881Speter        && (strcmp(name, SVN_PROP_EXECUTABLE) == 0
1818251881Speter            || strcmp(name, SVN_PROP_NEEDS_LOCK) == 0))
1819251881Speter    {
1820251881Speter      SVN_ERR(svn_wc__wq_build_sync_file_flags(&work_item, db, local_abspath,
1821251881Speter                                               scratch_pool, scratch_pool));
1822251881Speter    }
1823251881Speter
1824251881Speter  /* If we're changing this file's list of expanded keywords, then
1825251881Speter   * we'll need to invalidate its text timestamp, since keyword
1826251881Speter   * expansion affects the comparison of working file to text base.
1827251881Speter   *
1828251881Speter   * Here we retrieve the old list of expanded keywords; after the
1829251881Speter   * property is set, we'll grab the new list and see if it differs
1830251881Speter   * from the old one.
1831251881Speter   */
1832251881Speter  if (kind == svn_node_file && strcmp(name, SVN_PROP_KEYWORDS) == 0)
1833251881Speter    {
1834251881Speter      svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_KEYWORDS);
1835251881Speter      apr_hash_t *old_keywords, *new_keywords;
1836251881Speter
1837251881Speter      if (old_value)
1838251881Speter        SVN_ERR(svn_wc__expand_keywords(&old_keywords,
1839251881Speter                                        db, local_abspath, NULL,
1840251881Speter                                        old_value->data, TRUE,
1841251881Speter                                        scratch_pool, scratch_pool));
1842251881Speter      else
1843251881Speter        old_keywords = apr_hash_make(scratch_pool);
1844251881Speter
1845251881Speter      if (value)
1846251881Speter        SVN_ERR(svn_wc__expand_keywords(&new_keywords,
1847251881Speter                                        db, local_abspath, NULL,
1848251881Speter                                        value->data, TRUE,
1849251881Speter                                        scratch_pool, scratch_pool));
1850251881Speter      else
1851251881Speter        new_keywords = apr_hash_make(scratch_pool);
1852251881Speter
1853251881Speter      if (svn_subst_keywords_differ2(old_keywords, new_keywords, FALSE,
1854251881Speter                                     scratch_pool))
1855251881Speter        {
1856251881Speter          /* If the keywords have changed, then the translation of the file
1857251881Speter             may be different. We should invalidate the RECORDED_SIZE
1858251881Speter             and RECORDED_TIME on this node.
1859251881Speter
1860251881Speter             Note that we don't immediately re-translate the file. But a
1861251881Speter             "has it changed?" check in the future will do a translation
1862251881Speter             from the pristine, and it will want to compare the (new)
1863251881Speter             resulting RECORDED_SIZE against the working copy file.
1864251881Speter
1865251881Speter             Also, when this file is (de)translated with the new keywords,
1866251881Speter             then it could be different, relative to the pristine. We want
1867251881Speter             to ensure the RECORDED_TIME is different, to indicate that
1868251881Speter             a full detranslate/compare is performed.  */
1869251881Speter          clear_recorded_info = TRUE;
1870251881Speter        }
1871251881Speter    }
1872251881Speter  else if (kind == svn_node_file && strcmp(name, SVN_PROP_EOL_STYLE) == 0)
1873251881Speter    {
1874251881Speter      svn_string_t *old_value = svn_hash_gets(prophash, SVN_PROP_EOL_STYLE);
1875251881Speter
1876251881Speter      if (((value == NULL) != (old_value == NULL))
1877251881Speter          || (value && ! svn_string_compare(value, old_value)))
1878251881Speter        {
1879251881Speter          clear_recorded_info = TRUE;
1880251881Speter        }
1881251881Speter    }
1882251881Speter
1883251881Speter  /* Find out what type of property change we are doing: add, modify, or
1884251881Speter     delete. */
1885251881Speter  if (svn_hash_gets(prophash, name) == NULL)
1886251881Speter    {
1887251881Speter      if (value == NULL)
1888251881Speter        /* Deleting a non-existent property. */
1889251881Speter        notify_action = svn_wc_notify_property_deleted_nonexistent;
1890251881Speter      else
1891251881Speter        /* Adding a property. */
1892251881Speter        notify_action = svn_wc_notify_property_added;
1893251881Speter    }
1894251881Speter  else
1895251881Speter    {
1896251881Speter      if (value == NULL)
1897251881Speter        /* Deleting the property. */
1898251881Speter        notify_action = svn_wc_notify_property_deleted;
1899251881Speter      else
1900251881Speter        /* Modifying property. */
1901251881Speter        notify_action = svn_wc_notify_property_modified;
1902251881Speter    }
1903251881Speter
1904251881Speter  /* Now we have all the properties in our hash.  Simply merge the new
1905251881Speter     property into it. */
1906251881Speter  svn_hash_sets(prophash, name, value);
1907251881Speter
1908251881Speter  /* Drop it right into the db..  */
1909251881Speter  SVN_ERR(svn_wc__db_op_set_props(db, local_abspath, prophash,
1910251881Speter                                  clear_recorded_info, NULL, work_item,
1911251881Speter                                  scratch_pool));
1912251881Speter
1913251881Speter  /* Run our workqueue item for sync'ing flags with props. */
1914251881Speter  if (work_item)
1915251881Speter    SVN_ERR(svn_wc__wq_run(db, local_abspath, NULL, NULL, scratch_pool));
1916251881Speter
1917251881Speter  if (notify_func)
1918251881Speter    {
1919251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath,
1920251881Speter                                                     notify_action,
1921251881Speter                                                     scratch_pool);
1922251881Speter      notify->prop_name = name;
1923251881Speter      notify->kind = kind;
1924251881Speter
1925251881Speter      (*notify_func)(notify_baton, notify, scratch_pool);
1926251881Speter    }
1927251881Speter
1928251881Speter  return SVN_NO_ERROR;
1929251881Speter}
1930251881Speter
1931251881Speter/* A baton for propset_walk_cb. */
1932251881Speterstruct propset_walk_baton
1933251881Speter{
1934251881Speter  const char *propname;  /* The name of the property to set. */
1935251881Speter  const svn_string_t *propval;  /* The value to set. */
1936251881Speter  svn_wc__db_t *db;  /* Database for the tree being walked. */
1937251881Speter  svn_boolean_t force;  /* True iff force was passed. */
1938251881Speter  svn_wc_notify_func2_t notify_func;
1939251881Speter  void *notify_baton;
1940251881Speter};
1941251881Speter
1942251881Speter/* An node-walk callback for svn_wc_prop_set4().
1943251881Speter *
1944251881Speter * For LOCAL_ABSPATH, set the property named wb->PROPNAME to the value
1945251881Speter * wb->PROPVAL, where "wb" is the WALK_BATON of type "struct
1946251881Speter * propset_walk_baton *".
1947251881Speter */
1948251881Speterstatic svn_error_t *
1949251881Speterpropset_walk_cb(const char *local_abspath,
1950251881Speter                svn_node_kind_t kind,
1951251881Speter                void *walk_baton,
1952251881Speter                apr_pool_t *scratch_pool)
1953251881Speter{
1954251881Speter  struct propset_walk_baton *wb = walk_baton;
1955251881Speter  svn_error_t *err;
1956251881Speter
1957251881Speter  err = do_propset(wb->db, local_abspath, kind, wb->propname, wb->propval,
1958251881Speter                   wb->force, wb->notify_func, wb->notify_baton, scratch_pool);
1959251881Speter  if (err && (err->apr_err == SVN_ERR_ILLEGAL_TARGET
1960251881Speter              || err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS))
1961251881Speter    {
1962251881Speter      svn_error_clear(err);
1963251881Speter      err = SVN_NO_ERROR;
1964251881Speter    }
1965251881Speter
1966251881Speter  return svn_error_trace(err);
1967251881Speter}
1968251881Speter
1969251881Spetersvn_error_t *
1970251881Spetersvn_wc_prop_set4(svn_wc_context_t *wc_ctx,
1971251881Speter                 const char *local_abspath,
1972251881Speter                 const char *name,
1973251881Speter                 const svn_string_t *value,
1974251881Speter                 svn_depth_t depth,
1975251881Speter                 svn_boolean_t skip_checks,
1976251881Speter                 const apr_array_header_t *changelist_filter,
1977251881Speter                 svn_cancel_func_t cancel_func,
1978251881Speter                 void *cancel_baton,
1979251881Speter                 svn_wc_notify_func2_t notify_func,
1980251881Speter                 void *notify_baton,
1981251881Speter                 apr_pool_t *scratch_pool)
1982251881Speter{
1983251881Speter  enum svn_prop_kind prop_kind = svn_property_kind2(name);
1984251881Speter  svn_wc__db_status_t status;
1985251881Speter  svn_node_kind_t kind;
1986251881Speter  svn_wc__db_t *db = wc_ctx->db;
1987251881Speter
1988251881Speter  /* we don't do entry properties here */
1989251881Speter  if (prop_kind == svn_prop_entry_kind)
1990251881Speter    return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
1991251881Speter                             _("Property '%s' is an entry property"), name);
1992251881Speter
1993251881Speter  /* Check to see if we're setting the dav cache. */
1994251881Speter  if (prop_kind == svn_prop_wc_kind)
1995251881Speter    {
1996251881Speter      SVN_ERR_ASSERT(depth == svn_depth_empty);
1997251881Speter      return svn_error_trace(wcprop_set(wc_ctx->db, local_abspath,
1998251881Speter                                        name, value, scratch_pool));
1999251881Speter    }
2000251881Speter
2001251881Speter  SVN_ERR(svn_wc__db_read_info(&status, &kind, NULL, NULL, NULL, NULL, NULL,
2002251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2003251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2004251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL,
2005251881Speter                               wc_ctx->db, local_abspath,
2006251881Speter                               scratch_pool, scratch_pool));
2007251881Speter
2008251881Speter  if (status != svn_wc__db_status_normal
2009251881Speter      && status != svn_wc__db_status_added
2010251881Speter      && status != svn_wc__db_status_incomplete)
2011251881Speter    {
2012251881Speter      return svn_error_createf(SVN_ERR_WC_INVALID_SCHEDULE, NULL,
2013251881Speter                                  _("Can't set properties on '%s':"
2014251881Speter                                  " invalid status for updating properties."),
2015251881Speter                                  svn_dirent_local_style(local_abspath,
2016251881Speter                                                         scratch_pool));
2017251881Speter    }
2018251881Speter
2019251881Speter  /* We have to do this little DIR_ABSPATH dance for backwards compat.
2020251881Speter     But from 1.7 onwards, all locks are of infinite depth, and from 1.6
2021251881Speter     backward we never call this API with depth > empty, so we only need
2022251881Speter     to do the write check once per call, here (and not for every node in
2023251881Speter     the node walker).
2024251881Speter
2025251881Speter     ### Note that we could check for a write lock on local_abspath first
2026251881Speter     ### if we would want to. And then justy check for kind if that fails.
2027251881Speter     ### ... but we need kind for the "svn:" property checks anyway */
2028251881Speter  {
2029251881Speter    const char *dir_abspath;
2030251881Speter
2031251881Speter    if (kind == svn_node_dir)
2032251881Speter      dir_abspath = local_abspath;
2033251881Speter    else
2034251881Speter      dir_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
2035251881Speter
2036251881Speter    /* Verify that we're holding this directory's write lock.  */
2037251881Speter    SVN_ERR(svn_wc__write_check(db, dir_abspath, scratch_pool));
2038251881Speter  }
2039251881Speter
2040251881Speter  if (depth == svn_depth_empty || kind != svn_node_dir)
2041251881Speter    {
2042251881Speter      apr_hash_t *changelist_hash = NULL;
2043251881Speter
2044251881Speter      if (changelist_filter && changelist_filter->nelts)
2045251881Speter        SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelist_filter,
2046251881Speter                                           scratch_pool));
2047251881Speter
2048251881Speter      if (!svn_wc__internal_changelist_match(wc_ctx->db, local_abspath,
2049251881Speter                                             changelist_hash, scratch_pool))
2050251881Speter        return SVN_NO_ERROR;
2051251881Speter
2052251881Speter      SVN_ERR(do_propset(wc_ctx->db, local_abspath,
2053251881Speter                         kind == svn_node_dir
2054251881Speter                            ? svn_node_dir
2055251881Speter                            : svn_node_file,
2056251881Speter                         name, value, skip_checks,
2057251881Speter                         notify_func, notify_baton, scratch_pool));
2058251881Speter
2059251881Speter    }
2060251881Speter  else
2061251881Speter    {
2062251881Speter      struct propset_walk_baton wb;
2063251881Speter
2064251881Speter      wb.propname = name;
2065251881Speter      wb.propval = value;
2066251881Speter      wb.db = wc_ctx->db;
2067251881Speter      wb.force = skip_checks;
2068251881Speter      wb.notify_func = notify_func;
2069251881Speter      wb.notify_baton = notify_baton;
2070251881Speter
2071251881Speter      SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath,
2072251881Speter                                             FALSE, changelist_filter,
2073251881Speter                                             propset_walk_cb, &wb,
2074251881Speter                                             depth,
2075251881Speter                                             cancel_func, cancel_baton,
2076251881Speter                                             scratch_pool));
2077251881Speter    }
2078251881Speter
2079251881Speter  return SVN_NO_ERROR;
2080251881Speter}
2081251881Speter
2082251881Speter/* Check that NAME names a regular prop. Return an error if it names an
2083251881Speter * entry prop or a WC prop. */
2084251881Speterstatic svn_error_t *
2085251881Speterensure_prop_is_regular_kind(const char *name)
2086251881Speter{
2087251881Speter  enum svn_prop_kind prop_kind = svn_property_kind2(name);
2088251881Speter
2089251881Speter  /* we don't do entry properties here */
2090251881Speter  if (prop_kind == svn_prop_entry_kind)
2091251881Speter    return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
2092251881Speter                             _("Property '%s' is an entry property"), name);
2093251881Speter
2094251881Speter  /* Check to see if we're setting the dav cache. */
2095251881Speter  if (prop_kind == svn_prop_wc_kind)
2096251881Speter    return svn_error_createf(SVN_ERR_BAD_PROP_KIND, NULL,
2097251881Speter                             _("Property '%s' is a WC property, not "
2098251881Speter                               "a regular property"), name);
2099251881Speter
2100251881Speter  return SVN_NO_ERROR;
2101251881Speter}
2102251881Speter
2103251881Spetersvn_error_t *
2104251881Spetersvn_wc__canonicalize_props(apr_hash_t **prepared_props,
2105251881Speter                           const char *local_abspath,
2106251881Speter                           svn_node_kind_t node_kind,
2107251881Speter                           const apr_hash_t *props,
2108251881Speter                           svn_boolean_t skip_some_checks,
2109251881Speter                           apr_pool_t *result_pool,
2110251881Speter                           apr_pool_t *scratch_pool)
2111251881Speter{
2112251881Speter  const svn_string_t *mime_type;
2113251881Speter  struct getter_baton gb;
2114251881Speter  apr_hash_index_t *hi;
2115251881Speter
2116251881Speter  /* While we allocate new parts of *PREPARED_PROPS in RESULT_POOL, we
2117251881Speter     don't promise to deep-copy the unchanged keys and values. */
2118251881Speter  *prepared_props = apr_hash_make(result_pool);
2119251881Speter
2120251881Speter  /* Before we can canonicalize svn:eol-style we need to know svn:mime-type,
2121251881Speter   * so process that first. */
2122251881Speter  mime_type = svn_hash_gets((apr_hash_t *)props, SVN_PROP_MIME_TYPE);
2123251881Speter  if (mime_type)
2124251881Speter    {
2125251881Speter      SVN_ERR(svn_wc_canonicalize_svn_prop(
2126251881Speter                &mime_type, SVN_PROP_MIME_TYPE, mime_type,
2127251881Speter                local_abspath, node_kind, skip_some_checks,
2128251881Speter                NULL, NULL, scratch_pool));
2129251881Speter      svn_hash_sets(*prepared_props, SVN_PROP_MIME_TYPE, mime_type);
2130251881Speter    }
2131251881Speter
2132251881Speter  /* Set up the context for canonicalizing the other properties. */
2133251881Speter  gb.mime_type = mime_type;
2134251881Speter  gb.local_abspath = local_abspath;
2135251881Speter
2136251881Speter  /* Check and canonicalize the other properties. */
2137251881Speter  for (hi = apr_hash_first(scratch_pool, (apr_hash_t *)props); hi;
2138251881Speter       hi = apr_hash_next(hi))
2139251881Speter    {
2140299742Sdim      const char *name = apr_hash_this_key(hi);
2141299742Sdim      const svn_string_t *value = apr_hash_this_val(hi);
2142251881Speter
2143251881Speter      if (strcmp(name, SVN_PROP_MIME_TYPE) == 0)
2144251881Speter        continue;
2145251881Speter
2146251881Speter      SVN_ERR(ensure_prop_is_regular_kind(name));
2147251881Speter      SVN_ERR(svn_wc_canonicalize_svn_prop(
2148251881Speter                &value, name, value,
2149251881Speter                local_abspath, node_kind, skip_some_checks,
2150251881Speter                get_file_for_validation, &gb, scratch_pool));
2151251881Speter      svn_hash_sets(*prepared_props, name, value);
2152251881Speter    }
2153251881Speter
2154251881Speter  return SVN_NO_ERROR;
2155251881Speter}
2156251881Speter
2157251881Speter
2158251881Spetersvn_error_t *
2159251881Spetersvn_wc_canonicalize_svn_prop(const svn_string_t **propval_p,
2160251881Speter                             const char *propname,
2161251881Speter                             const svn_string_t *propval,
2162251881Speter                             const char *path,
2163251881Speter                             svn_node_kind_t kind,
2164251881Speter                             svn_boolean_t skip_some_checks,
2165251881Speter                             svn_wc_canonicalize_svn_prop_get_file_t getter,
2166251881Speter                             void *getter_baton,
2167251881Speter                             apr_pool_t *pool)
2168251881Speter{
2169251881Speter  svn_stringbuf_t *new_value = NULL;
2170251881Speter
2171251881Speter  /* Keep this static, it may get stored (for read-only purposes) in a
2172251881Speter     hash that outlives this function. */
2173251881Speter  static const svn_string_t boolean_value =
2174251881Speter    {
2175251881Speter      SVN_PROP_BOOLEAN_TRUE,
2176251881Speter      sizeof(SVN_PROP_BOOLEAN_TRUE) - 1
2177251881Speter    };
2178251881Speter
2179251881Speter  SVN_ERR(validate_prop_against_node_kind(propname, path, kind, pool));
2180251881Speter
2181251881Speter  /* This code may place the new prop val in either NEW_VALUE or PROPVAL. */
2182251881Speter  if (!skip_some_checks && (strcmp(propname, SVN_PROP_EOL_STYLE) == 0))
2183251881Speter    {
2184251881Speter      svn_subst_eol_style_t eol_style;
2185251881Speter      const char *ignored_eol;
2186251881Speter      new_value = svn_stringbuf_create_from_string(propval, pool);
2187251881Speter      svn_stringbuf_strip_whitespace(new_value);
2188251881Speter      svn_subst_eol_style_from_value(&eol_style, &ignored_eol, new_value->data);
2189251881Speter      if (eol_style == svn_subst_eol_style_unknown)
2190251881Speter        return svn_error_createf(SVN_ERR_IO_UNKNOWN_EOL, NULL,
2191251881Speter                                 _("Unrecognized line ending style '%s' for '%s'"),
2192251881Speter                                 new_value->data,
2193251881Speter                                 svn_dirent_local_style(path, pool));
2194251881Speter      SVN_ERR(validate_eol_prop_against_file(path, getter, getter_baton,
2195251881Speter                                             pool));
2196251881Speter    }
2197251881Speter  else if (!skip_some_checks && (strcmp(propname, SVN_PROP_MIME_TYPE) == 0))
2198251881Speter    {
2199251881Speter      new_value = svn_stringbuf_create_from_string(propval, pool);
2200251881Speter      svn_stringbuf_strip_whitespace(new_value);
2201251881Speter      SVN_ERR(svn_mime_type_validate(new_value->data, pool));
2202251881Speter    }
2203251881Speter  else if (strcmp(propname, SVN_PROP_IGNORE) == 0
2204251881Speter           || strcmp(propname, SVN_PROP_EXTERNALS) == 0
2205251881Speter           || strcmp(propname, SVN_PROP_INHERITABLE_IGNORES) == 0
2206251881Speter           || strcmp(propname, SVN_PROP_INHERITABLE_AUTO_PROPS) == 0)
2207251881Speter    {
2208251881Speter      /* Make sure that the last line ends in a newline */
2209251881Speter      if (propval->len == 0
2210251881Speter          || propval->data[propval->len - 1] != '\n')
2211251881Speter        {
2212251881Speter          new_value = svn_stringbuf_create_from_string(propval, pool);
2213251881Speter          svn_stringbuf_appendbyte(new_value, '\n');
2214251881Speter        }
2215251881Speter
2216251881Speter      /* Make sure this is a valid externals property.  Do not
2217251881Speter         allow 'skip_some_checks' to override, as there is no circumstance in
2218251881Speter         which this is proper (because there is no circumstance in
2219251881Speter         which Subversion can handle it). */
2220251881Speter      if (strcmp(propname, SVN_PROP_EXTERNALS) == 0)
2221251881Speter        {
2222251881Speter          /* We don't allow "." nor ".." as target directories in
2223251881Speter             an svn:externals line.  As it happens, our parse code
2224251881Speter             checks for this, so all we have to is invoke it --
2225251881Speter             we're not interested in the parsed result, only in
2226251881Speter             whether or not the parsing errored. */
2227251881Speter          apr_array_header_t *externals = NULL;
2228251881Speter          apr_array_header_t *duplicate_targets = NULL;
2229251881Speter          SVN_ERR(svn_wc_parse_externals_description3(&externals, path,
2230251881Speter                                                      propval->data, FALSE,
2231251881Speter                                                      /*scratch_*/pool));
2232251881Speter          SVN_ERR(svn_wc__externals_find_target_dups(&duplicate_targets,
2233251881Speter                                                     externals,
2234251881Speter                                                     /*scratch_*/pool,
2235251881Speter                                                     /*scratch_*/pool));
2236251881Speter          if (duplicate_targets && duplicate_targets->nelts > 0)
2237251881Speter            {
2238251881Speter              const char *more_str = "";
2239251881Speter              if (duplicate_targets->nelts > 1)
2240251881Speter                {
2241251881Speter                  more_str = apr_psprintf(/*scratch_*/pool,
2242251881Speter                               _(" (%d more duplicate targets found)"),
2243251881Speter                               duplicate_targets->nelts - 1);
2244251881Speter                }
2245251881Speter              return svn_error_createf(
2246251881Speter                SVN_ERR_WC_DUPLICATE_EXTERNALS_TARGET, NULL,
2247251881Speter                _("Invalid %s property on '%s': "
2248251881Speter                  "target '%s' appears more than once%s"),
2249251881Speter                SVN_PROP_EXTERNALS,
2250251881Speter                svn_dirent_local_style(path, pool),
2251251881Speter                APR_ARRAY_IDX(duplicate_targets, 0, const char*),
2252251881Speter                more_str);
2253251881Speter            }
2254251881Speter        }
2255251881Speter    }
2256251881Speter  else if (strcmp(propname, SVN_PROP_KEYWORDS) == 0)
2257251881Speter    {
2258251881Speter      new_value = svn_stringbuf_create_from_string(propval, pool);
2259251881Speter      svn_stringbuf_strip_whitespace(new_value);
2260251881Speter    }
2261251881Speter  else if (svn_prop_is_boolean(propname))
2262251881Speter    {
2263251881Speter      /* SVN_PROP_EXECUTABLE, SVN_PROP_NEEDS_LOCK, SVN_PROP_SPECIAL */
2264251881Speter      propval = &boolean_value;
2265251881Speter    }
2266251881Speter  else if (strcmp(propname, SVN_PROP_MERGEINFO) == 0)
2267251881Speter    {
2268251881Speter      apr_hash_t *mergeinfo;
2269251881Speter      svn_string_t *new_value_str;
2270251881Speter
2271251881Speter      SVN_ERR(svn_mergeinfo_parse(&mergeinfo, propval->data, pool));
2272251881Speter
2273251881Speter      /* Non-inheritable mergeinfo is only valid on directories. */
2274251881Speter      if (kind != svn_node_dir
2275251881Speter          && svn_mergeinfo__is_noninheritable(mergeinfo, pool))
2276251881Speter        return svn_error_createf(
2277251881Speter          SVN_ERR_MERGEINFO_PARSE_ERROR, NULL,
2278251881Speter          _("Cannot set non-inheritable mergeinfo on a non-directory ('%s')"),
2279251881Speter          svn_dirent_local_style(path, pool));
2280251881Speter
2281251881Speter      SVN_ERR(svn_mergeinfo_to_string(&new_value_str, mergeinfo, pool));
2282251881Speter      propval = new_value_str;
2283251881Speter    }
2284251881Speter
2285251881Speter  if (new_value)
2286251881Speter    *propval_p = svn_stringbuf__morph_into_string(new_value);
2287251881Speter  else
2288251881Speter    *propval_p = propval;
2289251881Speter
2290251881Speter  return SVN_NO_ERROR;
2291251881Speter}
2292251881Speter
2293251881Speter
2294251881Spetersvn_boolean_t
2295251881Spetersvn_wc_is_normal_prop(const char *name)
2296251881Speter{
2297251881Speter  enum svn_prop_kind kind = svn_property_kind2(name);
2298251881Speter  return (kind == svn_prop_regular_kind);
2299251881Speter}
2300251881Speter
2301251881Speter
2302251881Spetersvn_boolean_t
2303251881Spetersvn_wc_is_wc_prop(const char *name)
2304251881Speter{
2305251881Speter  enum svn_prop_kind kind = svn_property_kind2(name);
2306251881Speter  return (kind == svn_prop_wc_kind);
2307251881Speter}
2308251881Speter
2309251881Speter
2310251881Spetersvn_boolean_t
2311251881Spetersvn_wc_is_entry_prop(const char *name)
2312251881Speter{
2313251881Speter  enum svn_prop_kind kind = svn_property_kind2(name);
2314251881Speter  return (kind == svn_prop_entry_kind);
2315251881Speter}
2316251881Speter
2317251881Speter
2318251881Spetersvn_error_t *
2319251881Spetersvn_wc__props_modified(svn_boolean_t *modified_p,
2320251881Speter                       svn_wc__db_t *db,
2321251881Speter                       const char *local_abspath,
2322251881Speter                       apr_pool_t *scratch_pool)
2323251881Speter{
2324251881Speter  SVN_ERR(svn_wc__db_read_info(NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2325251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2326251881Speter                               NULL, NULL, NULL, NULL, NULL, NULL, NULL,
2327251881Speter                               NULL, NULL, modified_p, NULL, NULL, NULL,
2328251881Speter                               db, local_abspath,
2329251881Speter                               scratch_pool, scratch_pool));
2330251881Speter
2331251881Speter  return SVN_NO_ERROR;
2332251881Speter}
2333251881Speter
2334251881Spetersvn_error_t *
2335251881Spetersvn_wc_props_modified_p2(svn_boolean_t *modified_p,
2336251881Speter                         svn_wc_context_t* wc_ctx,
2337251881Speter                         const char *local_abspath,
2338251881Speter                         apr_pool_t *scratch_pool)
2339251881Speter{
2340251881Speter  return svn_error_trace(
2341251881Speter             svn_wc__props_modified(modified_p,
2342251881Speter                                    wc_ctx->db,
2343251881Speter                                    local_abspath,
2344251881Speter                                    scratch_pool));
2345251881Speter}
2346251881Speter
2347251881Spetersvn_error_t *
2348251881Spetersvn_wc__internal_propdiff(apr_array_header_t **propchanges,
2349251881Speter                          apr_hash_t **original_props,
2350251881Speter                          svn_wc__db_t *db,
2351251881Speter                          const char *local_abspath,
2352251881Speter                          apr_pool_t *result_pool,
2353251881Speter                          apr_pool_t *scratch_pool)
2354251881Speter{
2355251881Speter  apr_hash_t *baseprops;
2356251881Speter
2357251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
2358251881Speter
2359251881Speter  /* ### if pristines are not defined, then should this raise an error,
2360251881Speter     ### or use an empty set?  */
2361251881Speter  SVN_ERR(svn_wc__db_read_pristine_props(&baseprops, db, local_abspath,
2362251881Speter                                         result_pool, scratch_pool));
2363251881Speter
2364251881Speter  if (original_props != NULL)
2365251881Speter    *original_props = baseprops;
2366251881Speter
2367251881Speter  if (propchanges != NULL)
2368251881Speter    {
2369251881Speter      apr_hash_t *actual_props;
2370251881Speter
2371251881Speter      /* Some nodes do not have pristine props, so let's just use an empty
2372251881Speter         set here. Thus, any ACTUAL props are additions.  */
2373251881Speter      if (baseprops == NULL)
2374251881Speter        baseprops = apr_hash_make(scratch_pool);
2375251881Speter
2376251881Speter      SVN_ERR(svn_wc__db_read_props(&actual_props, db, local_abspath,
2377251881Speter                                    result_pool, scratch_pool));
2378251881Speter      /* ### be wary. certain nodes don't have ACTUAL props either. we
2379251881Speter         ### may want to raise an error. or maybe that is a deletion of
2380251881Speter         ### any potential pristine props?  */
2381251881Speter
2382251881Speter      SVN_ERR(svn_prop_diffs(propchanges, actual_props, baseprops,
2383251881Speter                             result_pool));
2384251881Speter    }
2385251881Speter
2386251881Speter  return SVN_NO_ERROR;
2387251881Speter}
2388251881Speter
2389251881Spetersvn_error_t *
2390251881Spetersvn_wc_get_prop_diffs2(apr_array_header_t **propchanges,
2391251881Speter                       apr_hash_t **original_props,
2392251881Speter                       svn_wc_context_t *wc_ctx,
2393251881Speter                       const char *local_abspath,
2394251881Speter                       apr_pool_t *result_pool,
2395251881Speter                       apr_pool_t *scratch_pool)
2396251881Speter{
2397251881Speter  return svn_error_trace(svn_wc__internal_propdiff(propchanges,
2398251881Speter                                    original_props, wc_ctx->db, local_abspath,
2399251881Speter                                    result_pool, scratch_pool));
2400251881Speter}
2401251881Speter
2402251881Spetersvn_boolean_t
2403251881Spetersvn_wc__has_magic_property(const apr_array_header_t *properties)
2404251881Speter{
2405251881Speter  int i;
2406251881Speter
2407251881Speter  for (i = 0; i < properties->nelts; i++)
2408251881Speter    {
2409251881Speter      const svn_prop_t *property = &APR_ARRAY_IDX(properties, i, svn_prop_t);
2410251881Speter
2411251881Speter      if (strcmp(property->name, SVN_PROP_EXECUTABLE) == 0
2412251881Speter          || strcmp(property->name, SVN_PROP_KEYWORDS) == 0
2413251881Speter          || strcmp(property->name, SVN_PROP_EOL_STYLE) == 0
2414251881Speter          || strcmp(property->name, SVN_PROP_SPECIAL) == 0
2415251881Speter          || strcmp(property->name, SVN_PROP_NEEDS_LOCK) == 0)
2416251881Speter        return TRUE;
2417251881Speter    }
2418251881Speter  return FALSE;
2419251881Speter}
2420251881Speter
2421251881Spetersvn_error_t *
2422251881Spetersvn_wc__get_iprops(apr_array_header_t **inherited_props,
2423251881Speter                   svn_wc_context_t *wc_ctx,
2424251881Speter                   const char *local_abspath,
2425251881Speter                   const char *propname,
2426251881Speter                   apr_pool_t *result_pool,
2427251881Speter                   apr_pool_t *scratch_pool)
2428251881Speter{
2429251881Speter  return svn_error_trace(
2430251881Speter            svn_wc__db_read_inherited_props(inherited_props, NULL,
2431251881Speter                                            wc_ctx->db, local_abspath,
2432251881Speter                                            propname,
2433251881Speter                                            result_pool, scratch_pool));
2434251881Speter}
2435251881Speter
2436251881Spetersvn_error_t *
2437251881Spetersvn_wc__get_cached_iprop_children(apr_hash_t **iprop_paths,
2438251881Speter                                  svn_depth_t depth,
2439251881Speter                                  svn_wc_context_t *wc_ctx,
2440251881Speter                                  const char *local_abspath,
2441251881Speter                                  apr_pool_t *result_pool,
2442251881Speter                                  apr_pool_t *scratch_pool)
2443251881Speter{
2444251881Speter  SVN_ERR(svn_wc__db_get_children_with_cached_iprops(iprop_paths,
2445251881Speter                                                     depth,
2446251881Speter                                                     local_abspath,
2447251881Speter                                                     wc_ctx->db,
2448251881Speter                                                     result_pool,
2449251881Speter                                                     scratch_pool));
2450251881Speter  return SVN_NO_ERROR;
2451251881Speter}
2452