status.c revision 262253
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 PATH 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. */ 148static const char * 149make_relpath(const char *target_abspath, 150 const char *target_path, 151 const char *path, 152 apr_pool_t *result_pool, 153 apr_pool_t *scratch_pool) 154{ 155 const char *la; 156 const char *parent_dir_els = ""; 157 const char *abspath, *relative; 158 svn_error_t *err = svn_dirent_get_absolute(&abspath, path, scratch_pool); 159 160 if (err) 161 { 162 /* We probably got passed some invalid path. */ 163 svn_error_clear(err); 164 return apr_pstrdup(result_pool, path); 165 } 166 167 relative = svn_dirent_skip_ancestor(target_abspath, abspath); 168 if (relative) 169 { 170 return svn_dirent_join(target_path, relative, result_pool); 171 } 172 173 /* An example: 174 * relative_to_path = /a/b/c 175 * path = /a/x/y/z 176 * result = ../../x/y/z 177 * 178 * Another example (Windows specific): 179 * relative_to_path = F:/wc 180 * path = C:/wc 181 * result = C:/wc 182 */ 183 184 /* Skip the common ancestor of both paths, here '/a'. */ 185 la = svn_dirent_get_longest_ancestor(target_abspath, 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, path); 191 } 192 relative = svn_dirent_skip_ancestor(la, target_abspath); 193 path = svn_dirent_skip_ancestor(la, path); 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 (*relative) 202 { 203 svn_dirent_split(&relative, NULL, relative, 204 scratch_pool); 205 parent_dir_els = svn_dirent_join(parent_dir_els, "..", scratch_pool); 206 } 207 208 return svn_dirent_join(parent_dir_els, path, 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 path = make_relpath(target_abspath, target_path, path, pool, pool); 236 237 /* For historic reasons svn ignores the property status for added nodes, even 238 if these nodes were copied and have local property changes. 239 240 Note that it doesn't do this on replacements, or children of copies. 241 242 ### Our test suite would catch more errors if we reported property 243 changes on copies. */ 244 if (node_status == svn_wc_status_added) 245 prop_status = svn_wc_status_none; 246 247 /* To indicate this node is the victim of a tree conflict, we show 248 'C' in the tree-conflict column, overriding any other status. 249 We also print a separate line describing the nature of the tree 250 conflict. */ 251 if (status->conflicted) 252 { 253 const char *desc; 254 const char *local_abspath = status->local_abspath; 255 svn_boolean_t text_conflicted; 256 svn_boolean_t prop_conflicted; 257 svn_boolean_t tree_conflicted; 258 259 if (status->versioned) 260 { 261 svn_error_t *err; 262 263 err = svn_wc_conflicted_p3(&text_conflicted, 264 &prop_conflicted, 265 &tree_conflicted, ctx->wc_ctx, 266 local_abspath, pool); 267 268 if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) 269 { 270 svn_error_clear(err); 271 text_conflicted = FALSE; 272 prop_conflicted = FALSE; 273 tree_conflicted = FALSE; 274 } 275 else 276 SVN_ERR(err); 277 } 278 else 279 { 280 text_conflicted = FALSE; 281 prop_conflicted = FALSE; 282 tree_conflicted = TRUE; 283 } 284 285 if (tree_conflicted) 286 { 287 const svn_wc_conflict_description2_t *tree_conflict; 288 SVN_ERR(svn_wc__get_tree_conflict(&tree_conflict, ctx->wc_ctx, 289 local_abspath, pool, pool)); 290 SVN_ERR_ASSERT(tree_conflict != NULL); 291 292 tree_status_code = 'C'; 293 SVN_ERR(svn_cl__get_human_readable_tree_conflict_description( 294 &desc, tree_conflict, pool)); 295 tree_desc_line = apr_psprintf(pool, "\n > %s", desc); 296 (*tree_conflicts)++; 297 } 298 else if (text_conflicted) 299 (*text_conflicts)++; 300 else if (prop_conflicted) 301 (*prop_conflicts)++; 302 } 303 304 /* Note that moved-from and moved-to information is only available in STATUS 305 * for (op-)roots of a move. Those are exactly the nodes we want to show 306 * move info for in 'svn status'. See also comments in svn_wc_status3_t. */ 307 if (status->moved_from_abspath && status->moved_to_abspath && 308 strcmp(status->moved_from_abspath, status->moved_to_abspath) == 0) 309 { 310 const char *relpath; 311 312 relpath = make_relpath(target_abspath, target_path, 313 status->moved_from_abspath, 314 pool, pool); 315 relpath = svn_dirent_local_style(relpath, pool); 316 moved_from_line = apr_pstrcat(pool, "\n > ", 317 apr_psprintf(pool, 318 _("swapped places with %s"), 319 relpath), 320 (char *)NULL); 321 } 322 else if (status->moved_from_abspath || status->moved_to_abspath) 323 { 324 const char *relpath; 325 326 if (status->moved_from_abspath) 327 { 328 relpath = make_relpath(target_abspath, target_path, 329 status->moved_from_abspath, 330 pool, pool); 331 relpath = svn_dirent_local_style(relpath, pool); 332 moved_from_line = apr_pstrcat(pool, "\n > ", 333 apr_psprintf(pool, _("moved from %s"), 334 relpath), 335 (char *)NULL); 336 } 337 338 if (status->moved_to_abspath) 339 { 340 relpath = make_relpath(target_abspath, target_path, 341 status->moved_to_abspath, 342 pool, pool); 343 relpath = svn_dirent_local_style(relpath, pool); 344 moved_to_line = apr_pstrcat(pool, "\n > ", 345 apr_psprintf(pool, _("moved to %s"), 346 relpath), 347 (char *)NULL); 348 } 349 } 350 351 path = svn_dirent_local_style(path, pool); 352 353 if (detailed) 354 { 355 char ood_status, lock_status; 356 const char *working_rev; 357 358 if (! status->versioned) 359 working_rev = ""; 360 else if (status->copied 361 || ! SVN_IS_VALID_REVNUM(status->revision)) 362 working_rev = "-"; 363 else 364 working_rev = apr_psprintf(pool, "%ld", status->revision); 365 366 if (status->repos_node_status != svn_wc_status_none) 367 ood_status = '*'; 368 else 369 ood_status = ' '; 370 371 if (repos_locks) 372 { 373 if (status->repos_lock) 374 { 375 if (status->lock) 376 { 377 if (strcmp(status->repos_lock->token, status->lock->token) 378 == 0) 379 lock_status = 'K'; 380 else 381 lock_status = 'T'; 382 } 383 else 384 lock_status = 'O'; 385 } 386 else if (status->lock) 387 lock_status = 'B'; 388 else 389 lock_status = ' '; 390 } 391 else 392 lock_status = (status->lock) ? 'K' : ' '; 393 394 if (show_last_committed) 395 { 396 const char *commit_rev; 397 const char *commit_author; 398 399 if (SVN_IS_VALID_REVNUM(status->changed_rev)) 400 commit_rev = apr_psprintf(pool, "%ld", status->changed_rev); 401 else if (status->versioned) 402 commit_rev = " ? "; 403 else 404 commit_rev = ""; 405 406 if (status->changed_author) 407 commit_author = status->changed_author; 408 else if (status->versioned) 409 commit_author = " ? "; 410 else 411 commit_author = ""; 412 413 SVN_ERR 414 (svn_cmdline_printf(pool, 415 "%c%c%c%c%c%c%c %c %8s %8s %-12s %s%s%s%s\n", 416 generate_status_code(combined_status(status)), 417 generate_status_code(prop_status), 418 status->wc_is_locked ? 'L' : ' ', 419 status->copied ? '+' : ' ', 420 generate_switch_column_code(status), 421 lock_status, 422 tree_status_code, 423 ood_status, 424 working_rev, 425 commit_rev, 426 commit_author, 427 path, 428 moved_to_line, 429 moved_from_line, 430 tree_desc_line)); 431 } 432 else 433 SVN_ERR( 434 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %c %8s %s%s%s%s\n", 435 generate_status_code(combined_status(status)), 436 generate_status_code(prop_status), 437 status->wc_is_locked ? 'L' : ' ', 438 status->copied ? '+' : ' ', 439 generate_switch_column_code(status), 440 lock_status, 441 tree_status_code, 442 ood_status, 443 working_rev, 444 path, 445 moved_to_line, 446 moved_from_line, 447 tree_desc_line)); 448 } 449 else 450 SVN_ERR( 451 svn_cmdline_printf(pool, "%c%c%c%c%c%c%c %s%s%s%s\n", 452 generate_status_code(combined_status(status)), 453 generate_status_code(prop_status), 454 status->wc_is_locked ? 'L' : ' ', 455 status->copied ? '+' : ' ', 456 generate_switch_column_code(status), 457 ((status->lock) 458 ? 'K' : ' '), 459 tree_status_code, 460 path, 461 moved_to_line, 462 moved_from_line, 463 tree_desc_line)); 464 465 return svn_cmdline_fflush(stdout); 466} 467 468 469svn_error_t * 470svn_cl__print_status_xml(const char *target_abspath, 471 const char *target_path, 472 const char *path, 473 const svn_client_status_t *status, 474 svn_client_ctx_t *ctx, 475 apr_pool_t *pool) 476{ 477 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 478 apr_hash_t *att_hash; 479 const char *local_abspath = status->local_abspath; 480 svn_boolean_t tree_conflicted = FALSE; 481 482 if (status->node_status == svn_wc_status_none 483 && status->repos_node_status == svn_wc_status_none) 484 return SVN_NO_ERROR; 485 486 if (status->conflicted) 487 SVN_ERR(svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, 488 ctx->wc_ctx, local_abspath, pool)); 489 490 path = make_relpath(target_abspath, target_path, path, pool, pool); 491 492 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "entry", 493 "path", svn_dirent_local_style(path, pool), NULL); 494 495 att_hash = apr_hash_make(pool); 496 svn_hash_sets(att_hash, "item", 497 generate_status_desc(combined_status(status))); 498 499 svn_hash_sets(att_hash, "props", 500 generate_status_desc( 501 (status->node_status != svn_wc_status_deleted) 502 ? status->prop_status 503 : svn_wc_status_none)); 504 if (status->wc_is_locked) 505 svn_hash_sets(att_hash, "wc-locked", "true"); 506 if (status->copied) 507 svn_hash_sets(att_hash, "copied", "true"); 508 if (status->switched) 509 svn_hash_sets(att_hash, "switched", "true"); 510 if (status->file_external) 511 svn_hash_sets(att_hash, "file-external", "true"); 512 if (status->versioned && ! status->copied) 513 svn_hash_sets(att_hash, "revision", 514 apr_psprintf(pool, "%ld", status->revision)); 515 if (tree_conflicted) 516 svn_hash_sets(att_hash, "tree-conflicted", "true"); 517 if (status->moved_from_abspath || status->moved_to_abspath) 518 { 519 const char *relpath; 520 521 if (status->moved_from_abspath) 522 { 523 relpath = make_relpath(target_abspath, target_path, 524 status->moved_from_abspath, 525 pool, pool); 526 relpath = svn_dirent_local_style(relpath, pool); 527 svn_hash_sets(att_hash, "moved-from", relpath); 528 } 529 if (status->moved_to_abspath) 530 { 531 relpath = make_relpath(target_abspath, target_path, 532 status->moved_to_abspath, 533 pool, pool); 534 relpath = svn_dirent_local_style(relpath, pool); 535 svn_hash_sets(att_hash, "moved-to", relpath); 536 } 537 } 538 svn_xml_make_open_tag_hash(&sb, pool, svn_xml_normal, "wc-status", 539 att_hash); 540 541 if (SVN_IS_VALID_REVNUM(status->changed_rev)) 542 { 543 svn_cl__print_xml_commit(&sb, status->changed_rev, 544 status->changed_author, 545 svn_time_to_cstring(status->changed_date, 546 pool), 547 pool); 548 } 549 550 if (status->lock) 551 svn_cl__print_xml_lock(&sb, status->lock, pool); 552 553 svn_xml_make_close_tag(&sb, pool, "wc-status"); 554 555 if (status->repos_node_status != svn_wc_status_none 556 || status->repos_lock) 557 { 558 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "repos-status", 559 "item", 560 generate_status_desc(combined_repos_status(status)), 561 "props", 562 generate_status_desc(status->repos_prop_status), 563 NULL); 564 if (status->repos_lock) 565 svn_cl__print_xml_lock(&sb, status->repos_lock, pool); 566 567 svn_xml_make_close_tag(&sb, pool, "repos-status"); 568 } 569 570 svn_xml_make_close_tag(&sb, pool, "entry"); 571 572 return svn_cl__error_checked_fputs(sb->data, stdout); 573} 574 575/* Called by status-cmd.c */ 576svn_error_t * 577svn_cl__print_status(const char *target_abspath, 578 const char *target_path, 579 const char *path, 580 const svn_client_status_t *status, 581 svn_boolean_t suppress_externals_placeholders, 582 svn_boolean_t detailed, 583 svn_boolean_t show_last_committed, 584 svn_boolean_t skip_unrecognized, 585 svn_boolean_t repos_locks, 586 unsigned int *text_conflicts, 587 unsigned int *prop_conflicts, 588 unsigned int *tree_conflicts, 589 svn_client_ctx_t *ctx, 590 apr_pool_t *pool) 591{ 592 if (! status 593 || (skip_unrecognized 594 && !(status->versioned 595 || status->conflicted 596 || status->node_status == svn_wc_status_external)) 597 || (status->node_status == svn_wc_status_none 598 && status->repos_node_status == svn_wc_status_none)) 599 return SVN_NO_ERROR; 600 601 /* If we're trying not to print boring "X /path/to/external" 602 lines..." */ 603 if (suppress_externals_placeholders) 604 { 605 /* ... skip regular externals unmodified in the repository. */ 606 if ((status->node_status == svn_wc_status_external) 607 && (status->repos_node_status == svn_wc_status_none) 608 && (! status->conflicted)) 609 return SVN_NO_ERROR; 610 611 /* ... skip file externals that aren't modified locally or 612 remotely, changelisted, or locked (in either sense of the 613 word). */ 614 if ((status->file_external) 615 && (status->repos_node_status == svn_wc_status_none) 616 && ((status->node_status == svn_wc_status_normal) 617 || (status->node_status == svn_wc_status_none)) 618 && ((status->prop_status == svn_wc_status_normal) 619 || (status->prop_status == svn_wc_status_none)) 620 && (! status->changelist) 621 && (! status->lock) 622 && (! status->wc_is_locked) 623 && (! status->conflicted)) 624 return SVN_NO_ERROR; 625 } 626 627 return print_status(target_abspath, target_path, path, 628 detailed, show_last_committed, repos_locks, status, 629 text_conflicts, prop_conflicts, tree_conflicts, 630 ctx, pool); 631} 632