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