1251881Speter/* 2251881Speter * list-cmd.c -- list a URL 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#include "svn_cmdline.h" 25251881Speter#include "svn_client.h" 26251881Speter#include "svn_error.h" 27251881Speter#include "svn_pools.h" 28251881Speter#include "svn_time.h" 29251881Speter#include "svn_xml.h" 30251881Speter#include "svn_dirent_uri.h" 31251881Speter#include "svn_path.h" 32251881Speter#include "svn_utf.h" 33251881Speter#include "svn_opt.h" 34251881Speter 35251881Speter#include "cl.h" 36251881Speter 37251881Speter#include "svn_private_config.h" 38251881Speter 39251881Speter 40251881Speter 41251881Speter/* Baton used when printing directory entries. */ 42251881Speterstruct print_baton { 43251881Speter svn_boolean_t verbose; 44251881Speter svn_client_ctx_t *ctx; 45251881Speter 46251881Speter /* To keep track of last seen external information. */ 47251881Speter const char *last_external_parent_url; 48251881Speter const char *last_external_target; 49251881Speter svn_boolean_t in_external; 50251881Speter}; 51251881Speter 52289166Speter/* Field flags required for this function */ 53289166Speterstatic const apr_uint32_t print_dirent_fields = SVN_DIRENT_KIND; 54289166Speterstatic const apr_uint32_t print_dirent_fields_verbose = ( 55289166Speter SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME | 56289166Speter SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR); 57289166Speter 58251881Speter/* This implements the svn_client_list_func2_t API, printing a single 59251881Speter directory entry in text format. */ 60251881Speterstatic svn_error_t * 61251881Speterprint_dirent(void *baton, 62251881Speter const char *path, 63251881Speter const svn_dirent_t *dirent, 64251881Speter const svn_lock_t *lock, 65251881Speter const char *abs_path, 66251881Speter const char *external_parent_url, 67251881Speter const char *external_target, 68251881Speter apr_pool_t *scratch_pool) 69251881Speter{ 70251881Speter struct print_baton *pb = baton; 71251881Speter const char *entryname; 72251881Speter static const char *time_format_long = NULL; 73251881Speter static const char *time_format_short = NULL; 74251881Speter 75251881Speter SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || 76251881Speter (external_parent_url && external_target)); 77251881Speter 78251881Speter if (time_format_long == NULL) 79251881Speter time_format_long = _("%b %d %H:%M"); 80251881Speter if (time_format_short == NULL) 81251881Speter time_format_short = _("%b %d %Y"); 82251881Speter 83251881Speter if (pb->ctx->cancel_func) 84251881Speter SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); 85251881Speter 86251881Speter if (strcmp(path, "") == 0) 87251881Speter { 88251881Speter if (dirent->kind == svn_node_file) 89251881Speter entryname = svn_dirent_basename(abs_path, scratch_pool); 90251881Speter else if (pb->verbose) 91251881Speter entryname = "."; 92251881Speter else 93251881Speter /* Don't bother to list if no useful information will be shown. */ 94251881Speter return SVN_NO_ERROR; 95251881Speter } 96251881Speter else 97251881Speter entryname = path; 98251881Speter 99251881Speter if (external_parent_url && external_target) 100251881Speter { 101251881Speter if ((pb->last_external_parent_url == NULL 102251881Speter && pb->last_external_target == NULL) 103251881Speter || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 104251881Speter || strcmp(pb->last_external_target, external_target) != 0)) 105251881Speter { 106251881Speter SVN_ERR(svn_cmdline_printf(scratch_pool, 107251881Speter _("Listing external '%s'" 108251881Speter " defined on '%s':\n"), 109251881Speter external_target, 110251881Speter external_parent_url)); 111251881Speter 112251881Speter pb->last_external_parent_url = external_parent_url; 113251881Speter pb->last_external_target = external_target; 114251881Speter } 115251881Speter } 116251881Speter 117251881Speter if (pb->verbose) 118251881Speter { 119251881Speter apr_time_t now = apr_time_now(); 120251881Speter apr_time_exp_t exp_time; 121251881Speter apr_status_t apr_err; 122251881Speter apr_size_t size; 123251881Speter char timestr[20]; 124251881Speter const char *sizestr, *utf8_timestr; 125251881Speter 126251881Speter /* svn_time_to_human_cstring gives us something *way* too long 127251881Speter to use for this, so we have to roll our own. We include 128251881Speter the year if the entry's time is not within half a year. */ 129251881Speter apr_time_exp_lt(&exp_time, dirent->time); 130251881Speter if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2) 131251881Speter && apr_time_sec(dirent->time - now) < (365 * 86400 / 2)) 132251881Speter { 133251881Speter apr_err = apr_strftime(timestr, &size, sizeof(timestr), 134251881Speter time_format_long, &exp_time); 135251881Speter } 136251881Speter else 137251881Speter { 138251881Speter apr_err = apr_strftime(timestr, &size, sizeof(timestr), 139251881Speter time_format_short, &exp_time); 140251881Speter } 141251881Speter 142251881Speter /* if that failed, just zero out the string and print nothing */ 143251881Speter if (apr_err) 144251881Speter timestr[0] = '\0'; 145251881Speter 146251881Speter /* we need it in UTF-8. */ 147251881Speter SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool)); 148251881Speter 149251881Speter sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, 150251881Speter dirent->size); 151251881Speter 152251881Speter return svn_cmdline_printf 153251881Speter (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n", 154251881Speter dirent->created_rev, 155251881Speter dirent->last_author ? dirent->last_author : " ? ", 156251881Speter lock ? 'O' : ' ', 157251881Speter (dirent->kind == svn_node_file) ? sizestr : "", 158251881Speter utf8_timestr, 159251881Speter entryname, 160251881Speter (dirent->kind == svn_node_dir) ? "/" : ""); 161251881Speter } 162251881Speter else 163251881Speter { 164251881Speter return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname, 165251881Speter (dirent->kind == svn_node_dir) 166251881Speter ? "/" : ""); 167251881Speter } 168251881Speter} 169251881Speter 170289166Speter/* Field flags required for this function */ 171289166Speterstatic const apr_uint32_t print_dirent_xml_fields = ( 172289166Speter SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME | 173289166Speter SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR); 174251881Speter/* This implements the svn_client_list_func2_t API, printing a single dirent 175251881Speter in XML format. */ 176251881Speterstatic svn_error_t * 177251881Speterprint_dirent_xml(void *baton, 178251881Speter const char *path, 179251881Speter const svn_dirent_t *dirent, 180251881Speter const svn_lock_t *lock, 181251881Speter const char *abs_path, 182251881Speter const char *external_parent_url, 183251881Speter const char *external_target, 184251881Speter apr_pool_t *scratch_pool) 185251881Speter{ 186251881Speter struct print_baton *pb = baton; 187251881Speter const char *entryname; 188251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool); 189251881Speter 190251881Speter SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || 191251881Speter (external_parent_url && external_target)); 192251881Speter 193251881Speter if (strcmp(path, "") == 0) 194251881Speter { 195251881Speter if (dirent->kind == svn_node_file) 196251881Speter entryname = svn_dirent_basename(abs_path, scratch_pool); 197251881Speter else 198251881Speter /* Don't bother to list if no useful information will be shown. */ 199251881Speter return SVN_NO_ERROR; 200251881Speter } 201251881Speter else 202251881Speter entryname = path; 203251881Speter 204251881Speter if (pb->ctx->cancel_func) 205251881Speter SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); 206251881Speter 207251881Speter if (external_parent_url && external_target) 208251881Speter { 209251881Speter if ((pb->last_external_parent_url == NULL 210251881Speter && pb->last_external_target == NULL) 211251881Speter || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 212251881Speter || strcmp(pb->last_external_target, external_target) != 0)) 213251881Speter { 214251881Speter if (pb->in_external) 215251881Speter { 216251881Speter /* The external item being listed is different from the previous 217251881Speter one, so close the tag. */ 218251881Speter svn_xml_make_close_tag(&sb, scratch_pool, "external"); 219251881Speter pb->in_external = FALSE; 220251881Speter } 221251881Speter 222251881Speter svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external", 223251881Speter "parent_url", external_parent_url, 224251881Speter "target", external_target, 225299742Sdim SVN_VA_NULL); 226251881Speter 227251881Speter pb->last_external_parent_url = external_parent_url; 228251881Speter pb->last_external_target = external_target; 229251881Speter pb->in_external = TRUE; 230251881Speter } 231251881Speter } 232251881Speter 233251881Speter svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry", 234251881Speter "kind", svn_cl__node_kind_str_xml(dirent->kind), 235299742Sdim SVN_VA_NULL); 236251881Speter 237251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname); 238251881Speter 239251881Speter if (dirent->kind == svn_node_file) 240251881Speter { 241251881Speter svn_cl__xml_tagged_cdata 242251881Speter (&sb, scratch_pool, "size", 243251881Speter apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size)); 244251881Speter } 245251881Speter 246251881Speter svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit", 247251881Speter "revision", 248251881Speter apr_psprintf(scratch_pool, "%ld", dirent->created_rev), 249299742Sdim SVN_VA_NULL); 250251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author); 251251881Speter if (dirent->time) 252251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date", 253251881Speter svn_time_to_cstring(dirent->time, scratch_pool)); 254251881Speter svn_xml_make_close_tag(&sb, scratch_pool, "commit"); 255251881Speter 256251881Speter if (lock) 257251881Speter { 258299742Sdim svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", 259299742Sdim SVN_VA_NULL); 260251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token); 261251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner); 262251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment); 263251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created", 264251881Speter svn_time_to_cstring(lock->creation_date, 265251881Speter scratch_pool)); 266251881Speter if (lock->expiration_date != 0) 267251881Speter svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires", 268251881Speter svn_time_to_cstring 269251881Speter (lock->expiration_date, scratch_pool)); 270251881Speter svn_xml_make_close_tag(&sb, scratch_pool, "lock"); 271251881Speter } 272251881Speter 273251881Speter svn_xml_make_close_tag(&sb, scratch_pool, "entry"); 274251881Speter 275251881Speter return svn_cl__error_checked_fputs(sb->data, stdout); 276251881Speter} 277251881Speter 278251881Speter 279251881Speter/* This implements the `svn_opt_subcommand_t' interface. */ 280251881Spetersvn_error_t * 281251881Spetersvn_cl__list(apr_getopt_t *os, 282251881Speter void *baton, 283251881Speter apr_pool_t *pool) 284251881Speter{ 285251881Speter svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 286251881Speter svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 287251881Speter apr_array_header_t *targets; 288251881Speter int i; 289251881Speter apr_pool_t *subpool = svn_pool_create(pool); 290251881Speter apr_uint32_t dirent_fields; 291251881Speter struct print_baton pb; 292251881Speter svn_boolean_t seen_nonexistent_target = FALSE; 293251881Speter svn_error_t *err; 294251881Speter svn_error_t *externals_err = SVN_NO_ERROR; 295251881Speter struct svn_cl__check_externals_failed_notify_baton nwb; 296251881Speter 297251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 298251881Speter opt_state->targets, 299251881Speter ctx, FALSE, pool)); 300251881Speter 301251881Speter /* Add "." if user passed 0 arguments */ 302251881Speter svn_opt_push_implicit_dot_target(targets, pool); 303251881Speter 304251881Speter if (opt_state->xml) 305251881Speter { 306251881Speter /* The XML output contains all the information, so "--verbose" 307251881Speter does not apply. */ 308251881Speter if (opt_state->verbose) 309251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 310251881Speter _("'verbose' option invalid in XML mode")); 311251881Speter 312251881Speter /* If output is not incremental, output the XML header and wrap 313251881Speter everything in a top-level element. This makes the output in 314251881Speter its entirety a well-formed XML document. */ 315251881Speter if (! opt_state->incremental) 316251881Speter SVN_ERR(svn_cl__xml_print_header("lists", pool)); 317251881Speter } 318251881Speter else 319251881Speter { 320251881Speter if (opt_state->incremental) 321251881Speter return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 322251881Speter _("'incremental' option only valid in XML " 323251881Speter "mode")); 324251881Speter } 325251881Speter 326289166Speter if (opt_state->xml) 327289166Speter dirent_fields = print_dirent_xml_fields; 328289166Speter else if (opt_state->verbose) 329289166Speter dirent_fields = print_dirent_fields_verbose; 330251881Speter else 331289166Speter dirent_fields = print_dirent_fields; 332251881Speter 333251881Speter pb.ctx = ctx; 334251881Speter pb.verbose = opt_state->verbose; 335251881Speter 336251881Speter if (opt_state->depth == svn_depth_unknown) 337251881Speter opt_state->depth = svn_depth_immediates; 338251881Speter 339251881Speter if (opt_state->include_externals) 340251881Speter { 341251881Speter nwb.wrapped_func = ctx->notify_func2; 342251881Speter nwb.wrapped_baton = ctx->notify_baton2; 343251881Speter nwb.had_externals_error = FALSE; 344251881Speter ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; 345251881Speter ctx->notify_baton2 = &nwb; 346251881Speter } 347251881Speter 348251881Speter /* For each target, try to list it. */ 349251881Speter for (i = 0; i < targets->nelts; i++) 350251881Speter { 351251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 352251881Speter const char *truepath; 353251881Speter svn_opt_revision_t peg_revision; 354251881Speter 355251881Speter /* Initialize the following variables for 356251881Speter every list target. */ 357251881Speter pb.last_external_parent_url = NULL; 358251881Speter pb.last_external_target = NULL; 359251881Speter pb.in_external = FALSE; 360251881Speter 361251881Speter svn_pool_clear(subpool); 362251881Speter 363251881Speter SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 364251881Speter 365251881Speter /* Get peg revisions. */ 366251881Speter SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, 367251881Speter subpool)); 368251881Speter 369251881Speter if (opt_state->xml) 370251881Speter { 371251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 372251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list", 373251881Speter "path", truepath[0] == '\0' ? "." : truepath, 374299742Sdim SVN_VA_NULL); 375251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 376251881Speter } 377251881Speter 378251881Speter err = svn_client_list3(truepath, &peg_revision, 379251881Speter &(opt_state->start_revision), 380251881Speter opt_state->depth, 381251881Speter dirent_fields, 382251881Speter (opt_state->xml || opt_state->verbose), 383251881Speter opt_state->include_externals, 384251881Speter opt_state->xml ? print_dirent_xml : print_dirent, 385251881Speter &pb, ctx, subpool); 386251881Speter 387251881Speter if (err) 388251881Speter { 389251881Speter /* If one of the targets is a non-existent URL or wc-entry, 390251881Speter don't bail out. Just warn and move on to the next target. */ 391251881Speter if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || 392251881Speter err->apr_err == SVN_ERR_FS_NOT_FOUND) 393251881Speter svn_handle_warning2(stderr, err, "svn: "); 394251881Speter else 395251881Speter return svn_error_trace(err); 396251881Speter 397251881Speter svn_error_clear(err); 398251881Speter err = NULL; 399251881Speter seen_nonexistent_target = TRUE; 400251881Speter } 401251881Speter 402251881Speter if (opt_state->xml) 403251881Speter { 404251881Speter svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 405251881Speter 406251881Speter if (pb.in_external) 407251881Speter { 408251881Speter /* close the final external item's tag */ 409251881Speter svn_xml_make_close_tag(&sb, pool, "external"); 410251881Speter pb.in_external = FALSE; 411251881Speter } 412251881Speter 413251881Speter svn_xml_make_close_tag(&sb, pool, "list"); 414251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 415251881Speter } 416251881Speter } 417251881Speter 418251881Speter svn_pool_destroy(subpool); 419251881Speter 420251881Speter if (opt_state->include_externals && nwb.had_externals_error) 421251881Speter { 422251881Speter externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, 423251881Speter NULL, 424251881Speter _("Failure occurred processing one or " 425251881Speter "more externals definitions")); 426251881Speter } 427251881Speter 428251881Speter if (opt_state->xml && ! opt_state->incremental) 429251881Speter SVN_ERR(svn_cl__xml_print_footer("lists", pool)); 430251881Speter 431251881Speter if (seen_nonexistent_target) 432251881Speter err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 433251881Speter _("Could not list all targets because some targets don't exist")); 434299742Sdim else 435299742Sdim err = NULL; 436251881Speter 437251881Speter return svn_error_compose_create(externals_err, err); 438251881Speter} 439