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