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