2 * list-cmd.c -- list a URL
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 #include "svn_cmdline.h"
25 #include "svn_client.h"
26 #include "svn_error.h"
27 #include "svn_pools.h"
30 #include "svn_dirent_uri.h"
37 #include "svn_private_config.h"
41 /* Baton used when printing directory entries. */
43 svn_client_ctx_t *ctx;
44 svn_boolean_t verbose;
45 svn_cl__size_unit_t file_size_unit;
47 /* Keep track of the width of the author field. */
51 /* To keep track of last seen external information. */
52 const char *last_external_parent_url;
53 const char *last_external_target;
54 svn_boolean_t in_external;
57 /* Starting and maximum width of the author field */
58 static const int initial_author_width = 8;
59 static const int initial_human_readable_author_width = 14;
60 static const int maximum_author_width = 16;
61 static const int maximum_human_readable_author_width = 22;
63 /* Width of the size field */
64 static const int normal_size_width = 10;
65 static const int human_readable_size_width = 4;
67 /* Field flags required for this function */
68 static const apr_uint32_t print_dirent_fields = SVN_DIRENT_KIND;
69 static const apr_uint32_t print_dirent_fields_verbose = (
70 SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME |
71 SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR);
74 /* This implements the svn_client_list_func2_t API, printing a single
75 directory entry in text format. */
77 print_dirent(void *baton,
79 const svn_dirent_t *dirent,
80 const svn_lock_t *lock,
82 const char *external_parent_url,
83 const char *external_target,
84 apr_pool_t *scratch_pool)
86 struct print_baton *pb = baton;
87 const char *entryname;
88 static const char *time_format_long = NULL;
89 static const char *time_format_short = NULL;
91 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
92 (external_parent_url && external_target));
94 if (time_format_long == NULL)
95 time_format_long = _("%b %d %H:%M");
96 if (time_format_short == NULL)
97 time_format_short = _("%b %d %Y");
99 if (pb->ctx->cancel_func)
100 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
102 if (strcmp(path, "") == 0)
104 if (dirent->kind == svn_node_file)
105 entryname = svn_dirent_basename(abs_path, scratch_pool);
106 else if (pb->verbose)
109 /* Don't bother to list if no useful information will be shown. */
115 if (external_parent_url && external_target)
117 if ((pb->last_external_parent_url == NULL
118 && pb->last_external_target == NULL)
119 || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
120 || strcmp(pb->last_external_target, external_target) != 0))
122 SVN_ERR(svn_cmdline_printf(scratch_pool,
123 _("Listing external '%s'"
124 " defined on '%s':\n"),
126 external_parent_url));
128 pb->last_external_parent_url = external_parent_url;
129 pb->last_external_target = external_target;
135 apr_time_t now = apr_time_now();
136 apr_time_exp_t exp_time;
137 apr_status_t apr_err;
140 const int sizewidth = (pb->file_size_unit == SVN_CL__SIZE_UNIT_NONE
142 : human_readable_size_width);
143 const char *sizestr = "";
144 const char *utf8_timestr;
146 /* svn_time_to_human_cstring gives us something *way* too long
147 to use for this, so we have to roll our own. We include
148 the year if the entry's time is not within half a year. */
149 apr_time_exp_lt(&exp_time, dirent->time);
150 if (apr_time_sec(now - dirent->time) < (365 * 86400 / 2)
151 && apr_time_sec(dirent->time - now) < (365 * 86400 / 2))
153 apr_err = apr_strftime(timestr, &size, sizeof(timestr),
154 time_format_long, &exp_time);
158 apr_err = apr_strftime(timestr, &size, sizeof(timestr),
159 time_format_short, &exp_time);
162 /* if that failed, just zero out the string and print nothing */
166 /* we need it in UTF-8. */
167 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
169 /* We may have to adjust the width of th 'author' field. */
170 if (dirent->last_author)
172 const int author_width = (int)strlen(dirent->last_author);
173 if (author_width > pb->author_width)
175 if (author_width < pb->max_author_width)
176 pb->author_width = author_width;
178 pb->author_width = pb->max_author_width;
182 if (dirent->kind == svn_node_file)
184 SVN_ERR(svn_cl__format_file_size(&sizestr, dirent->size,
186 FALSE, scratch_pool));
189 return svn_cmdline_printf
190 (scratch_pool, "%7ld %-*.*s %c %*s %12s %s%s\n",
192 pb->author_width, pb->author_width,
193 dirent->last_author ? dirent->last_author : " ? ",
198 (dirent->kind == svn_node_dir) ? "/" : "");
202 return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
203 (dirent->kind == svn_node_dir)
208 /* Field flags required for this function */
209 static const apr_uint32_t print_dirent_xml_fields = (
210 SVN_DIRENT_KIND | SVN_DIRENT_SIZE | SVN_DIRENT_TIME |
211 SVN_DIRENT_CREATED_REV | SVN_DIRENT_LAST_AUTHOR);
212 /* This implements the svn_client_list_func2_t API, printing a single dirent
215 print_dirent_xml(void *baton,
217 const svn_dirent_t *dirent,
218 const svn_lock_t *lock,
219 const char *abs_path,
220 const char *external_parent_url,
221 const char *external_target,
222 apr_pool_t *scratch_pool)
224 struct print_baton *pb = baton;
225 const char *entryname;
226 svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
228 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
229 (external_parent_url && external_target));
231 if (strcmp(path, "") == 0)
233 if (dirent->kind == svn_node_file)
234 entryname = svn_dirent_basename(abs_path, scratch_pool);
236 /* Don't bother to list if no useful information will be shown. */
242 if (pb->ctx->cancel_func)
243 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
245 if (external_parent_url && external_target)
247 if ((pb->last_external_parent_url == NULL
248 && pb->last_external_target == NULL)
249 || (strcmp(pb->last_external_parent_url, external_parent_url) != 0
250 || strcmp(pb->last_external_target, external_target) != 0))
254 /* The external item being listed is different from the previous
255 one, so close the tag. */
256 svn_xml_make_close_tag(&sb, scratch_pool, "external");
257 pb->in_external = FALSE;
260 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
261 "parent_url", external_parent_url,
262 "target", external_target,
265 pb->last_external_parent_url = external_parent_url;
266 pb->last_external_target = external_target;
267 pb->in_external = TRUE;
271 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
272 "kind", svn_cl__node_kind_str_xml(dirent->kind),
275 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
277 if (dirent->kind == svn_node_file)
280 SVN_ERR(svn_cl__format_file_size(&sizestr, dirent->size,
281 SVN_CL__SIZE_UNIT_XML,
282 FALSE, scratch_pool));
283 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "size", sizestr);
286 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
288 apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
290 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
292 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "date",
293 svn_time_to_cstring(dirent->time, scratch_pool));
294 svn_xml_make_close_tag(&sb, scratch_pool, "commit");
298 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "lock",
300 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
301 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
302 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
303 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
304 svn_time_to_cstring(lock->creation_date,
306 if (lock->expiration_date != 0)
307 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
309 (lock->expiration_date, scratch_pool));
310 svn_xml_make_close_tag(&sb, scratch_pool, "lock");
313 svn_xml_make_close_tag(&sb, scratch_pool, "entry");
315 return svn_cl__error_checked_fputs(sb->data, stdout);
319 /* This implements the `svn_opt_subcommand_t' interface. */
321 svn_cl__list(apr_getopt_t *os,
325 svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
326 svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
327 apr_array_header_t *targets;
329 apr_pool_t *subpool = svn_pool_create(pool);
330 apr_uint32_t dirent_fields;
331 struct print_baton pb;
332 svn_boolean_t seen_nonexistent_target = FALSE;
334 svn_error_t *externals_err = SVN_NO_ERROR;
335 struct svn_cl__check_externals_failed_notify_baton nwb;
337 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
341 /* Add "." if user passed 0 arguments */
342 svn_opt_push_implicit_dot_target(targets, pool);
346 /* The XML output contains all the information, so "--verbose" does
347 not apply, and using "--human-readable" with machine-readable
348 output does not make sense. */
349 if (opt_state->verbose)
350 return svn_error_create(
351 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
352 _("--verbose is not valid in --xml mode"));
353 if (opt_state->file_size_unit != SVN_CL__SIZE_UNIT_NONE)
354 return svn_error_create(
355 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
356 _("--human-readable is not valid in --xml mode"));
358 /* If output is not incremental, output the XML header and wrap
359 everything in a top-level element. This makes the output in
360 its entirety a well-formed XML document. */
361 if (! opt_state->incremental)
362 SVN_ERR(svn_cl__xml_print_header("lists", pool));
366 if (opt_state->incremental)
367 return svn_error_create(
368 SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
369 _("--incremental is only valid in --xml mode"));
373 dirent_fields = print_dirent_xml_fields;
374 else if (opt_state->verbose)
375 dirent_fields = print_dirent_fields_verbose;
377 dirent_fields = print_dirent_fields;
380 pb.verbose = opt_state->verbose;
381 pb.file_size_unit = opt_state->file_size_unit;
382 if (pb.file_size_unit == SVN_CL__SIZE_UNIT_NONE)
384 pb.author_width = initial_author_width;
385 pb.max_author_width = maximum_author_width;
389 pb.author_width = initial_human_readable_author_width;
390 pb.max_author_width = maximum_human_readable_author_width;
393 if (opt_state->depth == svn_depth_unknown)
394 opt_state->depth = svn_depth_immediates;
396 if (opt_state->include_externals)
398 nwb.wrapped_func = ctx->notify_func2;
399 nwb.wrapped_baton = ctx->notify_baton2;
400 nwb.had_externals_error = FALSE;
401 ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
402 ctx->notify_baton2 = &nwb;
405 /* For each target, try to list it. */
406 for (i = 0; i < targets->nelts; i++)
408 const char *target = APR_ARRAY_IDX(targets, i, const char *);
409 const char *truepath;
410 svn_opt_revision_t peg_revision;
411 apr_array_header_t *patterns = NULL;
414 /* Initialize the following variables for
415 every list target. */
416 pb.last_external_parent_url = NULL;
417 pb.last_external_target = NULL;
418 pb.in_external = FALSE;
420 svn_pool_clear(subpool);
422 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
424 /* Get peg revisions. */
425 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
430 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
431 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
432 "path", truepath[0] == '\0' ? "." : truepath,
434 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
437 if (opt_state->search_patterns)
439 patterns = apr_array_make(subpool, 4, sizeof(const char *));
440 for (k = 0; k < opt_state->search_patterns->nelts; ++k)
442 apr_array_header_t *pattern_group
443 = APR_ARRAY_IDX(opt_state->search_patterns, k,
444 apr_array_header_t *);
447 /* Should never fail but ... */
448 if (pattern_group->nelts != 1)
449 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
450 _("'search-and' option is not supported"));
452 pattern = APR_ARRAY_IDX(pattern_group, 0, const char *);
454 /* As we currently can't pass glob patterns via the Windows
455 CLI, fall back to sub-string search. */
456 pattern = apr_psprintf(subpool, "*%s*", pattern);
458 APR_ARRAY_PUSH(patterns, const char *) = pattern;
462 err = svn_client_list4(truepath, &peg_revision,
463 &(opt_state->start_revision), patterns,
466 (opt_state->xml || opt_state->verbose),
467 opt_state->include_externals,
468 opt_state->xml ? print_dirent_xml : print_dirent,
473 /* If one of the targets is a non-existent URL or wc-entry,
474 don't bail out. Just warn and move on to the next target. */
475 if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
476 err->apr_err == SVN_ERR_FS_NOT_FOUND)
477 svn_handle_warning2(stderr, err, "svn: ");
479 return svn_error_trace(err);
481 svn_error_clear(err);
483 seen_nonexistent_target = TRUE;
488 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
492 /* close the final external item's tag */
493 svn_xml_make_close_tag(&sb, pool, "external");
494 pb.in_external = FALSE;
497 svn_xml_make_close_tag(&sb, pool, "list");
498 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
502 svn_pool_destroy(subpool);
504 if (opt_state->include_externals && nwb.had_externals_error)
506 externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
508 _("Failure occurred processing one or "
509 "more externals definitions"));
512 if (opt_state->xml && ! opt_state->incremental)
513 SVN_ERR(svn_cl__xml_print_footer("lists", pool));
515 if (seen_nonexistent_target)
516 err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
517 _("Could not list all targets because some targets don't exist"));
521 return svn_error_compose_create(externals_err, err);