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