1/* 2 * tree_conflicts.c: Storage of tree conflict descriptions in the WC. 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#include "svn_dirent_uri.h" 25#include "svn_path.h" 26#include "svn_types.h" 27#include "svn_pools.h" 28 29#include "tree_conflicts.h" 30#include "conflicts.h" 31#include "wc.h" 32 33#include "private/svn_skel.h" 34#include "private/svn_wc_private.h" 35#include "private/svn_token.h" 36 37#include "svn_private_config.h" 38 39/* ### this should move to a more general location... */ 40/* A map for svn_node_kind_t values. */ 41/* FIXME: this mapping defines a different representation of 42 svn_node_unknown than the one defined in token-map.h */ 43static const svn_token_map_t node_kind_map[] = 44{ 45 { "none", svn_node_none }, 46 { "file", svn_node_file }, 47 { "dir", svn_node_dir }, 48 { "", svn_node_unknown }, 49 { NULL } 50}; 51 52/* A map for svn_wc_operation_t values. */ 53const svn_token_map_t svn_wc__operation_map[] = 54{ 55 { "none", svn_wc_operation_none }, 56 { "update", svn_wc_operation_update }, 57 { "switch", svn_wc_operation_switch }, 58 { "merge", svn_wc_operation_merge }, 59 { NULL } 60}; 61 62/* A map for svn_wc_conflict_action_t values. */ 63const svn_token_map_t svn_wc__conflict_action_map[] = 64{ 65 { "edited", svn_wc_conflict_action_edit }, 66 { "deleted", svn_wc_conflict_action_delete }, 67 { "added", svn_wc_conflict_action_add }, 68 { "replaced", svn_wc_conflict_action_replace }, 69 { NULL } 70}; 71 72/* A map for svn_wc_conflict_reason_t values. */ 73const svn_token_map_t svn_wc__conflict_reason_map[] = 74{ 75 { "edited", svn_wc_conflict_reason_edited }, 76 { "deleted", svn_wc_conflict_reason_deleted }, 77 { "missing", svn_wc_conflict_reason_missing }, 78 { "obstructed", svn_wc_conflict_reason_obstructed }, 79 { "added", svn_wc_conflict_reason_added }, 80 { "replaced", svn_wc_conflict_reason_replaced }, 81 { "unversioned", svn_wc_conflict_reason_unversioned }, 82 { "moved-away", svn_wc_conflict_reason_moved_away }, 83 { "moved-here", svn_wc_conflict_reason_moved_here }, 84 { NULL } 85}; 86 87 88/* */ 89static svn_boolean_t 90is_valid_version_info_skel(const svn_skel_t *skel) 91{ 92 return (svn_skel__list_length(skel) == 5 93 && svn_skel__matches_atom(skel->children, "version") 94 && skel->children->next->is_atom 95 && skel->children->next->next->is_atom 96 && skel->children->next->next->next->is_atom 97 && skel->children->next->next->next->next->is_atom); 98} 99 100 101/* */ 102static svn_boolean_t 103is_valid_conflict_skel(const svn_skel_t *skel) 104{ 105 int i; 106 107 if (svn_skel__list_length(skel) != 8 108 || !svn_skel__matches_atom(skel->children, "conflict")) 109 return FALSE; 110 111 /* 5 atoms ... */ 112 skel = skel->children->next; 113 for (i = 5; i--; skel = skel->next) 114 if (!skel->is_atom) 115 return FALSE; 116 117 /* ... and 2 version info skels. */ 118 return (is_valid_version_info_skel(skel) 119 && is_valid_version_info_skel(skel->next)); 120} 121 122 123/* Parse the enumeration value in VALUE into a plain 124 * 'int', using MAP to convert from strings to enumeration values. 125 * In MAP, a null .str field marks the end of the map. 126 */ 127static svn_error_t * 128read_enum_field(int *result, 129 const svn_token_map_t *map, 130 const svn_skel_t *skel) 131{ 132 int value = svn_token__from_mem(map, skel->data, skel->len); 133 134 if (value == SVN_TOKEN_UNKNOWN) 135 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 136 _("Unknown enumeration value in tree conflict " 137 "description")); 138 139 *result = value; 140 141 return SVN_NO_ERROR; 142} 143 144 145/* Parse the conflict info fields from SKEL into *VERSION_INFO. */ 146static svn_error_t * 147read_node_version_info(const svn_wc_conflict_version_t **version_info, 148 const svn_skel_t *skel, 149 apr_pool_t *result_pool, 150 apr_pool_t *scratch_pool) 151{ 152 int n; 153 const char *repos_root; 154 const char *repos_relpath; 155 svn_revnum_t peg_rev; 156 svn_node_kind_t kind; 157 158 if (!is_valid_version_info_skel(skel)) 159 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 160 _("Invalid version info in tree conflict " 161 "description")); 162 163 repos_root = apr_pstrmemdup(scratch_pool, 164 skel->children->next->data, 165 skel->children->next->len); 166 if (*repos_root == '\0') 167 { 168 *version_info = NULL; 169 return SVN_NO_ERROR; 170 } 171 172 /* Apply the Subversion 1.7+ url canonicalization rules to a pre 1.7 url */ 173 repos_root = svn_uri_canonicalize(repos_root, result_pool); 174 175 peg_rev = SVN_STR_TO_REV(apr_pstrmemdup(scratch_pool, 176 skel->children->next->next->data, 177 skel->children->next->next->len)); 178 179 repos_relpath = apr_pstrmemdup(result_pool, 180 skel->children->next->next->next->data, 181 skel->children->next->next->next->len); 182 183 SVN_ERR(read_enum_field(&n, node_kind_map, 184 skel->children->next->next->next->next)); 185 kind = (svn_node_kind_t)n; 186 187 *version_info = svn_wc_conflict_version_create2(repos_root, 188 NULL, 189 repos_relpath, 190 peg_rev, 191 kind, 192 result_pool); 193 194 return SVN_NO_ERROR; 195} 196 197 198svn_error_t * 199svn_wc__deserialize_conflict(const svn_wc_conflict_description2_t **conflict, 200 const svn_skel_t *skel, 201 const char *dir_path, 202 apr_pool_t *result_pool, 203 apr_pool_t *scratch_pool) 204{ 205 const char *victim_basename; 206 const char *victim_abspath; 207 svn_node_kind_t node_kind; 208 svn_wc_operation_t operation; 209 svn_wc_conflict_action_t action; 210 svn_wc_conflict_reason_t reason; 211 const svn_wc_conflict_version_t *src_left_version; 212 const svn_wc_conflict_version_t *src_right_version; 213 int n; 214 svn_wc_conflict_description2_t *new_conflict; 215 216 if (!is_valid_conflict_skel(skel)) 217 return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL, 218 _("Invalid conflict info '%s' in tree conflict " 219 "description"), 220 skel ? svn_skel__unparse(skel, scratch_pool)->data 221 : "(null)"); 222 223 /* victim basename */ 224 victim_basename = apr_pstrmemdup(scratch_pool, 225 skel->children->next->data, 226 skel->children->next->len); 227 if (victim_basename[0] == '\0') 228 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 229 _("Empty 'victim' field in tree conflict " 230 "description")); 231 232 /* node_kind */ 233 SVN_ERR(read_enum_field(&n, node_kind_map, skel->children->next->next)); 234 node_kind = (svn_node_kind_t)n; 235 if (node_kind != svn_node_file && node_kind != svn_node_dir) 236 return svn_error_create(SVN_ERR_WC_CORRUPT, NULL, 237 _("Invalid 'node_kind' field in tree conflict description")); 238 239 /* operation */ 240 SVN_ERR(read_enum_field(&n, svn_wc__operation_map, 241 skel->children->next->next->next)); 242 operation = (svn_wc_operation_t)n; 243 244 SVN_ERR(svn_dirent_get_absolute(&victim_abspath, 245 svn_dirent_join(dir_path, victim_basename, scratch_pool), 246 scratch_pool)); 247 248 /* action */ 249 SVN_ERR(read_enum_field(&n, svn_wc__conflict_action_map, 250 skel->children->next->next->next->next)); 251 action = n; 252 253 /* reason */ 254 SVN_ERR(read_enum_field(&n, svn_wc__conflict_reason_map, 255 skel->children->next->next->next->next->next)); 256 reason = n; 257 258 /* Let's just make it a bit easier on ourself here... */ 259 skel = skel->children->next->next->next->next->next->next; 260 261 /* src_left_version */ 262 SVN_ERR(read_node_version_info(&src_left_version, skel, 263 result_pool, scratch_pool)); 264 265 /* src_right_version */ 266 SVN_ERR(read_node_version_info(&src_right_version, skel->next, 267 result_pool, scratch_pool)); 268 269 new_conflict = svn_wc_conflict_description_create_tree2(victim_abspath, 270 node_kind, operation, src_left_version, src_right_version, 271 result_pool); 272 new_conflict->action = action; 273 new_conflict->reason = reason; 274 275 *conflict = new_conflict; 276 277 return SVN_NO_ERROR; 278} 279 280 281/* Prepend to SKEL the string corresponding to enumeration value N, as found 282 * in MAP. */ 283static void 284skel_prepend_enum(svn_skel_t *skel, 285 const svn_token_map_t *map, 286 int n, 287 apr_pool_t *result_pool) 288{ 289 svn_skel__prepend(svn_skel__str_atom(svn_token__to_word(map, n), 290 result_pool), skel); 291} 292 293 294/* Prepend to PARENT_SKEL the several fields that represent VERSION_INFO, */ 295static svn_error_t * 296prepend_version_info_skel(svn_skel_t *parent_skel, 297 const svn_wc_conflict_version_t *version_info, 298 apr_pool_t *pool) 299{ 300 svn_skel_t *skel = svn_skel__make_empty_list(pool); 301 302 /* node_kind */ 303 skel_prepend_enum(skel, node_kind_map, version_info->node_kind, pool); 304 305 /* path_in_repos */ 306 svn_skel__prepend(svn_skel__str_atom(version_info->path_in_repos 307 ? version_info->path_in_repos 308 : "", pool), skel); 309 310 /* peg_rev */ 311 svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld", 312 version_info->peg_rev), 313 pool), skel); 314 315 /* repos_url */ 316 svn_skel__prepend(svn_skel__str_atom(version_info->repos_url 317 ? version_info->repos_url 318 : "", pool), skel); 319 320 svn_skel__prepend(svn_skel__str_atom("version", pool), skel); 321 322 SVN_ERR_ASSERT(is_valid_version_info_skel(skel)); 323 324 svn_skel__prepend(skel, parent_skel); 325 326 return SVN_NO_ERROR; 327} 328 329 330svn_error_t * 331svn_wc__serialize_conflict(svn_skel_t **skel, 332 const svn_wc_conflict_description2_t *conflict, 333 apr_pool_t *result_pool, 334 apr_pool_t *scratch_pool) 335{ 336 /* A conflict version struct with all fields null/invalid. */ 337 static const svn_wc_conflict_version_t null_version = { 338 NULL, SVN_INVALID_REVNUM, NULL, svn_node_unknown }; 339 svn_skel_t *c_skel = svn_skel__make_empty_list(result_pool); 340 const char *victim_basename; 341 342 /* src_right_version */ 343 if (conflict->src_right_version) 344 SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_right_version, 345 result_pool)); 346 else 347 SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool)); 348 349 /* src_left_version */ 350 if (conflict->src_left_version) 351 SVN_ERR(prepend_version_info_skel(c_skel, conflict->src_left_version, 352 result_pool)); 353 else 354 SVN_ERR(prepend_version_info_skel(c_skel, &null_version, result_pool)); 355 356 /* reason */ 357 skel_prepend_enum(c_skel, svn_wc__conflict_reason_map, conflict->reason, 358 result_pool); 359 360 /* action */ 361 skel_prepend_enum(c_skel, svn_wc__conflict_action_map, conflict->action, 362 result_pool); 363 364 /* operation */ 365 skel_prepend_enum(c_skel, svn_wc__operation_map, conflict->operation, 366 result_pool); 367 368 /* node_kind */ 369 SVN_ERR_ASSERT(conflict->node_kind == svn_node_dir 370 || conflict->node_kind == svn_node_file); 371 skel_prepend_enum(c_skel, node_kind_map, conflict->node_kind, result_pool); 372 373 /* Victim path (escaping separator chars). */ 374 victim_basename = svn_dirent_basename(conflict->local_abspath, result_pool); 375 SVN_ERR_ASSERT(victim_basename[0]); 376 svn_skel__prepend(svn_skel__str_atom(victim_basename, result_pool), c_skel); 377 378 svn_skel__prepend(svn_skel__str_atom("conflict", result_pool), c_skel); 379 380 SVN_ERR_ASSERT(is_valid_conflict_skel(c_skel)); 381 382 *skel = c_skel; 383 384 return SVN_NO_ERROR; 385} 386 387 388svn_error_t * 389svn_wc__del_tree_conflict(svn_wc_context_t *wc_ctx, 390 const char *victim_abspath, 391 apr_pool_t *scratch_pool) 392{ 393 SVN_ERR_ASSERT(svn_dirent_is_absolute(victim_abspath)); 394 395 SVN_ERR(svn_wc__db_op_mark_resolved(wc_ctx->db, victim_abspath, 396 FALSE, FALSE, TRUE, NULL, 397 scratch_pool)); 398 399 return SVN_NO_ERROR; 400 } 401 402svn_error_t * 403svn_wc__add_tree_conflict(svn_wc_context_t *wc_ctx, 404 const svn_wc_conflict_description2_t *conflict, 405 apr_pool_t *scratch_pool) 406{ 407 svn_boolean_t existing_conflict; 408 svn_skel_t *conflict_skel; 409 svn_error_t *err; 410 411 SVN_ERR_ASSERT(conflict != NULL); 412 SVN_ERR_ASSERT(conflict->operation == svn_wc_operation_merge 413 || (conflict->reason != svn_wc_conflict_reason_moved_away 414 && conflict->reason != svn_wc_conflict_reason_moved_here) 415 ); 416 417 /* Re-adding an existing tree conflict victim is an error. */ 418 err = svn_wc__internal_conflicted_p(NULL, NULL, &existing_conflict, 419 wc_ctx->db, conflict->local_abspath, 420 scratch_pool); 421 if (err) 422 { 423 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) 424 return svn_error_trace(err); 425 426 svn_error_clear(err); 427 } 428 else if (existing_conflict) 429 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, 430 _("Attempt to add tree conflict that already " 431 "exists at '%s'"), 432 svn_dirent_local_style(conflict->local_abspath, 433 scratch_pool)); 434 else if (!conflict) 435 return SVN_NO_ERROR; 436 437 conflict_skel = svn_wc__conflict_skel_create(scratch_pool); 438 439 SVN_ERR(svn_wc__conflict_skel_add_tree_conflict(conflict_skel, wc_ctx->db, 440 conflict->local_abspath, 441 conflict->reason, 442 conflict->action, 443 NULL, 444 scratch_pool, scratch_pool)); 445 446 switch(conflict->operation) 447 { 448 case svn_wc_operation_update: 449 default: 450 SVN_ERR(svn_wc__conflict_skel_set_op_update(conflict_skel, 451 conflict->src_left_version, 452 conflict->src_right_version, 453 scratch_pool, scratch_pool)); 454 break; 455 case svn_wc_operation_switch: 456 SVN_ERR(svn_wc__conflict_skel_set_op_switch(conflict_skel, 457 conflict->src_left_version, 458 conflict->src_right_version, 459 scratch_pool, scratch_pool)); 460 break; 461 case svn_wc_operation_merge: 462 SVN_ERR(svn_wc__conflict_skel_set_op_merge(conflict_skel, 463 conflict->src_left_version, 464 conflict->src_right_version, 465 scratch_pool, scratch_pool)); 466 break; 467 } 468 469 return svn_error_trace( 470 svn_wc__db_op_mark_conflict(wc_ctx->db, conflict->local_abspath, 471 conflict_skel, NULL, scratch_pool)); 472} 473 474 475svn_error_t * 476svn_wc__get_tree_conflict(const svn_wc_conflict_description2_t **tree_conflict, 477 svn_wc_context_t *wc_ctx, 478 const char *local_abspath, 479 apr_pool_t *result_pool, 480 apr_pool_t *scratch_pool) 481{ 482 const apr_array_header_t *conflicts; 483 int i; 484 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); 485 486 SVN_ERR(svn_wc__read_conflicts(&conflicts, 487 wc_ctx->db, local_abspath, FALSE, 488 scratch_pool, scratch_pool)); 489 490 if (!conflicts || conflicts->nelts == 0) 491 { 492 *tree_conflict = NULL; 493 return SVN_NO_ERROR; 494 } 495 496 for (i = 0; i < conflicts->nelts; i++) 497 { 498 const svn_wc_conflict_description2_t *desc; 499 500 desc = APR_ARRAY_IDX(conflicts, i, svn_wc_conflict_description2_t *); 501 502 if (desc->kind == svn_wc_conflict_kind_tree) 503 { 504 *tree_conflict = svn_wc__conflict_description2_dup(desc, 505 result_pool); 506 return SVN_NO_ERROR; 507 } 508 } 509 510 *tree_conflict = NULL; 511 return SVN_NO_ERROR; 512} 513 514