list-cmd.c revision 299742
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                                SVN_VA_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                        SVN_VA_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                        SVN_VA_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",
259                            SVN_VA_NULL);
260      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "token", lock->token);
261      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "owner", lock->owner);
262      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "comment", lock->comment);
263      svn_cl__xml_tagged_cdata(&sb, scratch_pool, "created",
264                               svn_time_to_cstring(lock->creation_date,
265                                                   scratch_pool));
266      if (lock->expiration_date != 0)
267        svn_cl__xml_tagged_cdata(&sb, scratch_pool, "expires",
268                                 svn_time_to_cstring
269                                 (lock->expiration_date, scratch_pool));
270      svn_xml_make_close_tag(&sb, scratch_pool, "lock");
271    }
272
273  svn_xml_make_close_tag(&sb, scratch_pool, "entry");
274
275  return svn_cl__error_checked_fputs(sb->data, stdout);
276}
277
278
279/* This implements the `svn_opt_subcommand_t' interface. */
280svn_error_t *
281svn_cl__list(apr_getopt_t *os,
282             void *baton,
283             apr_pool_t *pool)
284{
285  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
286  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
287  apr_array_header_t *targets;
288  int i;
289  apr_pool_t *subpool = svn_pool_create(pool);
290  apr_uint32_t dirent_fields;
291  struct print_baton pb;
292  svn_boolean_t seen_nonexistent_target = FALSE;
293  svn_error_t *err;
294  svn_error_t *externals_err = SVN_NO_ERROR;
295  struct svn_cl__check_externals_failed_notify_baton nwb;
296
297  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
298                                                      opt_state->targets,
299                                                      ctx, FALSE, pool));
300
301  /* Add "." if user passed 0 arguments */
302  svn_opt_push_implicit_dot_target(targets, pool);
303
304  if (opt_state->xml)
305    {
306      /* The XML output contains all the information, so "--verbose"
307         does not apply. */
308      if (opt_state->verbose)
309        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
310                                _("'verbose' option invalid in XML mode"));
311
312      /* If output is not incremental, output the XML header and wrap
313         everything in a top-level element. This makes the output in
314         its entirety a well-formed XML document. */
315      if (! opt_state->incremental)
316        SVN_ERR(svn_cl__xml_print_header("lists", pool));
317    }
318  else
319    {
320      if (opt_state->incremental)
321        return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
322                                _("'incremental' option only valid in XML "
323                                  "mode"));
324    }
325
326  if (opt_state->xml)
327    dirent_fields = print_dirent_xml_fields;
328  else if (opt_state->verbose)
329    dirent_fields = print_dirent_fields_verbose;
330  else
331    dirent_fields = print_dirent_fields;
332
333  pb.ctx = ctx;
334  pb.verbose = opt_state->verbose;
335
336  if (opt_state->depth == svn_depth_unknown)
337    opt_state->depth = svn_depth_immediates;
338
339  if (opt_state->include_externals)
340    {
341      nwb.wrapped_func = ctx->notify_func2;
342      nwb.wrapped_baton = ctx->notify_baton2;
343      nwb.had_externals_error = FALSE;
344      ctx->notify_func2 = svn_cl__check_externals_failed_notify_wrapper;
345      ctx->notify_baton2 = &nwb;
346    }
347
348  /* For each target, try to list it. */
349  for (i = 0; i < targets->nelts; i++)
350    {
351      const char *target = APR_ARRAY_IDX(targets, i, const char *);
352      const char *truepath;
353      svn_opt_revision_t peg_revision;
354
355      /* Initialize the following variables for
356         every list target. */
357      pb.last_external_parent_url = NULL;
358      pb.last_external_target = NULL;
359      pb.in_external = FALSE;
360
361      svn_pool_clear(subpool);
362
363      SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
364
365      /* Get peg revisions. */
366      SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
367                                 subpool));
368
369      if (opt_state->xml)
370        {
371          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
372          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "list",
373                                "path", truepath[0] == '\0' ? "." : truepath,
374                                SVN_VA_NULL);
375          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
376        }
377
378      err = svn_client_list3(truepath, &peg_revision,
379                             &(opt_state->start_revision),
380                             opt_state->depth,
381                             dirent_fields,
382                             (opt_state->xml || opt_state->verbose),
383                             opt_state->include_externals,
384                             opt_state->xml ? print_dirent_xml : print_dirent,
385                             &pb, ctx, subpool);
386
387      if (err)
388        {
389          /* If one of the targets is a non-existent URL or wc-entry,
390             don't bail out.  Just warn and move on to the next target. */
391          if (err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND ||
392              err->apr_err == SVN_ERR_FS_NOT_FOUND)
393              svn_handle_warning2(stderr, err, "svn: ");
394          else
395              return svn_error_trace(err);
396
397          svn_error_clear(err);
398          err = NULL;
399          seen_nonexistent_target = TRUE;
400        }
401
402      if (opt_state->xml)
403        {
404          svn_stringbuf_t *sb = svn_stringbuf_create_empty(pool);
405
406          if (pb.in_external)
407            {
408              /* close the final external item's tag */
409              svn_xml_make_close_tag(&sb, pool, "external");
410              pb.in_external = FALSE;
411            }
412
413          svn_xml_make_close_tag(&sb, pool, "list");
414          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
415        }
416    }
417
418  svn_pool_destroy(subpool);
419
420  if (opt_state->include_externals && nwb.had_externals_error)
421    {
422      externals_err = svn_error_create(SVN_ERR_CL_ERROR_PROCESSING_EXTERNALS,
423                                       NULL,
424                                       _("Failure occurred processing one or "
425                                         "more externals definitions"));
426    }
427
428  if (opt_state->xml && ! opt_state->incremental)
429    SVN_ERR(svn_cl__xml_print_footer("lists", pool));
430
431  if (seen_nonexistent_target)
432    err = svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
433          _("Could not list all targets because some targets don't exist"));
434  else
435    err = NULL;
436
437  return svn_error_compose_create(externals_err, err);
438}
439