status-cmd.c revision 299742
1/* 2 * status-cmd.c -- Display status information in current directory 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 30#include "svn_hash.h" 31#include "svn_string.h" 32#include "svn_wc.h" 33#include "svn_client.h" 34#include "svn_error_codes.h" 35#include "svn_error.h" 36#include "svn_pools.h" 37#include "svn_xml.h" 38#include "svn_dirent_uri.h" 39#include "svn_path.h" 40#include "svn_cmdline.h" 41#include "cl.h" 42 43#include "svn_private_config.h" 44#include "private/svn_wc_private.h" 45 46 47 48/*** Code. ***/ 49 50struct status_baton 51{ 52 /* These fields all correspond to the ones in the 53 svn_cl__print_status() interface. */ 54 const char *target_abspath; 55 const char *target_path; 56 svn_boolean_t suppress_externals_placeholders; 57 svn_boolean_t detailed; 58 svn_boolean_t show_last_committed; 59 svn_boolean_t skip_unrecognized; 60 svn_boolean_t repos_locks; 61 62 apr_hash_t *cached_changelists; 63 apr_pool_t *cl_pool; /* where cached changelists are allocated */ 64 65 svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get 66 errors while printing to stdout */ 67 svn_boolean_t xml_mode; 68 69 /* Conflict stats. */ 70 unsigned int text_conflicts; 71 unsigned int prop_conflicts; 72 unsigned int tree_conflicts; 73 74 svn_client_ctx_t *ctx; 75}; 76 77 78struct status_cache 79{ 80 const char *path; 81 const char *target_abspath; 82 const char *target_path; 83 svn_client_status_t *status; 84}; 85 86/* Print conflict stats accumulated in status baton SB. 87 * Do temporary allocations in POOL. */ 88static svn_error_t * 89print_conflict_stats(struct status_baton *sb, apr_pool_t *pool) 90{ 91 if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 || 92 sb->tree_conflicts > 0) 93 SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n"))); 94 95 if (sb->text_conflicts > 0) 96 SVN_ERR(svn_cmdline_printf 97 (pool, _(" Text conflicts: %u\n"), sb->text_conflicts)); 98 99 if (sb->prop_conflicts > 0) 100 SVN_ERR(svn_cmdline_printf 101 (pool, _(" Property conflicts: %u\n"), sb->prop_conflicts)); 102 103 if (sb->tree_conflicts > 0) 104 SVN_ERR(svn_cmdline_printf 105 (pool, _(" Tree conflicts: %u\n"), sb->tree_conflicts)); 106 107 return SVN_NO_ERROR; 108} 109 110/* Prints XML target element with path attribute TARGET, using POOL for 111 temporary allocations. */ 112static svn_error_t * 113print_start_target_xml(const char *target, apr_pool_t *pool) 114{ 115 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 116 117 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", 118 "path", target, SVN_VA_NULL); 119 120 return svn_cl__error_checked_fputs(sb->data, stdout); 121} 122 123 124/* Finish a target element by optionally printing an against element if 125 * REPOS_REV is a valid revision number, and then printing an target end tag. 126 * Use POOL for temporary allocations. */ 127static svn_error_t * 128print_finish_target_xml(svn_revnum_t repos_rev, 129 apr_pool_t *pool) 130{ 131 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 132 133 if (SVN_IS_VALID_REVNUM(repos_rev)) 134 { 135 const char *repos_rev_str; 136 repos_rev_str = apr_psprintf(pool, "%ld", repos_rev); 137 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against", 138 "revision", repos_rev_str, SVN_VA_NULL); 139 } 140 141 svn_xml_make_close_tag(&sb, pool, "target"); 142 143 return svn_cl__error_checked_fputs(sb->data, stdout); 144} 145 146 147/* Function which *actually* causes a status structure to be output to 148 the user. Called by both print_status() and svn_cl__status(). */ 149static svn_error_t * 150print_status_normal_or_xml(void *baton, 151 const char *path, 152 const svn_client_status_t *status, 153 apr_pool_t *pool) 154{ 155 struct status_baton *sb = baton; 156 157 if (sb->xml_mode) 158 return svn_cl__print_status_xml(sb->target_abspath, sb->target_path, 159 path, status, sb->ctx, pool); 160 else 161 return svn_cl__print_status(sb->target_abspath, sb->target_path, 162 path, status, 163 sb->suppress_externals_placeholders, 164 sb->detailed, 165 sb->show_last_committed, 166 sb->skip_unrecognized, 167 sb->repos_locks, 168 &sb->text_conflicts, 169 &sb->prop_conflicts, 170 &sb->tree_conflicts, 171 sb->ctx, 172 pool); 173} 174 175 176/* A status callback function for printing STATUS for PATH. */ 177static svn_error_t * 178print_status(void *baton, 179 const char *path, 180 const svn_client_status_t *status, 181 apr_pool_t *pool) 182{ 183 struct status_baton *sb = baton; 184 const char *local_abspath = status->local_abspath; 185 186 /* ### The revision information with associates are based on what 187 * ### _read_info() returns. The svn_wc_status_func4_t callback is 188 * ### suppposed to handle the gathering of additional information from the 189 * ### WORKING nodes on its own. Until we've agreed on how the CLI should 190 * ### handle the revision information, we use this approach to stay compat 191 * ### with our testsuite. */ 192 if (status->versioned 193 && !SVN_IS_VALID_REVNUM(status->revision) 194 && !status->copied 195 && (status->node_status == svn_wc_status_deleted 196 || status->node_status == svn_wc_status_replaced)) 197 { 198 svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool); 199 200 /* Copied is FALSE, so either we have a local addition, or we have 201 a delete that directly shadows a BASE node */ 202 203 switch(status->node_status) 204 { 205 case svn_wc_status_replaced: 206 /* Just retrieve the revision below the replacement. 207 The other fields are filled by a copy. 208 (With ! copied, we know we have a BASE node) 209 210 ### Is this really what we want to provide? */ 211 SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, 212 NULL, NULL, NULL, 213 sb->ctx->wc_ctx, 214 local_abspath, 215 sb->cl_pool, pool)); 216 break; 217 case svn_wc_status_deleted: 218 /* Retrieve some data from the original version below the delete */ 219 SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, 220 &twks->changed_rev, 221 &twks->changed_date, 222 &twks->changed_author, 223 sb->ctx->wc_ctx, 224 local_abspath, 225 sb->cl_pool, pool)); 226 break; 227 228 default: 229 /* This space intentionally left blank. */ 230 break; 231 } 232 233 status = twks; 234 } 235 236 /* If the path is part of a changelist, then we don't print 237 the item, but instead dup & cache the status structure for later. */ 238 if (status->changelist) 239 { 240 /* The hash maps a changelist name to an array of status_cache 241 structures. */ 242 apr_array_header_t *path_array; 243 const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist); 244 struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache)); 245 scache->path = apr_pstrdup(sb->cl_pool, path); 246 scache->target_abspath = apr_pstrdup(sb->cl_pool, sb->target_abspath); 247 scache->target_path = apr_pstrdup(sb->cl_pool, sb->target_path); 248 scache->status = svn_client_status_dup(status, sb->cl_pool); 249 250 path_array = 251 svn_hash_gets(sb->cached_changelists, cl_key); 252 if (path_array == NULL) 253 { 254 path_array = apr_array_make(sb->cl_pool, 1, 255 sizeof(struct status_cache *)); 256 svn_hash_sets(sb->cached_changelists, cl_key, path_array); 257 } 258 259 APR_ARRAY_PUSH(path_array, struct status_cache *) = scache; 260 return SVN_NO_ERROR; 261 } 262 263 return print_status_normal_or_xml(baton, path, status, pool); 264} 265 266/* This implements the `svn_opt_subcommand_t' interface. */ 267svn_error_t * 268svn_cl__status(apr_getopt_t *os, 269 void *baton, 270 apr_pool_t *scratch_pool) 271{ 272 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 273 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 274 apr_array_header_t *targets; 275 apr_pool_t *iterpool; 276 apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool); 277 int i; 278 svn_opt_revision_t rev; 279 struct status_baton sb; 280 281 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 282 opt_state->targets, 283 ctx, FALSE, 284 scratch_pool)); 285 286 /* Add "." if user passed 0 arguments */ 287 svn_opt_push_implicit_dot_target(targets, scratch_pool); 288 289 SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); 290 291 /* We want our -u statuses to be against HEAD by default. */ 292 if (opt_state->start_revision.kind == svn_opt_revision_unspecified) 293 rev.kind = svn_opt_revision_head; 294 else if (! opt_state->update) 295 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 296 _("--revision (-r) option valid only with " 297 "--show-updates (-u) option")); 298 else 299 rev = opt_state->start_revision; 300 301 sb.had_print_error = FALSE; 302 303 if (opt_state->xml) 304 { 305 /* If output is not incremental, output the XML header and wrap 306 everything in a top-level element. This makes the output in 307 its entirety a well-formed XML document. */ 308 if (! opt_state->incremental) 309 SVN_ERR(svn_cl__xml_print_header("status", scratch_pool)); 310 } 311 else 312 { 313 if (opt_state->incremental) 314 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 315 _("'incremental' option only valid in XML " 316 "mode")); 317 } 318 319 sb.suppress_externals_placeholders = (opt_state->quiet 320 && (! opt_state->verbose)); 321 sb.detailed = (opt_state->verbose || opt_state->update); 322 sb.show_last_committed = opt_state->verbose; 323 sb.skip_unrecognized = opt_state->quiet; 324 sb.repos_locks = opt_state->update; 325 sb.xml_mode = opt_state->xml; 326 sb.cached_changelists = master_cl_hash; 327 sb.cl_pool = scratch_pool; 328 sb.text_conflicts = 0; 329 sb.prop_conflicts = 0; 330 sb.tree_conflicts = 0; 331 sb.ctx = ctx; 332 333 SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); 334 335 iterpool = svn_pool_create(scratch_pool); 336 for (i = 0; i < targets->nelts; i++) 337 { 338 const char *target = APR_ARRAY_IDX(targets, i, const char *); 339 svn_revnum_t repos_rev = SVN_INVALID_REVNUM; 340 341 svn_pool_clear(iterpool); 342 343 SVN_ERR(svn_dirent_get_absolute(&(sb.target_abspath), target, 344 scratch_pool)); 345 sb.target_path = target; 346 347 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 348 349 if (opt_state->xml) 350 SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool), 351 iterpool)); 352 353 /* Retrieve a hash of status structures with the information 354 requested by the user. */ 355 SVN_ERR(svn_cl__try(svn_client_status6(&repos_rev, ctx, target, &rev, 356 opt_state->depth, 357 opt_state->verbose, 358 opt_state->update, 359 TRUE /* check_working_copy */, 360 opt_state->no_ignore, 361 opt_state->ignore_externals, 362 FALSE /* depth_as_sticky */, 363 opt_state->changelists, 364 print_status, &sb, 365 iterpool), 366 NULL, opt_state->quiet, 367 /* not versioned: */ 368 SVN_ERR_WC_NOT_WORKING_COPY, 369 SVN_ERR_WC_PATH_NOT_FOUND, 370 0)); 371 372 if (opt_state->xml) 373 SVN_ERR(print_finish_target_xml(repos_rev, iterpool)); 374 } 375 376 /* If any paths were cached because they were associated with 377 changelists, we can now display them as grouped changelists. */ 378 if (apr_hash_count(master_cl_hash) > 0) 379 { 380 apr_hash_index_t *hi; 381 svn_stringbuf_t *buf; 382 383 if (opt_state->xml) 384 buf = svn_stringbuf_create_empty(scratch_pool); 385 386 for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi; 387 hi = apr_hash_next(hi)) 388 { 389 const char *changelist_name = apr_hash_this_key(hi); 390 apr_array_header_t *path_array = apr_hash_this_val(hi); 391 int j; 392 393 /* ### TODO: For non-XML output, we shouldn't print the 394 ### leading \n on the first changelist if there were no 395 ### non-changelist entries. */ 396 if (opt_state->xml) 397 { 398 svn_stringbuf_setempty(buf); 399 svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 400 "changelist", "name", changelist_name, 401 SVN_VA_NULL); 402 SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); 403 } 404 else 405 SVN_ERR(svn_cmdline_printf(scratch_pool, 406 _("\n--- Changelist '%s':\n"), 407 changelist_name)); 408 409 for (j = 0; j < path_array->nelts; j++) 410 { 411 struct status_cache *scache = 412 APR_ARRAY_IDX(path_array, j, struct status_cache *); 413 sb.target_abspath = scache->target_abspath; 414 sb.target_path = scache->target_path; 415 SVN_ERR(print_status_normal_or_xml(&sb, scache->path, 416 scache->status, scratch_pool)); 417 } 418 419 if (opt_state->xml) 420 { 421 svn_stringbuf_setempty(buf); 422 svn_xml_make_close_tag(&buf, scratch_pool, "changelist"); 423 SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); 424 } 425 } 426 } 427 svn_pool_destroy(iterpool); 428 429 if (opt_state->xml && (! opt_state->incremental)) 430 SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool)); 431 432 if (! opt_state->quiet && ! opt_state->xml) 433 SVN_ERR(print_conflict_stats(&sb, scratch_pool)); 434 435 return SVN_NO_ERROR; 436} 437