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