status.c revision 299742
1/* 2 * status.c: the command-line's portion of the "svn status" command 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 27 28/*** Includes. ***/ 29#include "svn_hash.h" 30#include "svn_cmdline.h" 31#include "svn_wc.h" 32#include "svn_dirent_uri.h" 33#include "svn_xml.h" 34#include "svn_time.h" 35#include "cl.h" 36#include "svn_private_config.h" 37#include "cl-conflicts.h" 38#include "private/svn_wc_private.h" 39 40/* Return the single character representation of STATUS */ 41static char 42generate_status_code(enum svn_wc_status_kind status) 43{ 44 switch (status) 45 { 46 case svn_wc_status_none: return ' '; 47 case svn_wc_status_normal: return ' '; 48 case svn_wc_status_added: return 'A'; 49 case svn_wc_status_missing: return '!'; 50 case svn_wc_status_incomplete: return '!'; 51 case svn_wc_status_deleted: return 'D'; 52 case svn_wc_status_replaced: return 'R'; 53 case svn_wc_status_modified: return 'M'; 54 case svn_wc_status_conflicted: return 'C'; 55 case svn_wc_status_obstructed: return '~'; 56 case svn_wc_status_ignored: return 'I'; 57 case svn_wc_status_external: return 'X'; 58 case svn_wc_status_unversioned: return '?'; 59 default: return '?'; 60 } 61} 62 63/* Return the combined STATUS as shown in 'svn status' based 64 on the node status and text status */ 65static enum svn_wc_status_kind 66combined_status(const svn_client_status_t *status) 67{ 68 enum svn_wc_status_kind new_status = status->node_status; 69 70 switch (status->node_status) 71 { 72 case svn_wc_status_conflicted: 73 if (!status->versioned && status->conflicted) 74 { 75 /* Report unversioned tree conflict victims as missing: '!' */ 76 new_status = svn_wc_status_missing; 77 break; 78 } 79 /* fall through */ 80 case svn_wc_status_modified: 81 /* This value might be the property status */ 82 new_status = status->text_status; 83 break; 84 default: 85 break; 86 } 87 88 return new_status; 89} 90 91/* Return the combined repository STATUS as shown in 'svn status' based 92 on the repository node status and repository text status */ 93static enum svn_wc_status_kind 94combined_repos_status(const svn_client_status_t *status) 95{ 96 if (status->repos_node_status == svn_wc_status_modified) 97 return status->repos_text_status; 98 99 return status->repos_node_status; 100} 101 102/* Return the single character representation of the switched column 103 status. */ 104static char 105generate_switch_column_code(const svn_client_status_t *status) 106{ 107 if (status->switched) 108 return 'S'; 109 else if (status->file_external) 110 return 'X'; 111 else 112 return ' '; 113} 114 115/* Return the detailed string representation of STATUS */ 116static const char * 117generate_status_desc(enum svn_wc_status_kind status) 118{ 119 switch (status) 120 { 121 case svn_wc_status_none: return "none"; 122 case svn_wc_status_normal: return "normal"; 123 case svn_wc_status_added: return "added"; 124 case svn_wc_status_missing: return "missing"; 125 case svn_wc_status_incomplete: return "incomplete"; 126 case svn_wc_status_deleted: return "deleted"; 127 case svn_wc_status_replaced: return "replaced"; 128 case svn_wc_status_modified: return "modified"; 129 case svn_wc_status_conflicted: return "conflicted"; 130 case svn_wc_status_obstructed: return "obstructed"; 131 case svn_wc_status_ignored: return "ignored"; 132 case svn_wc_status_external: return "external"; 133 case svn_wc_status_unversioned: return "unversioned"; 134 default: 135 SVN_ERR_MALFUNCTION_NO_RETURN(); 136 } 137} 138 139/* Make a relative path containing '..' elements as needed. 140 TARGET_ABSPATH shall be the absolute version of TARGET_PATH. 141 TARGET_ABSPATH, TARGET_PATH and LOCAL_ABSPATH shall be canonical 142 143 If above conditions are met, a relative path that leads to PATH 144 from TARGET_PATH is returned, but there is no error checking involved. 145 146 The returned path is allocated from RESULT_POOL, all other 147 allocations are made in SCRATCH_POOL. 148 149 Note that it is not possible to just join the resulting path to 150 reconstruct the real path as the "../" paths are relative from 151 a different base than the normal relative paths! 152 */ 153static const char * 154make_relpath(const char *target_abspath, 155 const char *target_path, 156 const char *local_abspath, 157 apr_pool_t *result_pool, 158 apr_pool_t *scratch_pool) 159{ 160 const char *la; 161 const char *parent_dir_els = ""; 162 const char *t_relpath; 163 const char *p_relpath; 164 165#ifdef SVN_DEBUG 166 SVN_ERR_ASSERT_NO_RETURN(svn_dirent_is_absolute(local_abspath)); 167#endif 168 169 t_relpath = svn_dirent_skip_ancestor(target_abspath, local_abspath); 170 171 if (t_relpath) 172 return svn_dirent_join(target_path, t_relpath, result_pool); 173 174 /* An example: 175 * relative_to_path = /a/b/c 176 * path = /a/x/y/z 177 * result = ../../x/y/z 178 * 179 * Another example (Windows specific): 180 * relative_to_path = F:/wc 181 * path = C:/wc 182 * result = C:/wc 183 */ 184 /* Skip the common ancestor of both paths, here '/a'. */ 185 la = svn_dirent_get_longest_ancestor(target_abspath, local_abspath, 186 scratch_pool); 187 if (*la == '\0') 188 { 189 /* Nothing in common: E.g. C:/ vs F:/ on Windows */ 190 return apr_pstrdup(result_pool, local_abspath); 191 } 192 t_relpath = svn_dirent_skip_ancestor(la, target_abspath); 193 p_relpath = svn_dirent_skip_ancestor(la, local_abspath); 194 195 /* In above example, we'd now have: 196 * relative_to_path = b/c 197 * path = x/y/z */ 198 199 /* Count the elements of relative_to_path and prepend as many '..' elements 200 * to path. */ 201 while (*t_relpath) 202 { 203 t_relpath = svn_dirent_dirname(t_relpath, scratch_pool); 204 parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool); 205 } 206 207 /* This returns a ../ style path relative from the status target */ 208 return svn_dirent_join(parent_dir_els, p_relpath, result_pool); 209} 210 211 212/* Print STATUS and PATH in a format determined by DETAILED and 213 SHOW_LAST_COMMITTED. */ 214static svn_error_t * 215print_status(const char *target_abspath, 216 const char *target_path, 217 const char *path, 218 svn_boolean_t detailed, 219 svn_boolean_t show_last_committed, 220 svn_boolean_t repos_locks, 221 const svn_client_status_t *status, 222 unsigned int *text_conflicts, 223 unsigned int *prop_conflicts, 224 unsigned int *tree_conflicts, 225 svn_client_ctx_t *ctx, 226 apr_pool_t *pool) 227{ 228 enum svn_wc_status_kind node_status = status->node_status; 229 enum svn_wc_status_kind prop_status = status->prop_status; 230 char tree_status_code = ' '; 231 const char *tree_desc_line = ""; 232 const char *moved_from_line = ""; 233 const char *moved_to_line = ""; 234 235 /* For historic reasons svn ignores the property status for added nodes, even 236 if these nodes were copied and have local property changes. 237 238 Note that it doesn't do this on replacements, or children of copies. 239 240 ### Our test suite would catch more errors if we reported property 241 changes on copies. */ 242 if (node_status == svn_wc_status_added) 243 prop_status = svn_wc_status_none; 244 245 /* To indicate this node is the victim of a tree conflict, we show 246 'C' in the tree-conflict column, overriding any other status. 247 We also print a separate line describing the nature of the tree 248 conflict. */ 249 if (status->conflicted) 250 { 251 const char *desc; 252 const char *local_abspath = status->local_abspath; 253 svn_boolean_t text_conflicted; 254 svn_boolean_t prop_conflicted; 255 svn_boolean_t tree_conflicted; 256 257 if (status->versioned) 258 { 259 svn_error_t *err; 260 261 err = svn_wc_conflicted_p3(&text_conflicted, 262 &prop_conflicted, 263 &tree_conflicted, ctx->wc_ctx, 264 local_abspath, pool); 265 266 if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) 267 { 268 svn_error_clear(err); 269 text_conflicted = FALSE; 270 prop_conflicted = FALSE; 271 tree_conflicted = FALSE; 272 } 273 else 274 SVN_ERR(err); 275 } 276 else 277 { 278 text_conflicted = FALSE; 279 prop_conflicted = FALSE; 280 tree_conflicted = TRUE; 281 } 282 283 if (tree_conflicted) 284 { 285 const svn_wc_conflict_description2_t *tree_conflict; 286 SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, 287 local_abspath, pool, pool)); 288 SVN_ERR_ASSERT(tree_conflict != NULL); 289 290 tree_status_code = 'C'; 291 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( 292 &desc, tree_conflict, pool)); 293 tree_desc_line = apr_psprintf(pool, "\n > %s", desc); 294 (*tree_conflicts)++; 295 } 296 else if (text_conflicted) 297 (*text_conflicts)++; 298 else if (prop_conflicted) 299 (*prop_conflicts)++; 300 } 301 302 /* Note that moved-from and moved-to information is only available in STATUS 303 * for (op-)roots of a move. Those are exactly the nodes we want to show 304 * move info for in 'svn status'. See also comments in svn_wc_status3_t. */ 305 if (status->moved_from_abspath && status->moved_to_abspath && 306 strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0) 307 { 308 const char *relpath; 309 310 relpath = make_relpath(target_abspath, target_path, 311 status->moved_from_abspath, 312 pool, pool); 313 relpath = svn_dirent_local_style(relpath, pool); 314 moved_from_line = apr_pstrcat(pool, "\n > ", 315 apr_psprintf(pool, 316 _("swapped places with %s"), 317 relpath), 318 SVN_VA_NULL); 319 } 320 else if (status->moved_from_abspath || status->moved_to_abspath) 321 { 322 const char *relpath; 323 324 if (status->moved_from_abspath) 325 { 326 relpath = make_relpath(target_abspath, target_path, 327 status->moved_from_abspath, 328 pool, pool); 329 relpath = svn_dirent_local_style(relpath, pool); 330 moved_from_line = apr_pstrcat(pool, "\n > ", 331 apr_psprintf(pool, _("moved from %s"), 332 relpath), 333 SVN_VA_NULL); 334 } 335 336 if (status->moved_to_abspath) 337 { 338 relpath = make_relpath(target_abspath, target_path, 339 status->moved_to_abspath, 340 pool, pool); 341 relpath = svn_dirent_local_style(relpath, pool); 342 moved_to_line = apr_pstrcat(pool, "\n > ", 343 apr_psprintf(pool, _("moved to %s"), 344 relpath), 345 SVN_VA_NULL); 346 } 347 } 348 349 path = svn_dirent_local_style(path, pool); 350 351 if (detailed) 352 { 353 char ood_status, lock_status; 354 const char *working_rev; 355 356 if (! status->versioned) 357 working_rev = ""; 358 else if (status->copied 359 || ! SVN_IS_VALID_REVNUM(status->revision)) 360 working_rev = "-"; 361 else 362 working_rev = apr_psprintf(pool, "%ld", status->revision); 363 364 if (status->repos_node_status != svn_wc_status_none) 365 ood_status = '*'; 366 else 367 ood_status = ' '; 368 369 if (repos_locks) 370 { 371 if (status->repos_lock) 372 { 373 if (status->lock) 374 { 375 if (strcmp(status->repos_lock->token, status->lock->token) 376 == 0) 377 lock_status = 'K'; 378 else 379 lock_status = 'T'; 380 } 381 else 382 lock_status = 'O'; 383 } 384 else if (status->lock) 385 lock_status = 'B'; 386 else 387 lock_status = ' '; 388 } 389 else 390 lock_status = (status->lock) ? 'K' : ' '; 391 392 if (show_last_committed) 393 { 394 const char *commit_rev; 395 const char *commit_author; 396 397 if (SVN_IS_VALID_REVNUM(status->changed_rev)) 398 commit_rev = apr_psprintf(pool, "%ld", status->changed_rev); 399 else if (status->versioned) 400 commit_rev = " ? "; 401 else 402 commit_rev = ""; 403 404 if (status->changed_author) 405 commit_author = status->changed_author; 406 else if (status->versioned) 407 commit_author = " ? "; 408 else 409 commit_author = ""; 410 411 SVN_ERR 412 (svn_cmdline_printf(pool, 413 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n", 414 generate_status_code(combined_status(status)), 415 generate_status_code(prop_status), 416 status->wc_is_locked ? 'L' : ' ', 417 status->copied ? '+' : ' ', 418 generate_switch_column_code(status), 419 lock_status, 420 tree_status_code, 421 ood_status, 422 working_rev, 423 commit_rev, 424 commit_author, 425 path, 426 moved_to_line, 427 moved_from_line, 428 tree_desc_line)); 429 } 430 else 431 SVN_ERR( 432 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n", 433 generate_status_code(combined_status(status)), 434 generate_status_code(prop_status), 435 status->wc_is_locked ? 'L' : ' ', 436 status->copied ? '+' : ' ', 437 generate_switch_column_code(status), 438 lock_status, 439 tree_status_code, 440 ood_status, 441 working_rev, 442 path, 443 moved_to_line, 444 moved_from_line, 445 tree_desc_line)); 446 } 447 else 448 SVN_ERR( 449 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n", 450 generate_status_code(combined_status(status)), 451 generate_status_code(prop_status), 452 status->wc_is_locked ? 'L' : ' ', 453 status->copied ? '+' : ' ', 454 generate_switch_column_code(status), 455 ((status->lock) 456 ? 'K' : ' '), 457 tree_status_code, 458 path, 459 moved_to_line, 460 moved_from_line, 461 tree_desc_line)); 462 463 return svn_cmdline_fflush(stdout); 464} 465 466 467svn_error_t * 468svn_cl__print_status_xml(const char *target_abspath, 469 const char *target_path, 470 const char *path, 471 const svn_client_status_t *status, 472 svn_client_ctx_t *ctx, 473 apr_pool_t *pool) 474{ 475 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 476 apr_hash_t *att_hash; 477 const char *local_abspath = status->local_abspath; 478 svn_boolean_t tree_conflicted = FALSE; 479 480 if (status->node_status == svn_wc_status_none 481 && status->repos_node_status == svn_wc_status_none) 482 return SVN_NO_ERROR; 483 484 if (status->conflicted) 485 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, 486 ctx->wc_ctx, local_abspath, pool)); 487 488 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", 489 "path", svn_dirent_local_style(path, pool), 490 SVN_VA_NULL); 491 492 att_hash = apr_hash_make(pool); 493 svn_hash_sets(att_hash, "item", 494 generate_status_desc(combined_status(status))); 495 496 svn_hash_sets(att_hash, "props", 497 generate_status_desc( 498 (status->node_status != svn_wc_status_deleted) 499 ? status->prop_status 500 : svn_wc_status_none)); 501 if (status->wc_is_locked) 502 svn_hash_sets(att_hash, "wc-locked", "true"); 503 if (status->copied) 504 svn_hash_sets(att_hash, "copied", "true"); 505 if (status->switched) 506 svn_hash_sets(att_hash, "switched", "true"); 507 if (status->file_external) 508 svn_hash_sets(att_hash, "file-external", "true"); 509 if (status->versioned && ! status->copied) 510 svn_hash_sets(att_hash, "revision", 511 apr_psprintf(pool, "%ld", status->revision)); 512 if (tree_conflicted) 513 svn_hash_sets(att_hash, "tree-conflicted", "true"); 514 if (status->moved_from_abspath || status->moved_to_abspath) 515 { 516 const char *relpath; 517 518 if (status->moved_from_abspath) 519 { 520 relpath = make_relpath(target_abspath, target_path, 521 status->moved_from_abspath, 522 pool, pool); 523 relpath = svn_dirent_local_style(relpath, pool); 524 svn_hash_sets(att_hash, "moved-from", relpath); 525 } 526 if (status->moved_to_abspath) 527 { 528 relpath = make_relpath(target_abspath, target_path, 529 status->moved_to_abspath, 530 pool, pool); 531 relpath = svn_dirent_local_style(relpath, pool); 532 svn_hash_sets(att_hash, "moved-to", relpath); 533 } 534 } 535 svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status", 536 att_hash); 537 538 if (SVN_IS_VALID_REVNUM(status->changed_rev)) 539 { 540 svn_cl__print_xml_commit(&sb, status->changed_rev, 541 status->changed_author, 542 svn_time_to_cstring(status->changed_date, 543 pool), 544 pool); 545 } 546 547 if (status->lock) 548 svn_cl__print_xml_lock(&sb, status->lock, pool); 549 550 svn_xml_make_close_tag(&sb, pool, "wc-status"); 551 552 if (status->repos_node_status != svn_wc_status_none 553 || status->repos_lock) 554 { 555 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status", 556 "item", 557 generate_status_desc(combined_repos_status(status)), 558 "props", 559 generate_status_desc(status->repos_prop_status), 560 SVN_VA_NULL); 561 if (status->repos_lock) 562 svn_cl__print_xml_lock(&sb, status->repos_lock, pool); 563 564 svn_xml_make_close_tag(&sb, pool, "repos-status"); 565 } 566 567 svn_xml_make_close_tag(&sb, pool, "entry"); 568 569 return svn_cl__error_checked_fputs(sb->data, stdout); 570} 571 572/* Called by status-cmd.c */ 573svn_error_t * 574svn_cl__print_status(const char *target_abspath, 575 const char *target_path, 576 const char *path, 577 const svn_client_status_t *status, 578 svn_boolean_t suppress_externals_placeholders, 579 svn_boolean_t detailed, 580 svn_boolean_t show_last_committed, 581 svn_boolean_t skip_unrecognized, 582 svn_boolean_t repos_locks, 583 unsigned int *text_conflicts, 584 unsigned int *prop_conflicts, 585 unsigned int *tree_conflicts, 586 svn_client_ctx_t *ctx, 587 apr_pool_t *pool) 588{ 589 if (! status 590 || (skip_unrecognized 591 && !(status->versioned 592 || status->conflicted 593 || status->node_status == svn_wc_status_external)) 594 || (status->node_status == svn_wc_status_none 595 && status->repos_node_status == svn_wc_status_none)) 596 return SVN_NO_ERROR; 597 598 /* If we're trying not to print boring "X /path/to/external" 599 lines..." */ 600 if (suppress_externals_placeholders) 601 { 602 /* ... skip regular externals unmodified in the repository. */ 603 if ((status->node_status == svn_wc_status_external) 604 && (status->repos_node_status == svn_wc_status_none) 605 && (! status->conflicted)) 606 return SVN_NO_ERROR; 607 608 /* ... skip file externals that aren't modified locally or 609 remotely, changelisted, or locked (in either sense of the 610 word). */ 611 if ((status->file_external) 612 && (status->repos_node_status == svn_wc_status_none) 613 && ((status->node_status == svn_wc_status_normal) 614 || (status->node_status == svn_wc_status_none)) 615 && ((status->prop_status == svn_wc_status_normal) 616 || (status->prop_status == svn_wc_status_none)) 617 && (! status->changelist) 618 && (! status->lock) 619 && (! status->wc_is_locked) 620 && (! status->conflicted)) 621 return SVN_NO_ERROR; 622 } 623 624 return print_status(target_abspath, target_path, path, 625 detailed, show_last_committed, repos_locks, status, 626 text_conflicts, prop_conflicts, tree_conflicts, 627 ctx, pool); 628} 629