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