list-cmd.c revision 289166
1/* 2 * list-cmd.c -- list a URL 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#include "svn_cmdline.h" 25#include "svn_client.h" 26#include "svn_error.h" 27#include "svn_pools.h" 28#include "svn_time.h" 29#include "svn_xml.h" 30#include "svn_dirent_uri.h" 31#include "svn_path.h" 32#include "svn_utf.h" 33#include "svn_opt.h" 34 35#include "cl.h" 36 37#include "svn_private_config.h" 38 39 40 41/* Baton used when printing directory entries. */ 42struct print_baton { 43 svn_boolean_t verbose; 44 svn_client_ctx_t *ctx; 45 46 /* To keep track of last seen external information. */ 47 const char *last_external_parent_url; 48 const char *last_external_target; 49 svn_boolean_t in_external; 50}; 51 52/* Field flags required for this function */ 53static const apr_uint32_t print_dirent_fields = SVN_DIRENT_KIND; 54static const apr_uint32_t print_dirent_fields_verbose = ( 55 SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME | 56 SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR); 57 58/* This implements the svn_client_list_func2_t API, printing a single 59 directory entry in text format. */ 60static svn_error_t * 61print_dirent(void *baton, 62 const char *path, 63 const svn_dirent_t *dirent, 64 const svn_lock_t *lock, 65 const char *abs_path, 66 const char *external_parent_url, 67 const char *external_target, 68 apr_pool_t *scratch_pool) 69{ 70 struct print_baton *pb = baton; 71 const char *entryname; 72 static const char *time_format_long = NULL; 73 static const char *time_format_short = NULL; 74 75 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || 76 (external_parent_url && external_target)); 77 78 if (time_format_long == NULL) 79 time_format_long = _("%b %d %H:%M"); 80 if (time_format_short == NULL) 81 time_format_short = _("%b %d %Y"); 82 83 if (pb->ctx->cancel_func) 84 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); 85 86 if (strcmp(path, "") == 0) 87 { 88 if (dirent->kind == svn_node_file) 89 entryname = svn_dirent_basename(abs_path, scratch_pool); 90 else if (pb->verbose) 91 entryname = "."; 92 else 93 /* Don't bother to list if no useful information will be shown. */ 94 return SVN_NO_ERROR; 95 } 96 else 97 entryname = path; 98 99 if (external_parent_url && external_target) 100 { 101 if ((pb->last_external_parent_url == NULL 102 && pb->last_external_target == NULL) 103 || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 104 || strcmp(pb->last_external_target, external_target) != 0)) 105 { 106 SVN_ERR(svn_cmdline_printf(scratch_pool, 107 _("Listing external '%s'" 108 " defined on '%s':\n"), 109 external_target, 110 external_parent_url)); 111 112 pb->last_external_parent_url = external_parent_url; 113 pb->last_external_target = external_target; 114 } 115 } 116 117 if (pb->verbose) 118 { 119 apr_time_t now = apr_time_now(); 120 apr_time_exp_t exp_time; 121 apr_status_t apr_err; 122 apr_size_t size; 123 char timestr[20]; 124 const char *sizestr, *utf8_timestr; 125 126 /* svn_time_to_human_cstring gives us something *way* too long 127 to use for this, so we have to roll our own. We include 128 the year if the entry's time is not within half a year. */ 129 apr_time_exp_lt(&exp_time, dirent->time); 130 if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2) 131 && apr_time_sec(dirent->time - now) < (365 * 86400 / 2)) 132 { 133 apr_err = apr_strftime(timestr, &size, sizeof(timestr), 134 time_format_long, &exp_time); 135 } 136 else 137 { 138 apr_err = apr_strftime(timestr, &size, sizeof(timestr), 139 time_format_short, &exp_time); 140 } 141 142 /* if that failed, just zero out the string and print nothing */ 143 if (apr_err) 144 timestr[0] = '\0'; 145 146 /* we need it in UTF-8. */ 147 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool)); 148 149 sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, 150 dirent->size); 151 152 return svn_cmdline_printf 153 (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n", 154 dirent->created_rev, 155 dirent->last_author ? dirent->last_author : " ? ", 156 lock ? 'O' : ' ', 157 (dirent->kind == svn_node_file) ? sizestr : "", 158 utf8_timestr, 159 entryname, 160 (dirent->kind == svn_node_dir) ? "/" : ""); 161 } 162 else 163 { 164 return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname, 165 (dirent->kind == svn_node_dir) 166 ? "/" : ""); 167 } 168} 169 170/* Field flags required for this function */ 171static const apr_uint32_t print_dirent_xml_fields = ( 172 SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME | 173 SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR); 174/* This implements the svn_client_list_func2_t API, printing a single dirent 175 in XML format. */ 176static svn_error_t * 177print_dirent_xml(void *baton, 178 const char *path, 179 const svn_dirent_t *dirent, 180 const svn_lock_t *lock, 181 const char *abs_path, 182 const char *external_parent_url, 183 const char *external_target, 184 apr_pool_t *scratch_pool) 185{ 186 struct print_baton *pb = baton; 187 const char *entryname; 188 svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool); 189 190 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) || 191 (external_parent_url && external_target)); 192 193 if (strcmp(path, "") == 0) 194 { 195 if (dirent->kind == svn_node_file) 196 entryname = svn_dirent_basename(abs_path, scratch_pool); 197 else 198 /* Don't bother to list if no useful information will be shown. */ 199 return SVN_NO_ERROR; 200 } 201 else 202 entryname = path; 203 204 if (pb->ctx->cancel_func) 205 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton)); 206 207 if (external_parent_url && external_target) 208 { 209 if ((pb->last_external_parent_url == NULL 210 && pb->last_external_target == NULL) 211 || (strcmp(pb->last_external_parent_url, external_parent_url) != 0 212 || strcmp(pb->last_external_target, external_target) != 0)) 213 { 214 if (pb->in_external) 215 { 216 /* The external item being listed is different from the previous 217 one, so close the tag. */ 218 svn_xml_make_close_tag(&sb, scratch_pool, "external"); 219 pb->in_external = FALSE; 220 } 221 222 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external", 223 "parent_url", external_parent_url, 224 "target", external_target, 225 NULL); 226 227 pb->last_external_parent_url = external_parent_url; 228 pb->last_external_target = external_target; 229 pb->in_external = TRUE; 230 } 231 } 232 233 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry", 234 "kind", svn_cl__node_kind_str_xml(dirent->kind), 235 NULL); 236 237 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname); 238 239 if (dirent->kind == svn_node_file) 240 { 241 svn_cl__xml_tagged_cdata 242 (&sb, scratch_pool, "size", 243 apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size)); 244 } 245 246 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit", 247 "revision", 248 apr_psprintf(scratch_pool, "%ld", dirent->created_rev), 249 NULL); 250 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author); 251 if (dirent->time) 252 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date", 253 svn_time_to_cstring(dirent->time, scratch_pool)); 254 svn_xml_make_close_tag(&sb, scratch_pool, "commit"); 255 256 if (lock) 257 { 258 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock", NULL); 259 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token); 260 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner); 261 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment); 262 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created", 263 svn_time_to_cstring(lock->creation_date, 264 scratch_pool)); 265 if (lock->expiration_date != 0) 266 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires", 267 svn_time_to_cstring 268 (lock->expiration_date, scratch_pool)); 269 svn_xml_make_close_tag(&sb, scratch_pool, "lock"); 270 } 271 272 svn_xml_make_close_tag(&sb, scratch_pool, "entry"); 273 274 return svn_cl__error_checked_fputs(sb->data, stdout); 275} 276 277 278/* This implements the `svn_opt_subcommand_t' interface. */ 279svn_error_t * 280svn_cl__list(apr_getopt_t *os, 281 void *baton, 282 apr_pool_t *pool) 283{ 284 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 285 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 286 apr_array_header_t *targets; 287 int i; 288 apr_pool_t *subpool = svn_pool_create(pool); 289 apr_uint32_t dirent_fields; 290 struct print_baton pb; 291 svn_boolean_t seen_nonexistent_target = FALSE; 292 svn_error_t *err; 293 svn_error_t *externals_err = SVN_NO_ERROR; 294 struct svn_cl__check_externals_failed_notify_baton nwb; 295 296 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 297 opt_state->targets, 298 ctx, FALSE, pool)); 299 300 /* Add "." if user passed 0 arguments */ 301 svn_opt_push_implicit_dot_target(targets, pool); 302 303 if (opt_state->xml) 304 { 305 /* The XML output contains all the information, so "--verbose" 306 does not apply. */ 307 if (opt_state->verbose) 308 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 309 _("'verbose' option invalid in XML mode")); 310 311 /* If output is not incremental, output the XML header and wrap 312 everything in a top-level element. This makes the output in 313 its entirety a well-formed XML document. */ 314 if (! opt_state->incremental) 315 SVN_ERR(svn_cl__xml_print_header("lists", pool)); 316 } 317 else 318 { 319 if (opt_state->incremental) 320 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 321 _("'incremental' option only valid in XML " 322 "mode")); 323 } 324 325 if (opt_state->xml) 326 dirent_fields = print_dirent_xml_fields; 327 else if (opt_state->verbose) 328 dirent_fields = print_dirent_fields_verbose; 329 else 330 dirent_fields = print_dirent_fields; 331 332 pb.ctx = ctx; 333 pb.verbose = opt_state->verbose; 334 335 if (opt_state->depth == svn_depth_unknown) 336 opt_state->depth = svn_depth_immediates; 337 338 if (opt_state->include_externals) 339 { 340 nwb.wrapped_func = ctx->notify_func2; 341 nwb.wrapped_baton = ctx->notify_baton2; 342 nwb.had_externals_error = FALSE; 343 ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper; 344 ctx->notify_baton2 = &nwb; 345 } 346 347 /* For each target, try to list it. */ 348 for (i = 0; i < targets->nelts; i++) 349 { 350 const char *target = APR_ARRAY_IDX(targets, i, const char *); 351 const char *truepath; 352 svn_opt_revision_t peg_revision; 353 354 /* Initialize the following variables for 355 every list target. */ 356 pb.last_external_parent_url = NULL; 357 pb.last_external_target = NULL; 358 pb.in_external = FALSE; 359 360 svn_pool_clear(subpool); 361 362 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 363 364 /* Get peg revisions. */ 365 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, 366 subpool)); 367 368 if (opt_state->xml) 369 { 370 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 371 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list", 372 "path", truepath[0] == '\0' ? "." : truepath, 373 NULL); 374 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 375 } 376 377 err = svn_client_list3(truepath, &peg_revision, 378 &(opt_state->start_revision), 379 opt_state->depth, 380 dirent_fields, 381 (opt_state->xml || opt_state->verbose), 382 opt_state->include_externals, 383 opt_state->xml ? print_dirent_xml : print_dirent, 384 &pb, ctx, subpool); 385 386 if (err) 387 { 388 /* If one of the targets is a non-existent URL or wc-entry, 389 don't bail out. Just warn and move on to the next target. */ 390 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND || 391 err->apr_err == SVN_ERR_FS_NOT_FOUND) 392 svn_handle_warning2(stderr, err, "svn: "); 393 else 394 return svn_error_trace(err); 395 396 svn_error_clear(err); 397 err = NULL; 398 seen_nonexistent_target = TRUE; 399 } 400 401 if (opt_state->xml) 402 { 403 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool); 404 405 if (pb.in_external) 406 { 407 /* close the final external item's tag */ 408 svn_xml_make_close_tag(&sb, pool, "external"); 409 pb.in_external = FALSE; 410 } 411 412 svn_xml_make_close_tag(&sb, pool, "list"); 413 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 414 } 415 } 416 417 svn_pool_destroy(subpool); 418 419 if (opt_state->include_externals && nwb.had_externals_error) 420 { 421 externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS, 422 NULL, 423 _("Failure occurred processing one or " 424 "more externals definitions")); 425 } 426 427 if (opt_state->xml && ! opt_state->incremental) 428 SVN_ERR(svn_cl__xml_print_footer("lists", pool)); 429 430 if (seen_nonexistent_target) 431 err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 432 _("Could not list all targets because some targets don't exist")); 433 434 return svn_error_compose_create(externals_err, err); 435} 436