1251881Speter/* 2251881Speter * status-cmd.c -- Display status information in current directory 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_string.h" 32251881Speter#include "svn_wc.h" 33251881Speter#include "svn_client.h" 34251881Speter#include "svn_error_codes.h" 35251881Speter#include "svn_error.h" 36251881Speter#include "svn_pools.h" 37251881Speter#include "svn_xml.h" 38251881Speter#include "svn_dirent_uri.h" 39251881Speter#include "svn_path.h" 40251881Speter#include "svn_cmdline.h" 41251881Speter#include "cl.h" 42251881Speter 43251881Speter#include "svn_private_config.h" 44251881Speter#include "private/svn_wc_private.h" 45251881Speter 46251881Speter 47251881Speter 48251881Speter/*** Code. ***/ 49251881Speter 50251881Speterstruct status_baton 51251881Speter{ 52251881Speter /* These fields all correspond to the ones in the 53251881Speter svn_cl__print_status() interface. */ 54251881Speter const char *cwd_abspath; 55251881Speter svn_boolean_t suppress_externals_placeholders; 56251881Speter svn_boolean_t detailed; 57251881Speter svn_boolean_t show_last_committed; 58251881Speter svn_boolean_t skip_unrecognized; 59251881Speter svn_boolean_t repos_locks; 60251881Speter 61251881Speter apr_hash_t *cached_changelists; 62251881Speter apr_pool_t *cl_pool; /* where cached changelists are allocated */ 63251881Speter 64251881Speter svn_boolean_t had_print_error; /* To avoid printing lots of errors if we get 65251881Speter errors while printing to stdout */ 66251881Speter svn_boolean_t xml_mode; 67251881Speter 68251881Speter /* Conflict stats. */ 69251881Speter unsigned int text_conflicts; 70251881Speter unsigned int prop_conflicts; 71251881Speter unsigned int tree_conflicts; 72251881Speter 73251881Speter svn_client_ctx_t *ctx; 74251881Speter}; 75251881Speter 76251881Speter 77251881Speterstruct status_cache 78251881Speter{ 79251881Speter const char *path; 80251881Speter svn_client_status_t *status; 81251881Speter}; 82251881Speter 83251881Speter/* Print conflict stats accumulated in status baton SB. 84251881Speter * Do temporary allocations in POOL. */ 85251881Speterstatic svn_error_t * 86251881Speterprint_conflict_stats(struct status_baton *sb, apr_pool_t *pool) 87251881Speter{ 88251881Speter if (sb->text_conflicts > 0 || sb->prop_conflicts > 0 || 89251881Speter sb->tree_conflicts > 0) 90251881Speter SVN_ERR(svn_cmdline_printf(pool, "%s", _("Summary of conflicts:\n"))); 91251881Speter 92251881Speter if (sb->text_conflicts > 0) 93251881Speter SVN_ERR(svn_cmdline_printf 94251881Speter (pool, _(" Text conflicts: %u\n"), sb->text_conflicts)); 95251881Speter 96251881Speter if (sb->prop_conflicts > 0) 97251881Speter SVN_ERR(svn_cmdline_printf 98251881Speter (pool, _(" Property conflicts: %u\n"), sb->prop_conflicts)); 99251881Speter 100251881Speter if (sb->tree_conflicts > 0) 101251881Speter SVN_ERR(svn_cmdline_printf 102251881Speter (pool, _(" Tree conflicts: %u\n"), sb->tree_conflicts)); 103251881Speter 104251881Speter return SVN_NO_ERROR; 105251881Speter} 106251881Speter 107251881Speter/* Prints XML target element with path attribute TARGET, using POOL for 108251881Speter temporary allocations. */ 109251881Speterstatic svn_error_t * 110251881Speterprint_start_target_xml(const char *target, apr_pool_t *pool) 111251881Speter{ 112251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 113251881Speter 114251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target", 115251881Speter "path", target, NULL); 116251881Speter 117251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 118251881Speter} 119251881Speter 120251881Speter 121251881Speter/* Finish a target element by optionally printing an against element if 122251881Speter * REPOS_REV is a valid revision number, and then printing an target end tag. 123251881Speter * Use POOL for temporary allocations. */ 124251881Speterstatic svn_error_t * 125251881Speterprint_finish_target_xml(svn_revnum_t repos_rev, 126251881Speter apr_pool_t *pool) 127251881Speter{ 128251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 129251881Speter 130251881Speter if (SVN_IS_VALID_REVNUM(repos_rev)) 131251881Speter { 132251881Speter const char *repos_rev_str; 133251881Speter repos_rev_str = apr_psprintf(pool, "%ld", repos_rev); 134251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "against", 135251881Speter "revision", repos_rev_str, NULL); 136251881Speter } 137251881Speter 138251881Speter svn_xml_make_close_tag(&sb, pool, "target"); 139251881Speter 140251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 141251881Speter} 142251881Speter 143251881Speter 144251881Speter/* Function which *actually* causes a status structure to be output to 145251881Speter the user. Called by both print_status() and svn_cl__status(). */ 146251881Speterstatic svn_error_t * 147251881Speterprint_status_normal_or_xml(void *baton, 148251881Speter const char *path, 149251881Speter const svn_client_status_t *status, 150251881Speter apr_pool_t *pool) 151251881Speter{ 152251881Speter struct status_baton *sb = baton; 153251881Speter 154251881Speter if (sb->xml_mode) 155251881Speter return svn_cl__print_status_xml(sb->cwd_abspath, path, status, 156251881Speter sb->ctx, pool); 157251881Speter else 158251881Speter return svn_cl__print_status(sb->cwd_abspath, path, status, 159251881Speter sb->suppress_externals_placeholders, 160251881Speter sb->detailed, 161251881Speter sb->show_last_committed, 162251881Speter sb->skip_unrecognized, 163251881Speter sb->repos_locks, 164251881Speter &sb->text_conflicts, 165251881Speter &sb->prop_conflicts, 166251881Speter &sb->tree_conflicts, 167251881Speter sb->ctx, 168251881Speter pool); 169251881Speter} 170251881Speter 171251881Speter 172251881Speter/* A status callback function for printing STATUS for PATH. */ 173251881Speterstatic svn_error_t * 174251881Speterprint_status(void *baton, 175251881Speter const char *path, 176251881Speter const svn_client_status_t *status, 177251881Speter apr_pool_t *pool) 178251881Speter{ 179251881Speter struct status_baton *sb = baton; 180251881Speter const char *local_abspath = status->local_abspath; 181251881Speter 182251881Speter /* ### The revision information with associates are based on what 183251881Speter * ### _read_info() returns. The svn_wc_status_func4_t callback is 184251881Speter * ### suppposed to handle the gathering of additional information from the 185251881Speter * ### WORKING nodes on its own. Until we've agreed on how the CLI should 186251881Speter * ### handle the revision information, we use this appproach to stay compat 187251881Speter * ### with our testsuite. */ 188251881Speter if (status->versioned 189251881Speter && !SVN_IS_VALID_REVNUM(status->revision) 190251881Speter && !status->copied 191251881Speter && (status->node_status == svn_wc_status_deleted 192251881Speter || status->node_status == svn_wc_status_replaced)) 193251881Speter { 194251881Speter svn_client_status_t *twks = svn_client_status_dup(status, sb->cl_pool); 195251881Speter 196251881Speter /* Copied is FALSE, so either we have a local addition, or we have 197251881Speter a delete that directly shadows a BASE node */ 198251881Speter 199251881Speter switch(status->node_status) 200251881Speter { 201251881Speter case svn_wc_status_replaced: 202251881Speter /* Just retrieve the revision below the replacement. 203251881Speter The other fields are filled by a copy. 204251881Speter (With ! copied, we know we have a BASE node) 205251881Speter 206251881Speter ### Is this really what we want to provide? */ 207251881Speter SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, 208251881Speter NULL, NULL, NULL, 209251881Speter sb->ctx->wc_ctx, 210251881Speter local_abspath, 211251881Speter sb->cl_pool, pool)); 212251881Speter break; 213251881Speter case svn_wc_status_deleted: 214251881Speter /* Retrieve some data from the original version below the delete */ 215251881Speter SVN_ERR(svn_wc__node_get_pre_ng_status_data(&twks->revision, 216251881Speter &twks->changed_rev, 217251881Speter &twks->changed_date, 218251881Speter &twks->changed_author, 219251881Speter sb->ctx->wc_ctx, 220251881Speter local_abspath, 221251881Speter sb->cl_pool, pool)); 222251881Speter break; 223251881Speter 224251881Speter default: 225251881Speter /* This space intentionally left blank. */ 226251881Speter break; 227251881Speter } 228251881Speter 229251881Speter status = twks; 230251881Speter } 231251881Speter 232251881Speter /* If the path is part of a changelist, then we don't print 233251881Speter the item, but instead dup & cache the status structure for later. */ 234251881Speter if (status->changelist) 235251881Speter { 236251881Speter /* The hash maps a changelist name to an array of status_cache 237251881Speter structures. */ 238251881Speter apr_array_header_t *path_array; 239251881Speter const char *cl_key = apr_pstrdup(sb->cl_pool, status->changelist); 240251881Speter struct status_cache *scache = apr_pcalloc(sb->cl_pool, sizeof(*scache)); 241251881Speter scache->path = apr_pstrdup(sb->cl_pool, path); 242251881Speter scache->status = svn_client_status_dup(status, sb->cl_pool); 243251881Speter 244251881Speter path_array = 245251881Speter svn_hash_gets(sb->cached_changelists, cl_key); 246251881Speter if (path_array == NULL) 247251881Speter { 248251881Speter path_array = apr_array_make(sb->cl_pool, 1, 249251881Speter sizeof(struct status_cache *)); 250251881Speter svn_hash_sets(sb->cached_changelists, cl_key, path_array); 251251881Speter } 252251881Speter 253251881Speter APR_ARRAY_PUSH(path_array, struct status_cache *) = scache; 254251881Speter return SVN_NO_ERROR; 255251881Speter } 256251881Speter 257251881Speter return print_status_normal_or_xml(baton, path, status, pool); 258251881Speter} 259251881Speter 260251881Speter/* This implements the `svn_opt_subcommand_t' interface. */ 261251881Spetersvn_error_t * 262251881Spetersvn_cl__status(apr_getopt_t *os, 263251881Speter void *baton, 264251881Speter apr_pool_t *scratch_pool) 265251881Speter{ 266251881Speter svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 267251881Speter svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 268251881Speter apr_array_header_t *targets; 269251881Speter apr_pool_t *iterpool; 270251881Speter apr_hash_t *master_cl_hash = apr_hash_make(scratch_pool); 271251881Speter int i; 272251881Speter svn_opt_revision_t rev; 273251881Speter struct status_baton sb; 274251881Speter 275251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 276251881Speter opt_state->targets, 277251881Speter ctx, FALSE, 278251881Speter scratch_pool)); 279251881Speter 280251881Speter /* Add "." if user passed 0 arguments */ 281251881Speter svn_opt_push_implicit_dot_target(targets, scratch_pool); 282251881Speter 283251881Speter SVN_ERR(svn_cl__check_targets_are_local_paths(targets)); 284251881Speter 285251881Speter /* We want our -u statuses to be against HEAD. */ 286251881Speter rev.kind = svn_opt_revision_head; 287251881Speter 288251881Speter sb.had_print_error = FALSE; 289251881Speter 290251881Speter if (opt_state->xml) 291251881Speter { 292251881Speter /* If output is not incremental, output the XML header and wrap 293251881Speter everything in a top-level element. This makes the output in 294251881Speter its entirety a well-formed XML document. */ 295251881Speter if (! opt_state->incremental) 296251881Speter SVN_ERR(svn_cl__xml_print_header("status", scratch_pool)); 297251881Speter } 298251881Speter else 299251881Speter { 300251881Speter if (opt_state->incremental) 301251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 302251881Speter _("'incremental' option only valid in XML " 303251881Speter "mode")); 304251881Speter } 305251881Speter 306251881Speter SVN_ERR(svn_dirent_get_absolute(&(sb.cwd_abspath), "", scratch_pool)); 307251881Speter sb.suppress_externals_placeholders = (opt_state->quiet 308251881Speter && (! opt_state->verbose)); 309251881Speter sb.detailed = (opt_state->verbose || opt_state->update); 310251881Speter sb.show_last_committed = opt_state->verbose; 311251881Speter sb.skip_unrecognized = opt_state->quiet; 312251881Speter sb.repos_locks = opt_state->update; 313251881Speter sb.xml_mode = opt_state->xml; 314251881Speter sb.cached_changelists = master_cl_hash; 315251881Speter sb.cl_pool = scratch_pool; 316251881Speter sb.text_conflicts = 0; 317251881Speter sb.prop_conflicts = 0; 318251881Speter sb.tree_conflicts = 0; 319251881Speter sb.ctx = ctx; 320251881Speter 321251881Speter SVN_ERR(svn_cl__eat_peg_revisions(&targets, targets, scratch_pool)); 322251881Speter 323251881Speter iterpool = svn_pool_create(scratch_pool); 324251881Speter for (i = 0; i < targets->nelts; i++) 325251881Speter { 326251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 327251881Speter svn_revnum_t repos_rev = SVN_INVALID_REVNUM; 328251881Speter 329251881Speter svn_pool_clear(iterpool); 330251881Speter 331251881Speter SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 332251881Speter 333251881Speter if (opt_state->xml) 334251881Speter SVN_ERR(print_start_target_xml(svn_dirent_local_style(target, iterpool), 335251881Speter iterpool)); 336251881Speter 337251881Speter /* Retrieve a hash of status structures with the information 338251881Speter requested by the user. */ 339251881Speter SVN_ERR(svn_cl__try(svn_client_status5(&repos_rev, ctx, target, &rev, 340251881Speter opt_state->depth, 341251881Speter opt_state->verbose, 342251881Speter opt_state->update, 343251881Speter opt_state->no_ignore, 344251881Speter opt_state->ignore_externals, 345251881Speter FALSE /* depth_as_sticky */, 346251881Speter opt_state->changelists, 347251881Speter print_status, &sb, 348251881Speter iterpool), 349251881Speter NULL, opt_state->quiet, 350251881Speter /* not versioned: */ 351251881Speter SVN_ERR_WC_NOT_WORKING_COPY, 352251881Speter SVN_ERR_WC_PATH_NOT_FOUND)); 353251881Speter 354251881Speter if (opt_state->xml) 355251881Speter SVN_ERR(print_finish_target_xml(repos_rev, iterpool)); 356251881Speter } 357251881Speter 358251881Speter /* If any paths were cached because they were associatied with 359251881Speter changelists, we can now display them as grouped changelists. */ 360251881Speter if (apr_hash_count(master_cl_hash) > 0) 361251881Speter { 362251881Speter apr_hash_index_t *hi; 363251881Speter svn_stringbuf_t *buf; 364251881Speter 365251881Speter if (opt_state->xml) 366251881Speter buf = svn_stringbuf_create_empty(scratch_pool); 367251881Speter 368251881Speter for (hi = apr_hash_first(scratch_pool, master_cl_hash); hi; 369251881Speter hi = apr_hash_next(hi)) 370251881Speter { 371251881Speter const char *changelist_name = svn__apr_hash_index_key(hi); 372251881Speter apr_array_header_t *path_array = svn__apr_hash_index_val(hi); 373251881Speter int j; 374251881Speter 375251881Speter /* ### TODO: For non-XML output, we shouldn't print the 376251881Speter ### leading \n on the first changelist if there were no 377251881Speter ### non-changelist entries. */ 378251881Speter if (opt_state->xml) 379251881Speter { 380251881Speter svn_stringbuf_setempty(buf); 381251881Speter svn_xml_make_open_tag(&buf, scratch_pool, svn_xml_normal, 382251881Speter "changelist", "name", changelist_name, 383251881Speter NULL); 384251881Speter SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); 385251881Speter } 386251881Speter else 387251881Speter SVN_ERR(svn_cmdline_printf(scratch_pool, 388251881Speter _("\n--- Changelist '%s':\n"), 389251881Speter changelist_name)); 390251881Speter 391251881Speter for (j = 0; j < path_array->nelts; j++) 392251881Speter { 393251881Speter struct status_cache *scache = 394251881Speter APR_ARRAY_IDX(path_array, j, struct status_cache *); 395251881Speter SVN_ERR(print_status_normal_or_xml(&sb, scache->path, 396251881Speter scache->status, scratch_pool)); 397251881Speter } 398251881Speter 399251881Speter if (opt_state->xml) 400251881Speter { 401251881Speter svn_stringbuf_setempty(buf); 402251881Speter svn_xml_make_close_tag(&buf, scratch_pool, "changelist"); 403251881Speter SVN_ERR(svn_cl__error_checked_fputs(buf->data, stdout)); 404251881Speter } 405251881Speter } 406251881Speter } 407251881Speter svn_pool_destroy(iterpool); 408251881Speter 409251881Speter if (opt_state->xml && (! opt_state->incremental)) 410251881Speter SVN_ERR(svn_cl__xml_print_footer("status", scratch_pool)); 411251881Speter 412251881Speter if (! opt_state->quiet && ! opt_state->xml) 413251881Speter SVN_ERR(print_conflict_stats(&sb, scratch_pool)); 414251881Speter 415251881Speter return SVN_NO_ERROR; 416251881Speter} 417