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_boolean_t verbose;
44 svn_client_ctx_t *ctx;
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;
52 /* This implements the svn_client_list_func2_t API, printing a single
53 directory entry in text format. */
55 print_dirent(void *baton,
57 const svn_dirent_t *dirent,
58 const svn_lock_t *lock,
60 const char *external_parent_url,
61 const char *external_target,
62 apr_pool_t *scratch_pool)
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;
69 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
70 (external_parent_url && external_target));
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");
77 if (pb->ctx->cancel_func)
78 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
80 if (strcmp(path, "") == 0)
82 if (dirent->kind == svn_node_file)
83 entryname = svn_dirent_basename(abs_path, scratch_pool);
87 /* Don't bother to list if no useful information will be shown. */
93 if (external_parent_url && external_target)
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))
100 SVN_ERR(svn_cmdline_printf(scratch_pool,
101 _("Listing external '%s'"
102 " defined on '%s':\n"),
104 external_parent_url));
106 pb->last_external_parent_url = external_parent_url;
107 pb->last_external_target = external_target;
113 apr_time_t now = apr_time_now();
114 apr_time_exp_t exp_time;
115 apr_status_t apr_err;
118 const char *sizestr, *utf8_timestr;
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))
127 apr_err = apr_strftime(timestr, &size, sizeof(timestr),
128 time_format_long, &exp_time);
132 apr_err = apr_strftime(timestr, &size, sizeof(timestr),
133 time_format_short, &exp_time);
136 /* if that failed, just zero out the string and print nothing */
140 /* we need it in UTF-8. */
141 SVN_ERR(svn_utf_cstring_to_utf8(&utf8_timestr, timestr, scratch_pool));
143 sizestr = apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT,
146 return svn_cmdline_printf
147 (scratch_pool, "%7ld %-8.8s %c %10s %12s %s%s\n",
149 dirent->last_author ? dirent->last_author : " ? ",
151 (dirent->kind == svn_node_file) ? sizestr : "",
154 (dirent->kind == svn_node_dir) ? "/" : "");
158 return svn_cmdline_printf(scratch_pool, "%s%s\n", entryname,
159 (dirent->kind == svn_node_dir)
165 /* This implements the svn_client_list_func2_t API, printing a single dirent
168 print_dirent_xml(void *baton,
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)
177 struct print_baton *pb = baton;
178 const char *entryname;
179 svn_stringbuf_t *sb = svn_stringbuf_create_empty(scratch_pool);
181 SVN_ERR_ASSERT((external_parent_url == NULL && external_target == NULL) ||
182 (external_parent_url && external_target));
184 if (strcmp(path, "") == 0)
186 if (dirent->kind == svn_node_file)
187 entryname = svn_dirent_basename(abs_path, scratch_pool);
189 /* Don't bother to list if no useful information will be shown. */
195 if (pb->ctx->cancel_func)
196 SVN_ERR(pb->ctx->cancel_func(pb->ctx->cancel_baton));
198 if (external_parent_url && external_target)
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))
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;
213 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "external",
214 "parent_url", external_parent_url,
215 "target", external_target,
218 pb->last_external_parent_url = external_parent_url;
219 pb->last_external_target = external_target;
220 pb->in_external = TRUE;
224 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "entry",
225 "kind", svn_cl__node_kind_str_xml(dirent->kind),
228 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "name", entryname);
230 if (dirent->kind == svn_node_file)
232 svn_cl__xml_tagged_cdata
233 (&sb, scratch_pool, "size",
234 apr_psprintf(scratch_pool, "%" SVN_FILESIZE_T_FMT, dirent->size));
237 svn_xml_make_open_tag(&sb, scratch_pool, svn_xml_normal, "commit",
239 apr_psprintf(scratch_pool, "%ld", dirent->created_rev),
241 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "author", dirent->last_author);
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");
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,
256 if (lock->expiration_date != 0)
257 svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
259 (lock->expiration_date, scratch_pool));
260 svn_xml_make_close_tag(&sb, scratch_pool, "lock");
263 svn_xml_make_close_tag(&sb, scratch_pool, "entry");
265 return svn_cl__error_checked_fputs(sb->data, stdout);
269 /* This implements the `svn_opt_subcommand_t' interface. */
271 svn_cl__list(apr_getopt_t *os,
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;
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;
284 svn_error_t *externals_err = SVN_NO_ERROR;
285 struct svn_cl__check_externals_failed_notify_baton nwb;
287 SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
291 /* Add "." if user passed 0 arguments */
292 svn_opt_push_implicit_dot_target(targets, pool);
296 /* The XML output contains all the information, so "--verbose"
298 if (opt_state->verbose)
299 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
300 _("'verbose' option invalid in XML mode"));
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));
310 if (opt_state->incremental)
311 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
312 _("'incremental' option only valid in XML "
316 if (opt_state->verbose || opt_state->xml)
317 dirent_fields = SVN_DIRENT_ALL;
319 dirent_fields = SVN_DIRENT_KIND; /* the only thing we actually need... */
322 pb.verbose = opt_state->verbose;
324 if (opt_state->depth == svn_depth_unknown)
325 opt_state->depth = svn_depth_immediates;
327 if (opt_state->include_externals)
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;
336 /* For each target, try to list it. */
337 for (i = 0; i < targets->nelts; i++)
339 const char *target = APR_ARRAY_IDX(targets, i, const char *);
340 const char *truepath;
341 svn_opt_revision_t peg_revision;
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;
349 svn_pool_clear(subpool);
351 SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
353 /* Get peg revisions. */
354 SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
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,
363 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
366 err = svn_client_list3(truepath, &peg_revision,
367 &(opt_state->start_revision),
370 (opt_state->xml || opt_state->verbose),
371 opt_state->include_externals,
372 opt_state->xml ? print_dirent_xml : print_dirent,
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: ");
383 return svn_error_trace(err);
385 svn_error_clear(err);
387 seen_nonexistent_target = TRUE;
392 svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
396 /* close the final external item's tag */
397 svn_xml_make_close_tag(&sb, pool, "external");
398 pb.in_external = FALSE;
401 svn_xml_make_close_tag(&sb, pool, "list");
402 SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
406 svn_pool_destroy(subpool);
408 if (opt_state->include_externals && nwb.had_externals_error)
410 externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
412 _("Failure occurred processing one or "
413 "more externals definitions"));
416 if (opt_state->xml && ! opt_state->incremental)
417 SVN_ERR(svn_cl__xml_print_footer("lists", pool));
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"));
423 return svn_error_compose_create(externals_err, err);