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